闭包的概念
红宝书上时这样描述的:闭包指那些引用了另一个函数作用域中变量的函数,通常时在嵌套函数中实现的。
mdn上是这样描述的:一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)
用最简单来说闭包让你可以在一个内层函数中访问到其外层函数的作用域。
简单的闭包
简单看一个例子:
function fun() {
var name = "xiaotan"; // name 是一个被 fun 创建的局部变量
function displayName() { // displayName() 是内部函数,一个闭包
console.log(name); // xiaotan 使用了父函数中声明的变量
}
displayName();
}
fun();
了解他的词法作用域, 函数fun中由于一个变量name和一个函数displayName,而displayName没有自己的局部变量,所以他会去外层寻找name变量,这个词法作用域的例子描述了分析器如何在函数嵌套的情况下解析变量名。词法(lexical)一词指的是,词法作用域根据源代码中声明变量的位置来确定该变量在何处可用。嵌套函数可访问声明于它们外部作用域的变量。
再来看一个变化之后的例子
function newFun() {
var name = "xiaotan";
function displayName() {
console.log(name); //xiaotan
}
return displayName;
}
var myFun = newFun();
myFun();
对于一些其他的编程语言,可以myFun在执行完之后里面的name变量,就不能在被访问了,然而,在javascript中,他这样形成了一个闭包,闭包是由函数以及声明该函数的词法环境组合而成的。该环境包含了这个闭包创建时作用域内的任何局部变量。在本例子中,
myFun是执行makeFun时创建的displayName函数实例的引用。displayName的实例维持了一个对它的词法环境(变量name存在于其中)的引用。因此,当myFun被调用时,变量name仍然可用中。
这个也是闭包的一个优点: 保存,把一些函数内的值保存下来。闭包可以实现方法和属性的私有化 通常你使用只有一个方法的对象的地方,都可以使用闭包。
闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来。这显然类似于面向对象编程。在面向对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联。
简要概括他的作用域: 通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。但是,在创建了一个闭包以后,这个函数的作用域就会一直保存到闭包不存在为止。
闭包的应用
-
封装私有变量
上面这个例子就是其简单应用
-
绑定函数上下文
最经典的就是bind方法的实现了
Function.prototype.myBind = function(context){
if (typeof this !== 'function') {
throw new Error('not function')
}
context = context || window
const _this = this
const args = [...arguments].slice(1)
// 返回函数
return function F(){
// 如果F 被new
if(this instanceof F){
return new _this(...args,...arguments)
}
// 把this调用
return _this.apply(context, args.concat(...arguments))
}
}
-
函数内部的定时器
当函数内部的定时器引用了外部函数的变量对象时,该变量对象不会被销毁。
(function() {
var a = 0;
setInterval(function(){
console.log(a++);
}, 1000);
})();
闭包的缺点
缺点: 闭包的缺点就是常驻内存会增大内存使用量,并且使用不当很容易造成内存泄露。 闭包会携带包含其它的函数作用域,因此会比其他函数占用更多的内存。过度使用闭包会导致内存占用过多,所以要谨慎使用闭包。
一个经典的面试题
for(var i = 0; i <= 5;i++){
setTimeout(function() {
console.log(i); // 6 6 6 6 6 6
},i*1000)
}
改变代码让他输出依次0、1、2、3、4
有同学肯定就会想到ES6中的let了,把var改成let也可以实现,现在我们说另一种,也就是闭包的解决方式
for ( var i = 0 ; i <= 5; i++ ) { //形成了闭包
(function(j){
setTimeout(function(){
console.log(j); // 0 1 2 3 4 5
}, 0);
})(i);
}
改成这样就可以解决这个问题了
总结
如果不是某些特定任务需要使用闭包,在其它函数中创建函数是不明智的,因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响。