什么是闭包?
- 官方说法:闭包就是指有权访问另一个函数作用域中的变量的函数。
- 自我理解:函数执行时,形成私有上下文,并且把内部函数return出去形成私有变量,称之为“闭包” ,它是一种机制
过程
- 形成私有上下文
- 进栈执行
- 一些列操作
- 赋值形参
- 变量提升
- 代码执行
- 正常情况下,代码执行完成之后,私有上下文出栈被回收。但是遇到特殊情况,如果当前私有上下文执行完成之后中的某个东西被执行上下文以外的东西占用,则当前私有上下文就不会出栈释放,也就是形成了不被销毁的上下文,闭包。
三种情况
- 当前上下文的某些东西被上下文以外的某些东西占用,那么当前上下文就不会被释放。
- 如果没有被占用就是执行完成之后就被释放。
- 除了这上面两种情况还有一种情况是,上下文没有被占用,但是要紧接着被用一次,这样没有用完之前是不能释放的,用完在释放,这样就形成了一个临时不被释放 )
- 函数每一次执行 都是从新形成一个全新的私有上下文,和之前执行产生的上下文没有必然的联系
闭包的作用
函数执行会形成全新的私有上下文,这个上下文可能被释放,也可能不被释放,不论是否被释放,它的作用是:
- 保护(防止变量污染):划分一个独立的代码执行区域,在这个区域中有自己私有变量存储的空间,而用到的私有变量和其它区域中的变量不会有任何的冲突(防止全局变量污染)
- 保存:如果上下文不被销毁,那么存储的私有变量的值也不会被销毁,可以被其下级上下文中调取使用
一般认为只有形成的私有上下文不被释放,才算是闭包(因为如果一但释放,之前的东西也就不存在了);还有人认为,只有一下级上下文用到了此上下文中的动西才算闭包;
var x = 5;
var fn = function (x) {
return function(y) {
console.log(y + (++x));
}
}
var f = fn(6);
f(7); // 14
fn(8)(9); // 18
f(10); // 18
console.log(x); // 5
1.变量提升
2.代码执行
-
x -> 5
-
fn (6) 函数执行
- 创建私有上下文,创建活动对象 AO(fn1)
- 初始化作用域链,<<EC(fn1), EC(G)>>
- 初始化 this
- 初始化 arguments
- 形参赋值
- 变量提升
- 函数执行,返回一个小函数,为小函数创建新的堆内存存储函数内容(作用域,代码字符串,键值对)
-
f = 返回的小函数,也就是新的函数执行,f -> 0x 002,由于0x 002 被 f 占用,所用0x 002 不能被释放,所以它的上下文(作用域)也不能被释放。
-
f(7) 函数执行
- 创建私有上下文,创建活动对象 AO(f1)
- 初始化作用域链,<<EC(f1), EC(fn1)>>
- 初始化 this
- 初始化 arguments
- 形参赋值
- 变量提升
- 函数执行,y = 7,x 当前作用域没有,沿着作用域链往上找,所以在 EC(fn1) 中查找 x = 6, y +(++ x) = 14
-
fn(8) 函数执行
- 创建私有上下文,创建活动对象 AO(fn2)
- 初始化作用域链,<<EC(fn2), EC(G)>>
- 初始化 this
- 初始化 arguments
- 形参赋值
- 变量提升
- 函数执行,返回一个小函数,为小函数创建新的堆内存存储函数内容(作用域,代码字符串,键值对)
-
fn(8)(9) 函数执行
- 创建私有上下文,创建活动对象 AO(f2)
- 初始化作用域链,<<EC(f2), EC(fn2)>>
- 初始化 this
- 初始化 arguments
- 形参赋值
- 变量提升
- 函数执行,y = 9,x 当前作用域没有,沿着作用域链往上找,所以在 EC(fn2) 中查找 x = 8, y +(++ x) = 18
-
f(10) 函数执行
- 创建私有上下文,创建活动对象 AO(f3)
- 初始化作用域链,<<EC(f3), EC(fn1)>>
- 初始化 this
- 初始化 arguments
- 形参赋值
- 变量提升
- 函数执行,y = 10,x 当前作用域没有,沿着作用域链往上找,所以在 EC(fn1) 中查找 x = 7, y +(++ x) = 18
面试中如何回答闭包问题
- 先说闭包是什么?
- 在说函数的创建和执行看闭包(引述:堆栈、EC、AO、VO、scope)
- 然后说闭包的作用以及在项目中的引用场景,以及带来的问题