闭包(*)

前言

今天好好的研究了一下闭包,顺便写一下自己的个人理解。

什么是闭包?

闭包是指有权访问另一个函数作用域中的变量的函数。闭包有如下特点:

  • 函数嵌套函数;
  • 函数外部可以读取内部的参数和变量;
  • 参数和变量不会被垃圾回收机制回收;

常见的闭包创建方式:在一个函数内部创建另一个函数,详见下面的例子:

function Foo(){
	var typeName = 'coffee';
	return function(){
		console.log(typeName);
	}
}
var func = Foo();
func();   // "coffee"

上面的例子中,按道理,typeName是函数Foo的内部变量,在函数执行完毕后,就会被js的垃圾回收机制,回收释放,但为什么我们还可以访问到typeName呢?那就是因为我们使用了闭包,所以闭包的用途之一:可以读取到函数内部的变量。

闭包究竟是如何做到这一点的呢?

        我们能访问到某个变量,那是因为该变量包含在我们可访问的作用域链上。在上面的例子中,我们先定义了Foo()函数,然后又在全局的作用域中调用了它。当我们第一次调用Foo()函数时,会创建一个包含this、arguments以及其他命名参数的值来初始化函数的活动对象。然而在函数内部定义的匿名函数,当它返回时,他的作用域链会被初始化为包含Foo函数的活动变量和全局变量对象。更为重要的是,Foo函数执行完毕后,其执行环境的作用域链会被销毁,但它的活动对象不会被销毁,因为匿名函数仍然在引用这个活动变量,直到匿名函数被销毁后,Foo函数的活动对象才会被销毁。

副作用

        基于作用域链的机制,使用闭包也会存在副作用,即闭包只能取得包含函数中的任何变量的最后一个值(尤其是在使用循环语句的时候特别要注意)因为闭包保存的整个变量对象,而不是某个特殊时刻的变量。详见下面例子

function createFun(){
	var result = [];
	for(var i=0;i<10;i++){
		result[i] = function () {
			return i;
		}
	}
	return result;
}
var test = createFun(); 
test[0](); // 10

上面的例子,从表面上看每个函数都会返回自己的索引值,即位置0的函数返回0,位置1的函数返回1,但实际每个函数都返回10。因为每个函数的作用域链中都保存着createFun()函数的活动变量,所以他们引用的都是同一个变量 i 。在函数createFun()执行完毕后,变量 i 的值为10,此时每个函数都引用着保存变量 i 的同一个活动变量,所以每个函数内部 i 的值都是10。

我们可创建另一个立即执行的匿名函数,利用闭包的另一作用:将函数内部的变量始终保存在内存中。使得强制返回每一时刻的变量的值。

function createFun(){
	var result = [];
	for(var i=0;i<10;i++){
		result[i] = function (num) {
			return function(){
				return num;
			};
		}(i);
	}
	return result;
}
createFun()[0]();   // 0

在上面的版本中,我们没有直接把闭包赋值给 result 数组,而是定义了一个匿名函数,并将立即执行该匿名函数的结果赋值给result 数组。而匿名函数有一个参数num,也就是最终要返回的值,在调用匿名函数的时候,我们将传入变量 i,由于函数参数是按值传递的,所以会将变量 i 的当前值赋值给参数 num。而在这个匿名函数内部,有创建并返回了一个可以访问num的闭包。这样一来,result 数组中的每个函数都有自己 num 变量的副本,因此可以返回不同的数值。

当然因为闭包在使用的时候会访问函数的内部作用域,导致函数的内部变量被引用,从而无法被GC机制回收,占用的内存也比普通函数要多,所以闭包不在使用时,要及时释放,将引用内层函数对象的变量赋值为null。

 

 

以上只是我个人的粗浅理解,如有不对,欢迎指正。详细的大家看可参看MDN上的文档,下附链接地址:

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures

#### JavaScript核心概念解析与简答题考点 --- ##### **一、变量声明机制** **概念内容** - `var`存在变量提升(声明提升至作用域顶部)和函数级作用域 - `let/const`具有块级作用域,存在暂时性死区(TDZ) - `const`声明必须初始化且不可重新赋值(对象属性可变) **简答题考点** - 对比`var`、`let`、`const`的特性差异 - 解释变量提升现象及实际影响 - 分析块级作用域对循环计数器的影响 ```javascript for (var i=0; i<3; i++) { setTimeout(() => console.log(i), 0); // 输出3次3 } ``` --- ##### **二、数据类型与内存管理** **概念内容** - 基本类型(栈内存):`Number`、`String`、`Boolean`等 - 引用类型(堆内存):`Object`、`Array`、`Function` - 类型检测方法:`typeof`、`instanceof`、`Object.prototype.toString` **简答题考点** - 解释基本类型与引用类型的存储差异 - 深拷贝与浅拷贝的实现原理及区别 - 为什么`typeof null`返回`"object"`(历史遗留问题) --- ##### **三、作用域与闭包** **概念内容** - 词法作用域(静态作用域)决定变量访问权限 - 闭包是函数及其引用环境的组合,可访问外部函数变量 - 常见应用:模块化、数据封装、防抖节流 **简答题考点** - 解释闭包的内存泄漏风险及解决方案 - 举例说明闭包在实践中的典型应用场景 - 分析以下代码输出: ```javascript function createFunc() { let name = "test"; return function() { console.log(name) }; } const func = createFunc(); func(); // 输
最新发布
04-28
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值