JavsScript 闭包 (Closure)
欢迎各位大佬指导!!!
前言:
要理解闭包首先需要了解javascript的变量作用域,JavaScript的变量作用域分为两种:
- 全局作用域
- 局部作用域
全局变量的作用域是全局性的,在整个javaScript程序中都在,而在函数内声明的变量作用域,只在函数内起作用。
eg: 在JavaScript中函数内部可以直接访问全局作用域的变量
var a = 123; // 定义全局变量 a
function fn(){
alert(a); // 在函数内部访问全局变量a
}
fn(); // 调用函数 fn() 弹框结果 123
但是函数内的局部变量,在外面不能访问到。
function fn(){
var a = 123; // 定义局部变量
}
alert(a); // a 未定义
注意:在函数内部定义局部变量时,如果没有用 var 声明,那这个变量就是全局变量。即外部就可以访问到这个变量。
如果想要获取函数内定义的局部变量,那么用闭包就可以获取到函数内部的局部变量。
闭包的概念 :
当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,且外部(父)函数被调用,就产生了闭包,简单的说,JavaScript 允许使用内部函数,即函数的定义和函数的表达式位于另一个函数体内,并且这些函数引用所在外部函数内中声明的局部参数、变量、和声明的其它内部函数,在最外层window作用域被调用时,就形成了闭包。
闭包的作用:
- 使函数的内部变量(函数)在函数执行完后,仍然存活在内存中,延长了局部变量的生命周期,变量始终保存在内存中,不会随着函数的结束而自动销毁。
- 将变量(函数)定义在闭包中使用,避免污染全局变量。
- 定义JS模块 模块中一般包含大量的变量和函数,如果不使用闭包,将会使这些变量(函数)暴露在全局作用域中,这就很有可能会与用户自己的代码产生冲突
闭包的隐患
- 函数执行完成后,函数内部的变量没有被释放,占用内存时间会变长容易造成内存泄露 ,应及时释放,将存在闭包的函数置为null。
是否使用闭包要考虑这两点:
隔离和数据保存。如果需要隔离数据并形成命名空间,可以使用匿名自执行函数 ;如果还需要在隔离状态下保存数据,保存之后后面还可以继续使用,那就可以使用闭包,如果及不需要隔离和数据保存,那就使用普通函数,在函数调用完后,数据就释放了。
来看下面的两端代码
代码一:
var name ="The Window";
var object = {
name : "My Object",
getNameFunc:function(){
return funcion(){
return this.name;
}
}
};
alert(object.getNameFunc() ());
运行结果: The Window
代码二:
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function(){
var that = this;
return function(){
return that.name;
}
}
};
alert(object.getNameFunc() ());
运行结果: My Object
思考一下,这是为何??
我们来分析一下,首先来看代码一:
// 首先在Javascript中 定义全局变量、全局对象、全局函数 都自动属于window对象的成员。也就是说它们都是window对象的属性,可以用window.出来。
var name = "The Window"; // 这是定义了一个name 的全局变量
var object = { // 这是定义了一个object 的全局对象
name: "My Obejct", // 局部变量 name
getNameFunc: function(){ // 局部函数 getNameFunc 返回的是一个匿名函数
return function(){
// 此处的this 指的是window对象,为什么这样说呢,是因为哪个对象调用this所在的函数,this指的就是哪个对象。
// alert(object.getNameFunc() ()) 首先调用了 object.getNameFunc的函数返回了一个匿名函数function(){return this.name} 此时这个匿名函数是全局函属于window对象,所以在调用匿名函数的时候,return this.name 的结果是 The Window
return this.name;
}
}
};
alert(object.getNameFunc() ());
我们再来分析一下 代码二:
var name = "The Window"; // 同样定义全局的name变量
var object = { // 定义全局的object全局对象
name: "My Object", // 定义局部变量
getNameFunc: function(){ // 定义局部函数 getNameFunc()
var that = this; // 此时当alert(object.getNameFunc()()) 调用时object调用getNameFunc函数,哪个对象调用this,this指的就是哪个对象,显然this指向的是object对象。然后 var that = this 将 object对象赋值给 that 然后返回匿名函数此时匿名函数中引用了that变量,所有当再次的调用匿名函数是return that.name 就等同于 return object.name 即输入结果为 My Object
return function(){
return that.name;
}
}
};
alert(object.getNameFunc() ());
OK,我们来看一道经典题
function fun(n,o){
console.log(o);
return {
fun:function(m){
return fun(m,n);
}
}
}
var a = fun(0);a.fun(1);a.fun(2);a.fun(3); //输入结果: undefined 0 0 0
var b = fun(0).fun(1).fun(2).fun(3); //输入结果: undefined 0 1 2
思考一下,OK ,我们来分析一下产生结果的原因。
var a = fun(0);a.fun(1);a.fun(2);a.fun(3); // 我们来拆分一下这段代码
var a= fun(0);
a.fun(1);
a.fun(2);
a.fun(3);
// 当执行第一行代码时,调用最外面的fun(n,o)函数,传入0,所以console.log(o)的结果为undefined,因为只传了一个参数。然后返回一个对象
// {fun:function(m){return fun(m,n)}}
// 将这个对象赋值给了变量a 即 a = {fun:function(m){return fun(m,n)}}
// 执行第二行代码时,调用fun:function(m)函数,此时m为1;因为a是第一次的对象传入了0。 所以此时的 n 为0;即 return fun(1,0) 再次调用最外面的fun(n,o)函数,打印o为0;
// 当执行第三行代码时,还是调用fun:function(m)函数,此时m为2;因为a是第一次的对象传入了0。 所以此时的 n 为0;即 return fun(2,0) 再次调用最外面的fun(n,o)函数,打印o为0;
// 当执行第四代码时,同上;打印o为0;
Ok ,第一个分析完毕,我们再来分析一下第二个输入出结果。
var b = fun(0).fun(1).fun(2).fun(3); // 同样我们把这行代码拆分一下
var b = fun(0)
.fun(1)
.fun(2)
.fun(3);
// 当执行第一行代码时,依然是调用最外面的fun(n,o)函数,并且传入n=0; 即第一次打印的o为undefined,然后返回一个对象
// {fun:function(m){return fun(m,n)}}
// 当执行第二行代码时,调用fun:function(m)函数,因 .fun(1) 此时m为1;此时 n 为上一次的值 0 ,即 return fun(1,0),再次调用最外面的函数 fun(n,o)
// 此时n 为 1; 打印 o 为 0 ,然后 return 返回。
// 当执行第三行代码时,调用fun:function(m)函数,因 .fun(2) 此时m为2;此时 n 为第二行执行后的值, n=1; 即 return fun(2,1) ,再次调用最外面的函数 fun(n,o) 此时 n 为 2 , o 为 1 ,即 打印 1,然后返回。
// 当执行第四行代码时,调用fun:function(m)函数,因 .fun(3) 此时m为3;此时 n 为第三行执行后的值, n=2; 即 return fun(3,2) ,再次调用最外面的函数fun(n,o) 此时 n 为 3 , o 为 2,即 打印 2, 然后返回。