词法作用域
词法作用域中使用的域,是变量在代码中声明的位置所决定的
闭包
个人理解:
-
闭包首先是一个函数
-
这个函数在一个函数作用域内(这里姑且称这个父函数为Func, 比如下方的
makeAdder())被声明,可以使用作用域内的局部变量, 且保持对这个作用域的引用.(以上两点为定义,第3条是应用场景和产生的影响)
-
这个函数在Func作用域以外被调用, 如果它调用了Func作用域的局部变量,就会导致内存泄漏.
function makeAdder(x) { function bar(y) { return x + y; }; return bar; } //add5 和 add10 类型都是function ,因为makeAdder(x)返回的类型为function var add5 = makeAdder(5);// typeof add5 === function var add10 = makeAdder(10);//typeof add10 === function console.log(add5(2)); // 7 console.log(add10(2)); // 12
你不知道的JavaScript(上卷) 中提及:
闭包是基于词法作用域书写代码时所产生的自然结果, 你甚至不需要为了利用它们而有意识地创建闭包。 闭包的创建和使用在你的代码中随处可见。 你缺少的是根据你自己的意愿来识别、 拥抱和影响闭包的思维环境。
定义:
当函数可以记住并访问所在的词法作用域时,就产生了闭包. 即使函数是在当前词法作用域中之外执行的.
或者:
bar()函数依然持有对
makeAdder(x)作用域的引用, 而这个引用就叫做闭包.
为什么要有闭包?
MDN上的解释为:
因为它允许将函数与其所操作的某些数据(环境)关联起来。这显然类似于面向对象编程。在面向对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联。
也就是说, 我们可以用闭包模拟私有方法,类似于java中的中的私有方法, 而原生JavaScript没有这种支持
| 类型 | 结果 |
|---|---|
| Undefined | "undefined" |
| Null |
"object" 这儿有坑
|
| Boolean | "boolean" |
| String | "string" |
| Symbol | "symbol" |
| Number | "number" |
| 宿主对象(由JS环境提供) | Implementation-dependent |
| 函数对象([[Call]] 在ECMA-262条款中实现了) | 函数对象([[Call]] 在ECMA-262条款中实现了) |
| 任何其他对象 "" | "object" |
在循环中中使用闭包所导致的问题
function loop(){
var arr = [];
for(var i = 0;i<5;i++){
arr.push(function(){
print(i);
//console.log(i);
})
}
return arr;
}
function print(x){
console.log(x);
}
var test = loop();
test[0]();// 5
test[1]();//5
关于这种现象, MDN的解释为:
由于循环在事件触发之前早已执行完毕,变量对象
i(被三个闭包所共享)已经指向了的最后一项。
也就是5. 不过我不太明白这里为什么"循环在事件触发之前早已执行完毕"这句话. 我的粗浅分析是: 由于JS中没有块级作用域, 所有这里的for循环和arr.push(...)处于同一级作用域,而js在编译时会对loop进行函数提升, 然后执行完for循环之后i的值就变成了5. 在test[0]()处调用print()时,引擎在作用域内查找i,这个作用域就是刚刚提到的和for循环同级的作用域. 而这时for循环在loop()编译时早已执行了,所以i的值是5.
个人理解:
如果函数的调用时的当前作用域不处于声明时的当前作用域, 那么就产生了闭包. 这个当前作用域指的是函数所处的最近作用域,举例来说:
function foo(){//foo的当前作用域:全局window对象
function bar(){//bar的当前作用域:foo
console.log("hello");// console.log的作用域栈 bar→foo→全局(window对象), 当前作用域: bar
}
}
所以, 总结一下我理解的闭包就是(只看加粗部分就行):
(为了解决外部作用域能访问一个函数作用域内局部变量的问题,类似java中的私有方法)
-
我们在函数内定义一个函数,这个函数可以访问父函数的局部变量,
(这很显然是可以访问的) -
这个函数也能被外部作用域调用
(类似前面var test = loop(); test[0]();, 外部的test[0]()调用了loop的内部函数) -
这个函数就是闭包. 这个函数就是闭包. 对,这就是闭包
当这个闭包被外部作用域调用时, 能够保持对父函数作用域的引用
(就是它能一直访问局部变量啦, 哪怕父函数早已执行完毕,本应该销毁这个变量, 然后这个变量"命不该绝", 一直存在着, 所以导致了内存泄漏,所以这里就应该提及垃圾回收机制了)










网友评论