js闭包

闭包,就是将不在自己作用域范围内的变量通过包装,然后作为另一个函数的入参或者返回值,使得在其他的作用域也能被访问。由于实现了跨作用域的变量访问,所以除非变量被释放,否则的话变量占用的内存一直都不会被回收。闭包跟作用域之间其实息息相关。所以在聊闭包之前,先看看下面的代码。

  • 案例一
    使用表达式函数的时候,此时dom就是一个普通的函数对象的实例,跟普通的对象没什么区别。Display函数是prototype的属性,所以直接通过dom是调用不到的。通过new创建对象d,通过d.Display()的时候会到原型对象中去查找,所以打印‘Property Message’,但是通过对象实例调用Show方法,d对象自己本身没有这个方法,在原型对象中也找不到该方法,所以会报错。
var dom = function(){
};
dom.Show = function(){
	console.log("Show Message");
};
dom.prototype.Display = function(){
	console.log("Property Message");
};
dom.Display(); // 报错,函数不存在
dom.Show();// Show Message
var d = new dom();
d.Display();// Property Message
d.Show();// 报错
  • 案例二
    由于js的作用域是函数级别的,所以在函数外面是访问不到函数中的变量的。
var dom = function(){ 
	var Name = "Default";
	this.Sex = "Boy";
	this.success = function(){
	console.log("Success");
	};
};
console.log(dom.Name);// undefined
console.log(dom.Sex);// undefined
闭包
存在的意义

如果想在一个函数中访问另一个函数的变量,直接访问是访问不到的,因为js是函数作用域,函数中的变量对于函数外部来说是不透明的。如果要解决这种需要,那就可以使用闭包。当然这只是闭包可以解决的一个小方面。利用闭包可以解决上面作用域的问题。

闭包的例子
var fn;
function foo() {
	var a = 2;
	function baz() { 
		console.log( a );
	}
	fn = baz; // 将 baz 分配给全局变量
}
function bar() {
	fn(); // 这就是闭包!在bar的作用域中访问了foo作用域中的a变量
}
foo();
bar(); // 2
应用
  • 控制变量的作用域
    如果定义一个函数表达式 let fn = function(){},如果此时要执行该函数,则是fn()。由于fn与function(){}的含义是一样的,所以可以用function(){}替换fn,则是function(){}(),如果只是这样写的话会报错,所以需要使用一个括号将函数的定义包起来,则是(function(){})(),也就是下面的代码。
    由于js是函数作用域,所以对于content中的变量,对于外部来说是不可见的,所以起到了隔离的效果,不会造成全局污染,利用闭包来实现作用域的控制。
(function (argument) {
	// content
})(); // 其实从这个角度来看,闭包其实就是为来解决作用域问题的
  • 实现共享变量,数据的存储
    利用返回值,向外暴漏一个对象,在对象中实现了添加和获取缓存的方法,则外部可以实现缓存,这实际也是共享的一个实现方式。
var CachedSearchBox = (function(){    
    var cache = {},// 由于所有调用返回函数的时候都是使用同一个cache,也就是同一份存储空间,所以对于cache来说相当于一个全局存储器
       	count = []; 
	return {    
		attachSearchBox : function(dsid){    
           if(dsid in cache){//如果结果在缓存中    
              return cache[dsid];//直接返回缓存中的对象    
           }  
           var fsb = new uikit.webctrl.SearchBox(dsid);//新建    
           cache[dsid] = fsb;//更新缓存    
           if(count.length > 100){//保正缓存的大小<=100    
              delete cache[count.shift()];    
           }    
           return fsb;          
       },    
       clearSearchBox : function(dsid){    
           if(dsid in cache){    
              cache[dsid].clearSelection();      
           }    
       }    
    };    
})();    
  • 作为回调函数
function setupBot(name, selector) {
	$(selector).click( function activator() { // 这里就利用了闭包,name变量的作用域本来是在setupBot中,函数执行完毕之后,name占用的内存不会被回收,因为闭包传递给了回调函数的缘故。
    	console.log( "Activating: " + name );
    });
}
setupBot( "Closure Bot 1", "#bot_1" );
setupBot( "Closure Bot 2", "#bot_2" );
带来的问题

由于js存在变量提升,所以变量i的声明会被提升到全局作用域,并且执行初始化,则是var i = undefined;当循环执行结束之后,i并不会被销毁,因为回调函数中引用了i变量,此时i的值为6,当5个触发器执行的时候,其实共用的都是同一个全局的内存变量i,所以会打印出5个6.这里的问题就是所有的回调函数都共用了同一个内存变量,所以打印的结果都是一样的。

for (var i=1; i<=5; i++) { 
	setTimeout(function timer() {
        console.log( i );
    }, i*1000 );
}

解决的办法其实有很多。
1、利用作用域来进行规避

for (var i=1; i<=5; i++) { 
	(function(i){ // 由于js是函数作用域,这里使用一个立即执行函数,将回调函数中的i与外部的全局变量分隔开,使用了局部变量。所以在执行回调的时候不会被外部的i变量影响。其实这里也可以理解为创建了多个变量,将变量传递给函数的时候,相当于在函数内重新声明一个变量,然后将每一个变量都传递给回调函数,所以彼此之间不会相互影响。
		setTimeout(function() {
        	console.log( i );
    	}, i*1000 );
	})(i);
}

2、使用let来声明变量。其实对于let变量来说,也存在变量提升的问题,则是let i; 但是并没有执行初始化,所以如果此时打印i的话会报错,与var不一样,var是打印undefined。其实看到这里可能还不明白为什么使用let就能规避这个问题,因为对于所有的回调函数来讲其实还是共用一个let变量。但其实这种写法默认隐藏了一行很重要的代码,请看代码二。在代码二中,标记出来的那行代码,其实就是解决问题的关键所在,在执行每一次的循环的时候,都会重新定义一个let变量,由于作用域的关系,此时回调函数中使用的变量就变成了这个新定义的变量i,由于let是块作用域,所以let的声明不会提升到循环块的最外面,这也是很关键的,不然的话变量提神,这样的话又只是声明一次变量。由于每次都定一个新的变量,所以回调函数之间相互不影响,这种其实也跟上面的解决方案大同小异。

for (let i=1; i<=5; i++) { 
	setTimeout(function() {
        console.log( i );
    }, i*1000 );
}
代码二:
for (let i=1; i<=5; i++) { 
	let i = i; // 很重要!!!
	setTimeout(function() {
        console.log( i );
    }, i*1000 );
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值