JS闭包
前景知识
函数每被调用一次,都会产生一个新的执行上下文环境。
函数在定义的时候(不是调用的时候),就已经确定了函数体内部自由变量的作用域。
在执行代码之前,把将要用到的所有的变量都事先拿出来,有的直接赋值了,有的先用undefined占个空。
处于活动状态的执行上下文环境只有一个。其实这是一个压栈出栈的过程——执行上下文栈。
作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。
作用域在函数定义时就已经确定了。而不是在函数调用时确定。
作用域只是一个“地盘”,一个抽象的概念,其中没有变量。要通过作用域对应的执行上下文环境来获取变量的值。
作用域中变量的值是在执行过程中产生的确定的,而作用域却是在函数创建时就确定了。
所以,如果要查找一个作用域下某个变量的值,就需要找到这个作用域对应的执行上下文环境,再在其中寻找变量的值。
在A作用域中使用的变量x,却没有在A作用域中声明(即在其他作用域中声明的),对于A作用域来说,x就是一个自由变量。
如果跨了一步,还没找到呢?——接着跨!——一直跨到全局作用域为止。要是在全局作用域中都没有找到,那就是真的没有了。
这个一步一步“跨”的路线,我们称之为——作用域链。
我们拿文字总结一下取自由变量时的这个“作用域链”过程:(假设a是自由量)
第一步,现在当前作用域查找a,如果有则获取并结束。如果没有则继续;
第二步,如果当前作用域是全局作用域,则证明a未定义,结束;否则继续;
第三步,(不是全局作用域,那就是函数作用域)将创建该函数的作用域作为当前作用域;
第四步,跳转到第一步。
要到创建这个函数的那个作用域中取值——是“创建”,而不是“调用”,切记切记
闭包
简单来说,闭包就是能够读取其他函数内部变量的函数。
由于在JS中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。
闭包的作用:封装变量,收敛权限
闭包应用的两种情况:函数作为返回值,函数作为参数传递。
1.函数作为返回值
function fn(){
var max = 10;
return function bar(x){
if(x>max){
console.log(x);
}
}
}
var f1 = fn();
f1(15); //15
2.函数作为参数被传递
var max = 10,
fn = function(x){
if(x>max){
console.log(x);
}
};
(function(f){
var max = 100;
f(15);
})(fn); //15 max取10,要注意
要去创建这个函数的作用域取值,而不是“父作用域”。
有些情况下,函数调用完成之后,其执行上下文环境不会接着被销毁。这就是需要理解闭包的核心内容。
使用闭包会增加内容开销,因为有些函数执行完了,但上下文环境并不能释放,依旧保存在执行上下文栈中。
闭包的神奇特性:闭包可以捕获到局部变量和参数的外部函数绑定,即便外部函数的调用已经结束。
var scope = "global scope";
function checkScope() {
var scope = "local scope";
function f() {
return scope;
}
return f;
}
checkScope()(); //=> "local scope"
闭包的应用场景
-
- 使用闭包代替全局变量
//全局变量,test1是全局变量
var test1=111
function outer(){
alert(test1);
}
outer(); //111
alert(test1); //111
//闭包,test2是局部变量,这是闭包的目的
//我们经常在小范围使用全局变量,这个时候就可以使用闭包来代替。
(function(){
var test2=222;
function outer(){
alert(test2);
}
function test(){
alert("测试闭包:"+test2);
}
outer(); //222
test(); //测试闭包:222
})();
alert(test2); //未定义,这里就访问不到test2
- 2.通过循环给页面上多个dom节点绑定事件
for(var i = 0, len = btns.length; i < len; i++) {
(function(i) {
btns[i].onclick = function() {
alert(i);
}
}(i))
}
-
3.柯里化 函数作为返回值。
-
4 结果缓存
-
5 封装
-
6 实现类和继承
-
7 匿名自执行函数
参考原文:王福朋的博客园