总结: 闭包的核心是 [[scope]] 属性, 在函数解析过程中, 如果函数引用了外层函数的变量, 那么外层函数 (即使自身被销毁) 的活动对象带着对应变量将会被保留, 并且记录在 scope 属性中, 作为作用域链的第二层, 如果还引用了外层函数的外层函数的变量, 那么对应的活动对象与变量也会被保留, 并记录, 将会作为作用域链的第三层, 依次类推.... 当函数被调用时, 所取到的外部变量的值将会是调用时各个变量的值. 即当前值.
闭包调用时也是普通函数, 只不过作用域链多了闭包 Closure 的成分
来个极端的例子
function closure() {
let result = [],
count = 1 setInterval(() = >{
count++
},
1000) function outer() {
let doufu = 'wu'
return function inner() {
let foo = 'foo'
return function closureCallback() {
let bar = {
bar: 'bar'
},
bar1 = 'bar'result[i] = function() {
console.log(count) let b = doufu
return bar.bar + i
}
}
}
}
let inner = outer() let closureCallback = inner() for (var i = 0; i < 10; i++) {
// [[scope]]包含 closure{result, i}
// 函数在这里立即调用
// i 为实时值 0,1,2,3...
closureCallback()
}
return result
}
调用结果图
从图中可以看到, 最终返回结果分别引用了 closureCallback, outer,closure 中的变量, 所以, 在作用域链中会保存这三个函数的活动对象, 不同时间调用, 返回的 count 值不同, 说明引用的是当前值.
以下是手工示意图
scope 构成示意图
以下是我的学习过程
JavaScript 里面的闭包, 指的是函数与声明该函数的词法环境的组合.
一般在函数里面声明函数, 并且引用外面函数的变量, 就会产生闭包. 定义在全局的时候, 默认不产生闭包 (所谓闭包, 就是当内部函数引用了外部函数中的变量时, 会在函数的作用域上面添加一层, 这一层包含了函数所引用的外部函数的变量, 存放在 scope 属性里面, 在调用时, 用于形成作用域链)
函数在执行时, 会在内存中创建一个活动对象, 该活动对象包含 arguments 以及一些参数. 并通过复制 [[scope]] 属性中的对象构建起执行环境的作用域链.
var bar = 'zoo'
function Foo() {
this.bar = bar
}
全局
function foo() {
let bar = 'zoo'
function zoo() {
let zoo = bar
}
}
foo()
函数内
function closure() {
let result = []
for (var i = 0; i < 10; i++) {
result[i] = function() {
return i
}
}
return result
}
let arr = closure()
主要体现在函数返回函数, 函数 A 在调用函数 B 时被创建并从 B 函数的内部返回. 当我们调用 A 函数的时候, B 函数的作用域链已经从内存中销毁, 但是我们仍然可以在 A 中访问 B 中存在的变量. 因为 B 中的变量仍然保存在 A 的活动对象中(作用域链中 [[scope]] 对象里面)
此时, 函数 A 与 A 的 scope 构成 closure 函数的闭包实例
scope
从图中可以看到, ar[0](以下称为函数 A) 函数的作用域链最顶层为自身活动对象 (arguments, caller, length, name 等等构成) 再往上则是由一个 Closure 对象实例构成, 可以看到这一层里面只包含一个变量 i, 即创建 A 时外层函数里面声明的变量 i. 当我们调用 A 时, 我们在第二层作用域链上面找到的这个 i 变量.
为什么 arr 数组每一项都返回的是 10, 而不是对应的下标值的原因就在这里: 当我们调用数组项函数时, 遇到变量 i, 并且在第二层作用域链读取到 i, 这里面保存的 i 就是 closure 函数里面定义的 i. 在 A 调用时, closure 已经执完毕, 在 closure 执行的过程中 i 的值从 0 变到了 10. 这个性质类似于把原本存在于 closure 函数中的变量, 在 closure 函数执行完毕后 (从内存中移除) 我们可以在 A 自身的 scope 属性里访问到.
简单一点说就是我们在调用 A 函数的时候, 访问到的 i 变量, 是函数 closure(虽然它不在了但是它的变量还在, 仍然被 scope 属性引用.)的当前值(实时值)
所以要达到预期目标, 我们只需要保证它的 scope 对象中保存的这个'i'值是正确的就可以了.
这里面的思路就是, 函数 A 被当做普通函数调用时, 非闭包情况下, 作用域就是自身(没有 i 变量)+ 全局作用域(也没有), 所以这里还是需要借助闭包. 即需要保证 A 上一层作用域的 i 是正确的值
1. 创建另外一个闭包, 每个 i 的值都会创建一个闭包
function closureCallback(i) {
// 返回函数里面的 i 变量就是 closureCallback 函数的参数 i
return function() {
return i
}
}
function closure() {
let result = [], b = 'closure'
for (var i=0; i<10; i++) {
// 这里的 closureCallback 作为普通函数调用
// 且没有引用 closure 函数的变量,
// 函数作用域内的变量, 无法直接在函数外取得
// 所以作用域链不包含 closure
// 所以在 closureCallback 函数 [[scope]] 中不会有 closure 函数
result[i] = closureCallback(i)
}
return result
}
2. 使用匿名闭包
function closure() {
let result = [], b = 'closure'
for (var i=0; i<10; i++) {
result[i] = (function (i) {
return () => i
})(i)
}
return result
}
let arr = closure()
3. 使用 let, 减少闭包, let 具有块级作用域的效果
function closure() {
let result = [],
b = 'closure'
for (var i = 0; i < 10; i++) {
let j = i result[i] = function() {
return j
}
}
return result
}
let arr = closure()
let 形成块级作用域
写了一天... 有不当处望不吝赐教, 谢谢
来源: http://www.jianshu.com/p/68f30866a315