今天我们一起来谈论JavaScript中的闭包,不过你要做好心理准备,因为闭包有非常多难懂的知识点,我也很困惑为什么有些人会对JS小白编一些很难理解的例子来讲解闭包。所以今天我将用最简单的例子来让你清楚的理解闭包。我打赌你看完这篇文章会对JavaScript中的闭包有基本的理解。
就如在大多数编程语言中一样,创建一个函数,并在函数内声明了形参和一些内部变量,就如以下代码片段:
var addTo = function(passed){
var inner = 2;
return passed + inner;
}
console.log(addTo(3)); // 5
如上,声明一个变量名叫做 addTo 的函数,该函数接收一个参数,在函数内部这一参数变量名叫做 passed 。并且还在函数内部声明一个变量名叫做 inner 的变量,并给它赋值为2。最后将变量 passed 和 inner 两个值相加后的结果返回,返回值也就是该函数执行后的值。当我们调用该函数,并为它传入数字3,显然 3+2 结果是 5,所以控制台会输出 5。
如果我们将上面的代码片段进行一些修改:
var passed = 3;
var addTo = function(){
var inner = 2;
return passed + inner;
}
console.log(addTo());
我们在函数 addTo 的外部作用域声明了一个变量名为 passed 值为 3 的变量。并且将函数 addTo修改为不需要传任何参数,但是 addTo 函数中依然需要用到一个叫做 passed 的变量,最后执行 console.log() 方法(显然,函数未定义形参,所以也不需要再传入实参了),在控制台输出的结果依然是 5。
看起来一切都再正常不过了,看似执行结果也满足我们的期望。其实上面的代码其实创建了一个 闭包环境 ,也许看起来和你曾经见过的 函数嵌套函数 看起来不一样(因为我不希望你一开始就被多层嵌套关系吓住而失去学习的信心),不过这种情况的确是创建了一个闭包,一个非常简单的闭包。
下面你将再看另一个比较复杂的创建闭包环境的例子。
在JavaScript中,在函数外部创建的变量,会自动在函数内部可以访问到,因为JavaScript使用一种叫做 词法作用域 的机制,表现出来的规则就是:外部无法访问内部变量,而内部可以访问外部定义的变量。而实现这一机制和规则的就是 闭包。
有一次一个家伙让我解释什么是闭包,我把上面的两则代码片段告诉他,他说不,这不是闭包,他觉得内部函数的内部又声明了一个函数的那才叫闭包。当然,是的,在函数内部创建一个函数,并且在内部的函数中使用到了该内部函数外的一些变量,也会创建闭包环境。但是上面两则代码片段的确也能称得上是 闭包 ——最简单的闭包。
理论上,任何直接使用外部变量的函数都称得上是闭包(这也是为什么多种代码规范中会提到多写 纯函数 避免不必要的闭包的产生,纯函数就是通过参数获取外部变量,并且如果传入的是引用值,比如数组、对象等,则不建议直接对该形参变量进行操作),你完全可以认为 一个JavaScript文件在浏览器中被解释执行时,是放在一个函数中执行的。 这样想的话,全局中声明的函数,其实就类似于在函数中声明的函数。因为 全局 和 局部 的概念总是相对的。
如果你还是觉得在全局变量中声明的函数,就算函数内部使用了外部变量,也称不上是闭包的话,没关系,继续往下看。
我们知道,数组、对象、函数,他们三者在JavaScript中本质上都是对象,对象就是包含一些属性和方法的集合。既然在JavaScript中函数也是对象,我们不妨将函数展开,看看它都有那些属性和方法:
可以清楚的看到,addTo 函数作用域链上存在一个闭包环境,该上下文中存储的就是在全局中定义的变量 passed 。
下面给出一则公认的闭包案例:
var addTo = function(passed){
var add = function(inner){
return passed + inner;
}
return add;
}
console.dir(addTo(1));
将上方代码执行可以发现,内部函数 add 在函数返回后依然可以访问到为 addTo 传入的参数。
希望你看完这篇文章能对闭包有全新的认识。