【前端】js闭包理解整理

什么是闭包?

函数与对其状态即词法环境(lexical environment)的引用共同构成闭包(closure)。

闭包可以让你从内部函数访问外部函数作用域。在JavaScript,函数在每次创建时生成闭包。
简要来讲,闭包就是一个函数引用另外一个函数的变量,因为变量被引用着所以不会被回收,因此可以用来封装一个私有变量。

MDN 上面这么说:闭包是一种特殊的对象。

词法作用域

废话不说,上代码:

function init() {
    var name = "first"; // name 是一个被 init 创建的局部变量
    function consoleName() { // displayName() 是内部函数,一个闭包
        console.log(name); // 使用了父函数中声明的变量
    }
    consoleName();
}
init(); //输出 first 

函数init创建了一个局部变量name,和一个名为consoleName的内部函数。consoleName没有自己的局部变量,然而它可以访问到外部函数的变量,于是输出‘first’;

闭包

理解闭包的关键在于:外部函数调用之后其变量对象本应该被销毁,但闭包的存在使我们仍然可以访问外部函数的变量对象,这就是闭包的重要概念。

function outer(){
	var a = 1;
	return function (){
		return a;
	}
}
const b = outer();
console.log(b());	// 1

以上就是一个闭包,b得到的是outer函数内部返回回来的函数,因而在调用b时,可以访问到outer的内部变量a。创建闭包最常见方式,就是在一个函数内部创建另一个函数。

常见的陷阱,闭包只能取得包含函数中的任何变量的最后一个值

下面这个例子:

function createFunctions(){ 
	var result = new Array(); 
	for (var i=0; i < 10; i++){
        result[i] = function(){ 
        	return i;
        };
    } 
    return result;
} 
var funcs = createFunctions(); 
for (var i=0; i < funcs.length; i++){
    console.log(funcs[i]());
}

输出10个10
这里的陷阱就是:函数带()才是执行函数! 单纯的一句 var f = function() { alert(‘Hi’); }; 是不会弹窗的,后面接一句 f(); 才会执行函数内部的代码。
上面的代码翻译过来就是

var result = new Array(), i;
result[0] = function(){ return i; }; //没执行函数,函数内部不变,不能将函数内的i替换!
result[1] = function(){ return i; }; //没执行函数,函数内部不变,不能将函数内的i替换!
...
result[9] = function(){ return i; }; //没执行函数,函数内部不变,不能将函数内的i替换!
i = 10;
funcs = result;
result = null;

console.log(i); // funcs[0]()就是执行 return i 语句,就是返回10
console.log(i); // funcs[1]()就是执行 return i 语句,就是返回10
...
console.log(i); // funcs[9]()就是执行 return i 语句,就是返回10

为什么只垃圾回收了 result,但却不收了 i 呢?
因为 i 还在被 function 引用着。当createFunctions执行完毕后,其作用域被销毁,但它的变量对象仍保存在内存中,得以被匿名访问,这时i的值为10。
要想保存在循环过程中每一个i的值,需要在匿名函数外部再套用一个匿名函数,在这个匿名函数中定义另一个变量并且立即执行来保存i的值。

function createFunctions(){ 
	var result = new Array(); 
	for (var i=0; i < 10; i++){
        result[i] = function(){ 
        	return function(){
        	
        		return i;
			}(i);
        };
    } 
    return result;
} 
var funcs = createFunctions(); 
for (var i=0; i < funcs.length; i++){
    console.log(funcs[i]());
}

这时最内部的匿名函数访问的是num的值,所以数组中10个匿名函数的返回值就是1-10。

闭包中的this对象

var name = 'window';
var obj = {
	name:'obj',
	getName:function(){
		return function(){
			return this.name;
		}
	}
}
console.log(obj.getName()())

此处调用obj.getName()后返回的是一个匿名函数,所以this指向window。
函数名与函数功能是分割开的,不要认为函数在哪里,其内部的this就指向哪里。
window才是匿名函数功能执行的环境。
如果想使this指向外部函数的执行环境,可以这样改写:

var name = 'window';
var obj = {
	name:'obj',
	getName:function(){
		const s = this;
		return function(){
			return s.name;
		}
	}
}
console.log(obj.getName()())

在闭包中,arguments与this也有相同的问题。下面的情况也要注意:

var name = 'window';
var obj = {
	name:'obj',
	getName:function(){
		return this.name;
	}
}
obj.getName();	//obj;
(obj.getName = obj.getName)(); //window

obj.getName();这时getName()是在对象obj的环境中执行的,所以this指向obj。

(obj.getName = obj.getName)赋值语句返回的是等号右边的值,在全局作用域中返回,所以(obj.getName = obj.getName)();的this指向全局。要把函数名和函数功能分割开来。

闭包的注意事项

通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。但是,在创建了一个闭包以后,这个函数的作用域就会一直保存到闭包不存在为止。
因此需要及时释放闭包内的函数。

function makeAdd(x){
	return function(y){return x+y}
}
var md5 = makeAdd(5);
var md10 = makeAdd(10);
console.log(md5(2));	//7
console.log(md10(2));	//12

//释放对闭包的引用
md5 = null;
md10 = null;

在javascript中,如果一个对象不再被引用,那么这个对象就会被垃圾回收机制回收;
如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。

函数内部的定时器

当函数内部的定时器引用了外部函数的变量对象时,该变量对象不会被销毁。

(function(){
	var a = 0;
	setInterval(function(){
		console.log(a++);
	},1000);
})();

闭包的应用

应用闭包的主要场合是:设计私有的方法和变量。

闭包运用的关键

闭包引用外部函数变量对象中的值;
在外部函数的外部调用闭包。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值