一、闭包
Javascript也采用词法作用域,也就是说,函数的执行依赖于变量作用域,这个作用域在函数定义时决定,而不是函数调用时决定的。为了实现这种词法作用域,Javascript函数对象的内部状态不仅包含函数的代码逻辑,还要引用当前的作用域链。
函数对象可以通过作用域链相互关联起来,函数体内部的变量可以保存在函数作用域内,称为闭包。
var scope = "global scope"; // 全局变量
function checkscope(){
var scope = "local scope"; // 局部变量
function f(){ return scope; } // 在作用域中返回这个值
return f();
}
checkscope() // => "local scope"
checkscope()函数声明了一个局部变量,并定义了一个函数f(),函数f()返回了这个变量的值,最后将函数f()的执行结果返回。你应当非常清楚为什么调用checkscope()会返回“local scope”。现在我们对这段代码做一点改动。你知道这段代码返回什么吗?
var scope = "global scope"; // 全局变量
function checkscope(){
var scope = "local scope"; // 局部变量
function f(){ return scope; } // 在作用域中返回这个值
return f;
}
checkscope()() // 返回值是什么?
词法作用域的基本规则:JavaScript函数的执行用到了作用域链,这个作用域链是函数定义的时候创建的。嵌套的函数f()定义在这个作用域链里,其中的变量scope一定是局部变量,不管在何时何地执行函数f(),这种绑定在执行f()时依然有效。因此最后一行代码返回“local scope”,而不是“global scope”。
简言之,闭包的这个特性可以捕捉到局部变量(和参数),并一直保存下来,看起来像这些变量绑定到了在其中定义它们的外部函数。
我之前看到一位大牛这样解释函数作用域的
如果把作用域的机制可视化,你可以想象有一个双向镜(单面透视玻璃)。你能从里面看到外面,但是外面的人不能看到你。如图所示:
函数作用域就像是双向镜一样。你可以从里面向外看,但是外面看不到你。 嵌套的作用域也是相似的机制,只是相当于有更多的双向镜。
我觉得他说的很形象所以拿来引用 嘿嘿^_^
实现闭包
理解闭包:函数定义时的作用域链到函数执行时依然有效。
每次调用JavaScript函数的时候,都会为之创建一个新的对象用来保存局部变量,把这个对象添加至作用域链中。当函数返回的时候,就从作用域链中将这个绑定变量的对象删除。如果不存在嵌套的函数,也没有其他引用指向这个绑定对象,它就会被当做垃圾回收掉。如果定义了嵌套的函数,每个嵌套的函数都各自对应一个作用域链,并且这个作用域链指向一个变量绑定对象。但如果这些嵌套的函数对象在外部函数中保存下来,那么它们也会和所指向的变量绑定对象一样当做垃圾回收。
var uniqueInteger =(function(){ // 定义函数并立即调用
var counter = 0;
// 函数的私有状态
return function(){
return counter++;
};
}());
console.log(uniqueInteger())
第一行代码看起来像将函数赋值给一个变量uniqueInteger,实际上,这段代码定义了一个立即调用的函数(函数的开始带有左圆括号),因此是这个函数的返回值赋值给变量uniqueInteger。现在,我们来看函数体,这个函数返回另外一个函数,这是一个嵌套的函数,我们将它赋值给变量uniqueInteger,嵌套的函数是可以访问作用域内的变量的,而且可以访问外部函数中定义的counter变量。当外部函数返回之后,其他任何代码都无法访问counter变量,只有内部的函数才能访问到它。 像counter一样的私有变量不是只能用在一个单独的闭包内,在同一个外部函数内定义的多个嵌套函数也可以访问它,这多个嵌套函数都共享一个作用域链。
有人可能会疑惑,不是说调用对象在函数执行完毕后就移除了作用域链吗,外围匿名函数(function(){})();
也是调用完毕了的,应该调用对象也没了才对。
是的,调用对象是在当前函数执行完毕后就结束引用,但是这里不要误解了上面uniqueInteger()的调用,
他并不是直接调用的外围函数,而是调用的嵌套函数,嵌套函数的作用域链是包含外围函数的作用域链的。所以在它的调用对象移除作用域链的时候是能够访问到这条作用域链上其他对象的属性并改变的。
思考
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()());
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function () {
var that = this;
return function () {
return that.name;
};
}
};
alert(object.getNameFunc()());
思考上面代码结果验证你是否了解了闭包。
接下来对于闭包还有几点需要注意:
- 在同一个作用域中定义两个闭包,这两个闭包共享同样的私有变量或变量。当然也会造成将不希望的共享的变量共享给其他的闭包。
- 关联到闭包的作用域都是'活动的'。
- 书写闭包时,this是JavaScript 的关键字,而不是变量。每个函数调用都包含一个this值,如果闭包在外部函数里无法访问this的除非外部函数将this转存为一个变量
- 闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。所以可以手动解除对匿名函数的引用,以便释放内存。
扩展:
JavaScript中的函数作用域的概念:在函数中声明的变量在整个函数体内都是可见的(包括在嵌套的函数中),在函数的外部是不可见的。不在任何函数内声明的变量是全局变量,在整个JavaScript程序中都是可见的。
还有不懂 请看上篇文章 变量作用域和作用域链
查看原文juejin.cn/post/684490…