每个函数创建的时候, 就会创建一个词法作用域, 当在运行的时候, 创建一个新的词法环境(*注意, 这个词法环境还是要从函数定义的时候开始找, 不过在这个时候函数被定义的词法环境也是有可能改变的*
), 这两个作用与很有可能是不同的. 所以分析的时候一定是要看最新的作用域
词法环境包括自己内部的环境, 和引用的外部的环境. 当存在嵌套函数的时候, 外部环境可能还引用更加外部的环境, 就形成了一条作用域链. 最最里层的函数在执行的时候, 会根据这条链子, 由内而外寻找需要的变量. 然后可以对找到的变量进行修改, 修改是在该变量所在的作用域内修改, 也就是说对于闭包来讲, 修改的地方在该函数的外部环境修改, 而非克隆一份放低自己的内部环境内修改
普通(纯)函数是没有状态的, 闭包可以使得函数有状态, 有状态就意味着有记忆, 毕竟状态可以发生改变. 当然, 这里的状态, 不在函数内部, 而是在函数外部, 不过函数可以读取到
有了闭包就可以模拟类的一些效果, 闭包和类都是保存状态用的
每次调用一个函数, 如果这个函数存在闭包, 那么都会创建一个单独的闭包环境, 里面有该闭包的状态. 多次调用这个函数, 会创建多个闭包, 这些闭包内部的环境状态都是独立的. 类似于有一个类, 你可以进行多次实例化, 每次实例化出来的都是不同的对象
一个比较好的解释:
一个函数在调用的时候, 内部的自由变量, 要到这个函数被定义的地方去找, 而不是在这个函数当前被调用的地方去找 这个函数连同它被定义时的环境一起, 构成了一个数据结构, 就是闭包
下面这个例子表明, 变量 x 还是会从定义的时候的作用域里面找
function foo() {
const x = 1
return function() {
console.log(x)
}
}
var x = 100
foo()() // 1
综上, 闭包的几个特性:
- 使得函数有状态, 且状态可以改变
- 使得函数有记忆
- 状态是私有的, 可以对外暴露方法读取/修改它
- 每个闭包里面的环境都是独立, 多次调用同一个函数创造出来的多个闭包的环境都是相互隔离的
- 函数调用时, 变量等需要从函数定义时候的词法环境找
例如实现一个计数器, 使用纯函数:
const Counter = function() {
let count = 0
count++
return count
}
Counter() // 1
Counter() // 1
这里的函数是没有状态的, 调用多少次永远都是 1. 同时内部变量 count 也被垃圾回收了
使用类:
class Counter {
constructor() {
this.count = 0
}
add() {
this.count += 1
}
getCount() {
return this.count
}
}
c1 = new Counter()
c1.add()
c1.add()
c1.getCount() // 2
c2 = new Counter()
c2.add()
c2.getCount() // 1
使用类模拟后, 每一次实例化都新生成了一个新的对象, 每个对象里面的 count 属性都是独立的
使用闭包:
const Counter = function() {
let count = 0
const add = function() {
count += 1
}
const getCount = function() {
return count
}
return [getCount, add]
}
const c1 = Counter()
c1[1]()
c1[1]()
c1[0]() // 2
const c2 = Counter()
c2[1]()
c2[0]() // 1
使用闭包模拟, 每次运行 Counter() 产生的闭包环境都是独立的, 不受其他闭包影响. 注意这里的闭包指的是 getCount 和 add 两个闭包, Counter 只是提供这两个闭包的一部分环境(count), count 作为状态, 被这两个函数读取操作
function foo() {
let x = 100
function render() {
const _x = x
function inner() {
x += 5
console.log('innerrender', { _x, x })
}
console.log('render', { _x, x })
return inner
}
function clear() {
x = 0
}
return [add, render, clear]
}
var [add, render, clear] = foo()
inner = render()
clear()
inner()
function foo() {
let x = 100
function add(_x) {
x += _x
console.log('chnange closure x', x)
}
return [x, add]
}
var [x1, add1] = foo()
console.log('x1 before', x1)
add1(5)
console.log('x1 after', x1)
var [x2, add2] = foo()
console.log('x2 before', x2)
add2(10)
console.log('x2 after', x2)