在以前咱们根绝对象的原型说过了js的原型链,那么一样的js 万物皆对象,函数也一样存在这么一个链式的关系,就是函数的做用域链面试
首先先来回顾一下以前讲到的原型链的寻找机制,就是实例会先从自己开始找,没有的话会一级一级的网上翻,直到顶端没有就会报一个undefined闭包
一样的js的机制就是这样的,函数在执行的时候会先函数自己的上下文的变量对象中查找,没有的话,也会从这个函数被建立的时候的父级的执行上下文的变量对象中去找(词法环境),一直找到全局上下文的变量对象(好比客户端的window对象),这个多层的执行上下文的链式关系就是函数的做用域链函数
盗一张图this
你们能够看到,我在控制台声明了一个函数,而且打印了他,这个a函数的里边有一个[[scope]]属性,
这是一个内部属性,当一个函数被建立的时候,会保存全部的父级的变量对象(词法环境)到这个里边,好比说上图中 就有一个global 属性展开后,往下找你会发现不少咱们常见的属性和方法,好比alert等等spa
如图code
姑且叫他生命周期,我是这么理解的,当进入一个函数的上下文,经历了建立阶段以后,就会把函数的做用域链建立出来,直到销毁这个上下文,这个做用域链也是存在的对象
先来一个正经的例子blog
function a(){ var aaa = 'aaa'; return aaa; } checkscope();
这个函数的生命周期是这样的生命周期
a.[[scope]] = [ globalContext.VO//这个也就是咱们上边图片里边看的golbal ];
globalContext 全局上下文 VO 这个以前没有介绍 是Variable object的简称,也就是以前常常提到的变量对象
还有一个AO ,这个AO指的是函数被激活的时候(被执行)得活动对象
如今执行栈里边已经有了两个执行上下文一个globalContext还有一个aContext图片
首先第一步复制以前得[[scope]]属性,建立做用域链
aContext = { Scope: a.[[scope]], }
而后开始初始化活动变量 argments对象 形参,函数声明,变量声明等等
最后把把活动变量也塞到做用域链中去
以上,一个函数得准备工做就算是作完了,而后下一步就是函数得执行阶段
在弹出得最后时候,a函数得结构大概长成这个样子
aContext = { AO: { arguments: { length: 0 }, }, Scope: [AO, [[Scope]]] }
接下来咱们在举一个不正经得例子,就是为了证实一下做用域链即便在函数被销毁后,也会存在这么一个事实
首先什么是闭包,闭包是指在一个函数内部可以访问不是函数得参数,也不是局部变量得函数,因此广义得讲咱们用的全部得函数都是可算做是闭包,都能访问全局变量。。。
不过工做中不是这样子得,说正题,给上边得问题举个例子
var item = '1' function a(){ var item = '2' function b(){ return item } return b; } var foo = a(); foo();
试着猜测一下这段代码得执行过程
仍是来一步一步得解释一下
在执行a得时候建立了b函数,这个时候,还记得上边以前说过得把,做用域链是在被建立得时候肯定得
这个时候得b函数得做用域链应该是这个样子的
bContext = { Scope: [AO, aContext.AO, globalContext.VO], }
这个是重点,咱们先把执行过程说完
上边已经把顺序说的很清楚了对吧, 执行过程是a进栈出栈,b进栈出栈,可是你打印这段代码的时候
会打印出一个2,就是由于虽说a的上下文被销毁了,可是b的做用域链里边仍是有a的活动对象的
,在b的上下文里边能够找到这个item
这也就是咱们以前所说的闭包,也符合闭包的定义
最后放一个网上很常见的面试题
var data = []; for (var i = 0; i < 3; i++) { data[i] = function () { console.log(i); }; } data[0](); data[1](); data[2]();
从做用域链的角度思考一下会打印出什么结果,为何会打印出这个结果
以上是我对js的做用域链和闭包的一些认识,有不足之处,但愿批评指正