闭包-javascript
闭包就是:能够读取其他函数内部变量的函数
定义在一个函数内部的函数、
闭包可以将函数内部和函数外部连接起来
闭包用途
延长作用域链,因为闭包可以读取函数内部变量
让这些变量始终保持在内存中
更好的组织代码,比如模块化,异步代码转同步
闭包缺点
增加了内存的消耗,除非手动设置为null
某些浏览器上因为回收机制的问题,有内存溢出风险。
增加了代码的复杂度,维护和调试不便。
function f1() {
var n = 999;
nAdd = function () {
n += 1;
};
function f2() {
alert(n);
}
return f2;
}
var result = f1();
result(); //999
nAdd();
result(); //1000
result 实际是闭包f2(),因为result是全局,导致f2一直在内存中
而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收
做题帮助理解
- 1.
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()());
输出 the window
object.getNameFunc()为闭包,内容为return this.name,而this指向的是window
- 2.
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()());
输出 my object
object.getNameFunc()为闭包,内容为return that.name, 而that在上下文执行环境为object
换个题哈
- 1.
var name = 'global';
var obj = {
name : 'obj',
dose : function(){
this.name = 'dose';
return function(){
return this.name;
}
}
}
alert(obj.dose().call(this))
输出 global
var xxx = obj.dose();
xxx.call(this);
xxx为闭包,return this.name,利用call将this指向window
- 2.
var name = 'global';
var obj = {
name : 'obj',
dose : function(){
this.name = 'dose';
return function(){
return this.name;
}
}
}
alert(obj.dose()())
输出 global, 道理同1,相当于直接执行xxx()
- 3.
var name = 'global';
var obj = {
name : 'obj',
dose : function(){
this.name = 'dose';
return function(){
return this.name;
}
}
}
alert(obj.dose.call(this))
输出 function(){
return this.name;
}
obj.dose只是指向dose这个方法,.call(this)将执行方法,返回return 这个函数
4
var name = 'global';
var obj = {
name : 'obj',
dose : function(){
this.name = 'dose';
return function(){
return this.name;
}
}
}
var xxx = obj.dose;
xxx();
console.log(name);
输出 dose, window下执行xxx,将global改为dose
- 5.
var name = 'global';
var obj = {
name : 'obj',
dose : function(){
this.name = 'dose';
return function(){
return this.name;
}
}
}
obj.dose();
console.log(obj.name);
console.log(name);
输出 dose
global
var name = 'global';
var obj = {
name : 'obj',
dose : function(){
this.name = 'dose';
return function(){
return this.name;
}.bind(this)
}
}
alert(obj.dose().call(this))
输出 dose
bind(this),里面的this是指的obj, 因为obj.dose()
由于return的function中用了bind,所以相当于固定了this,外边再call什么进来,也只是碍眼法而已。由于函数内部邦定了this,所以此处的情况要另外分析了
首先,obj对象定义了name属性为’obj’;接着在dose 方法内,又改写了name属性为’dose’; 根据作用域链的就近原则,alert访问的肯定就是’dose’这个值了。
再接再厉
- db
var db = (function() {
// 创建一个隐藏的object, 这个object持有一些数据
// 从外部是不能访问这个object的
var data = {};
// 创建一个函数, 这个函数提供一些访问data的数据的方法
return function(key, val) {
if (val === undefined) { return data[key] } // get
else { return data[key] = val } // set
}
// 我们可以调用这个匿名方法
// 返回这个内部函数,它是一个闭包
})();
db('x'); // 返回 undefined
db('x', 1); // 设置data['x']为1
db('x'); // 返回 1
// 我们不可能访问data这个object本身
// 但是我们可以设置它的成员
- setTimeout
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 0);
console.log(i);
}
0, 1, 2, 3, 3, 3
setTimeout是异步的
setTimeout()有两个参数,第一个参数是函数,第二参数是时间值。
调用setTimeout时,把函数参数,放到事件队列中。等主程序运行完,再调用。
就像我们给按钮绑定事件一样:
btn.onclick = function() {
alert(1);
};
这么写完,会弹出1吗。不会!!只是绑定事件而已!
必须等我们去触发事件,比如去点击这个按钮,才会弹出1。
setTimeout也是这样的!只是绑定事件,等主程序运行完毕后,再去调用
setTimeout(fn, 2000)
我们可以理解为2000之后,再放入事件队列中,如果此时队列为空,那么就直接调用fn。如果前面还有其他的事件,那就等待。
继续看:
setTimeout(function() {
console.log(i);
}, 0);
var i = 1;
程序会不会报错?
不会!而且还会准确得打印1。
为什么?
因为真正去执行console.log(i)这句代码时,var i = 1已经执行完毕了!
现在我们解决setTimeout里console.log问题,
setTimeout里是一个匿名函数,这个函数可以调用全局变量,所以这个函数是个闭包
for循环里每次执行setTimeout都会讲这个匿名函数压入栈,等到主程序运行完再来事件队列中依次执行匿名函数,此时匿名函数访问到的i已经变成3了,所以后面会打印3次3.
解决办法
for (var i = 0; i < 3; i++) {
setTimeout((function(i) {
return function() {
console.log(i);
};
})(i), 0);
console.log(i);
}
输出:0(for),1,2,0(function),1,2
再创建一个闭包:
var xxx = function(i) {
return function() {console.log(i)}
}
xxx(i);
或
function f1(i) {
console.log(i);
}
for (var i=0; i<3; i++) {
setTimeout(f1.bind(null, i),0);
console.log(i);
}
输出:0(for),1,2,0(function),1,2
bind返回一个函数,并将参数i传入
但是,注意下面这样输出的结果
function f1(i) {
console.log(i);
}
for (var i=0; i<3; i++) {
setTimeout(f1(i),0);
console.log(i);
}
输出0(f1),0(for), 1,1, 2, 2
原因:setTimeout()中第一个参数是一个函数表达式,在解析时候就已经执行了f1(i),将i打印出来了,而且f1返回的是一个undifine。等到指定时间去检查发现不是function就不再执行
参考:
http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
http://www.cnblogs.com/afrog/p/4276709.html
http://www.qdfuns.com/notes/17398/e8a1ce8f863e8b5abb530069b388a158/page/.html