Open hzzzzzzzq opened 2 years ago
闭包,在外面介绍了前面的之后,就可以了解我们的闭包了。
概念
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域
我们先来简单回顾一下执行上下文的第二个例子吧。
var name = 'hzq'; function checkName() { var name = 'hzzzzzzzq'; return function fn() { return name; }; } checkName()(); // hzzzzzzzq
我们这上面这段代码来看一下执行上下文栈和执行上下文的情况。
globalContext
checkName
checkNameContext
this
fn
我们看这个过程时,发现 checkName 函数已经不在执行上下文栈中,为什么还可以取到 name 的值呢?
name
从 《JavaScript 深入之作用域链》 中,我们了解到,fn 函数,其实维护了一个作用域链,可以看看。
fnContext = { Scope: [AO, fnContext.AO, globalContext.VO], };
所有,其实 fn1 取到的就是上层的函数。就是因为作用域链,实现了父级函数即使被销毁,也可以取到值。
fn1
我们先举一个简单的闭包使用方式。
function fn() { var count = 1; return function f() { return ++count; }; } var fn1 = fn(); console.log(fn1()); // 2 console.log(fn1()); // 3 console.log(fn1()); // 4 console.log(fn1()); // 5 console.log(fn1()); // 6
我们的执行过程类似,但是在作用域链中保存着值,可以进行修改,也导致了一个问题,也就是内存删除不干净,内存泄露。
我们来看一道经典的闭包面试题。
const data = []; for (var i = 0; i < 5; i++) { data[i] = function () { console.log(i); }; } data[2](); // 5 data[3](); // 5 data[4](); // 5
这题是我们经常遇到的题目,为什么打印的全是 5 呢?
5
我们来看一下全局上下文中,执行函数之前的 globalContext,如果对此有问题,可以去看一下前面的几篇文章。《JavaScript 深入之执行上下文栈》、《JavaScript 深入之变量对象》、《JavaScript 深入之作用域链》、《JavaScript 深入之 this》。
我们来看一下上面代码的全局上下文(globalContext) 的内容是什么。
globalContext = { VO: { data: [...], // item => function i: 5 }, Scope: [globalContext.VO], }
这样就一定都打印 5 了吗? 不是的,我们还需要看看 data 中函数的上下文。
data
data[0]Context = { AO: { arguments: { length: 0, }, }, Scope: [AO, globalContext.VO], }
所以,我们在 data[0] 函数中没有找到结果 i,于是从 global 中寻找,于是找到结果为 5。
data[0]
i
global
const data = []; for (var i = 0; i < 5; i++) { data[i] = (function () { return function () { console.log(i); }; })(); } data[2](); // 2 data[3](); // 3 data[4](); // 4
这变成了一个立即执行函数。
我们在全局上下文的 VO 是没有变化的。
但是我们来看看 data[0] 函数执行时,其中有一个是匿名函数,我们假设为 NoName 函数,我们来看一下 data[0] 的上下文
NoName
data[0]Context = { AO: { arguments: { length: 0, }, }, Scope: [AO, NoNameContext.AO, globalContext.VO] }
在来看看匿名函数的上下文
NoNameContext = { AO: { arguments: { length: 0, }, i: 0, }, Scope: [AO, globalContext.VO], };
所以,这时候执行时,找到了 i = 0,返回结果。
i = 0
闭包
介绍
闭包,在外面介绍了前面的之后,就可以了解我们的闭包了。
概念
我们先来简单回顾一下执行上下文的第二个例子吧。
我们这上面这段代码来看一下执行上下文栈和执行上下文的情况。
globalContext
入栈checkName
函数,创建checkName
函数执行上下文,checkNameContext
入栈checkName
执行上下文初始化,创建变量对象、作用域链、this
等checkName
函数执行完毕,出栈fn
函数,创建fn
函数执行上下文,fn
函数上下文入栈fn
执行上下文初始化,创建变量对象、作用域链、this
等fn
函数执行完毕,fn
函数上下文弹出我们看这个过程时,发现
checkName
函数已经不在执行上下文栈中,为什么还可以取到name
的值呢?从 《JavaScript 深入之作用域链》 中,我们了解到,
fn
函数,其实维护了一个作用域链,可以看看。所有,其实
fn1
取到的就是上层的函数。就是因为作用域链,实现了父级函数即使被销毁,也可以取到值。我们先举一个简单的闭包使用方式。
我们的执行过程类似,但是在作用域链中保存着值,可以进行修改,也导致了一个问题,也就是内存删除不干净,内存泄露。
必刷面试题
我们来看一道经典的闭包面试题。
这题是我们经常遇到的题目,为什么打印的全是
5
呢?我们来看一下全局上下文中,执行函数之前的
globalContext
,如果对此有问题,可以去看一下前面的几篇文章。《JavaScript 深入之执行上下文栈》、《JavaScript 深入之变量对象》、《JavaScript 深入之作用域链》、《JavaScript 深入之 this》。我们来看一下上面代码的全局上下文(
globalContext
) 的内容是什么。这样就一定都打印
5
了吗? 不是的,我们还需要看看data
中函数的上下文。所以,我们在
data[0]
函数中没有找到结果i
,于是从global
中寻找,于是找到结果为5
。这变成了一个立即执行函数。
我们在全局上下文的 VO 是没有变化的。
但是我们来看看
data[0]
函数执行时,其中有一个是匿名函数,我们假设为NoName
函数,我们来看一下data[0]
的上下文在来看看匿名函数的上下文
所以,这时候执行时,找到了
i = 0
,返回结果。