引言
首先我们得清楚一个东西,闭包不是一个原理,是我们在特殊使用JavaScript作用域时产生的一个概念。这句话说的又不清不楚了,也就是说,我们在学习闭包时不需要学习新的语法知识,也不需要学习新的语言模式,闭包可以看作是一些对作用域应用的特殊形式。换言而知,只要你用了闭包的书写形式那么这就是闭包。
闭包的书写形式
我们知道函数是JavaScript的一等公民,当函数被当作参数传入到另一个函数里面时,我们习惯把这种函数叫做高阶函数,当一个函数被他的爸爸函数return出去的时候,还保留着对爸爸函数属性的访问那么我们就叫做闭包。
function f() {
var mes = '看这就是闭包';
function speack() {
console.log(mes)
}
return speack;
}
var baz = f();
baz(); // 快看呀 这就是闭包的效果
闭包的书写形式进阶
在很长的时间断,我以为只要是上面的书写形式就是闭包,但是这远远是不够的
// 没有函数被返回 只是不在当前闭包函数的作用域内既可以产生闭包的效果
function foo() {
var a = 2;
function baz() {
console.log(2);
}
bar(baz);
}
function bar(fn) {
fn(); // 看呀,这就是闭包的效果
}
foo();
// 既没有函数被返回 也没有在外边被调用
function wait(mes) {
setTimeout(function timer() {
console.log(mes)
}, 1000)
}
wait('我也产生了闭包的效果呀')
所以,到底怎么才能彻底概括闭包?
- 《你不知道的JS》中概括到:当函数可以记住并访问所在的词法作用域(这时函数就有了闭包的功能),即使函数是在当前词法作用域之外执行(闭包的效果),这时就产生了闭包
- 阮一峰的博客:闭包就是能够读取其他函数内部变量的函数。由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
- MDN web docs:闭包是函数和声明该函数的词法环境的组合。
好了,那么我们的闭包就学完了!!!
闭包当然是学完了,但是闭包之所以难以理解,并不是在于闭包的概念有多复杂,完全是在于我们往往还不理解【词法作用域】,或者足够理解【词法作用域】就来试着理解闭包。这样的话,闭包对于我们来说,当然是晦涩难懂,充满神秘色彩。然而闭包的本质就是对词法作用域的巧妙应用。
闭包诞生的基石:词法作用域
词法作用域是什么鬼?变量作用域,函数作用域,我大概知道是什么东西,对es6的学习也知道了块作用域大致是什么鬼。
首先我们来看作用域的定义:是一套规则,是一套管理如何存储变量的规则,这套规则用来管理引擎如何在当前作用域以及嵌套的子作用域中根据标识符名称进行变量查找。
作用域:分为词法作用域和动态作用域,好了我要偷懒了,我们知道JavaScript是词法作用域就可以了,有关作用域的详细信息推荐阅读《你不知道的JavaScript 上》。
在这里我们知道作用域的一些相关性质有助于理解闭包就好了,毕竟我是来说闭包的,我才不管我忽略了最重要的东西。
作用域的某些性质
- 作用域是可以嵌套的
- 内部作用域可以访问外部作用域的参数和变量,直至全局作用域
- 外部作用域不能访问内部作用的参数和变量
我们就阮一峰闭包博客的思考题来感受下作用域对理解闭包的重要作用
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
// 这里闭包访问的属性是window作用的变量name
return function(){
return this.name; // this 换成 object 就是所期待的值
};
}
};
alert(object.getNameFunc()());
估计相当一部分人,看到这个题目很蒙,认为执行的结果是"The Window",但是这道题完全是在用this这个东西误导大家的判断,getNameFunc的第一个()是属于方法调用,所以this绑定到了object对象,自然this.name为"My Object",但是闭包函数无法访问这个this,它只能访问到全局的this。我们再看下一题。
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
// 这下 这个闭包访问的变量就是object这个作用域里面的属性了
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()()); // 啊呀 舒服了 是期待的值
阮一峰说:如果你能理解上面两段代码的运行结果,应该就算理解闭包的运行机制了。经过分析下来,其实是要完全理解作用域的机制,才能完美的理解闭包的运行机制。
总结
我更想把闭包理解成一中规律,因为作用域的一些特性,我们编写代码时正好让这些特性产生了一些好玩的规律,然后我们把这种规律叫做闭包。理解闭包的关键在于理解作用域。推荐《你不知道的JavaScript》
参考文章:
《你不知道的JavaScript》