菜单

JavaScript 深切之实践上下文

2019年3月27日 - 皇家前端

函数激活

当函数激活时,进入函数上下文,创制VO/AO后,就会将移步目的添加到功能链的前端。

那儿执行上下文的成效域链,我们命名为Scope:

Scope = [AO].concat([[Scope]]);

1
Scope = [AO].concat([[Scope]]);

时至明天,成效域链成立实现。

JavaScript 浓厚之闭包

2017/05/21 · JavaScript
· 闭包

原来的作品出处: 冴羽   

现实实施分析

我们分析第③段代码:

var scope = “global scope”; function checkscope(){ var scope = “local
scope”; function f(){ return scope; } return f(); } checkscope();

1
2
3
4
5
6
7
8
9
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope();

实践进度如下:

1.推行全局代码,创制全局执行上下文,全局上下文被压入执行上下文栈

ECStack = [ globalContext ];

1
2
3
    ECStack = [
        globalContext
    ];

2.全局上下文初叶化

globalContext = { VO: [global, scope, checkscope], Scope:
[globalContext.VO], this: globalContext.VO }

1
2
3
4
5
    globalContext = {
        VO: [global, scope, checkscope],
        Scope: [globalContext.VO],
        this: globalContext.VO
    }

2.起初化的同时,checkscope
函数被创立,保存效用域链到函数的内部属性[[scope]]

checkscope.[[scope]] = [ globalContext.VO ];

1
2
3
    checkscope.[[scope]] = [
      globalContext.VO
    ];

3.实施 checkscope 函数,成立 checkscope 函数执行上下文,checkscope
函数执行上下文被压入执行上下文栈

ECStack = [ checkscopeContext, globalContext ];

1
2
3
4
    ECStack = [
        checkscopeContext,
        globalContext
    ];

4.checkscope 函数执行上下文开头化:

  1. 复制函数 [[scope]] 属性创制效用域链,
  2. 用 arguments 创建活动对象,
  3. 伊始化活动对象,即进入形参、函数注脚、变量评释,
  4. 将移步目的压入 checkscope 成效域链顶端。

而且 f 函数被创立,保存功用域链到 f 函数的里边属性[[scope]]

checkscopeContext = { AO: { arguments: { length: 0 }, scope: undefined,
f: reference to function f(){} }, Scope: [AO, globalContext.VO], this:
undefined }

1
2
3
4
5
6
7
8
9
10
11
    checkscopeContext = {
        AO: {
            arguments: {
                length: 0
            },
            scope: undefined,
            f: reference to function f(){}
        },
        Scope: [AO, globalContext.VO],
        this: undefined
    }

5.实践 f 函数,创造 f 函数执行上下文,f 函数执行上下文被压入执行上下文栈

ECStack = [ fContext, checkscopeContext, globalContext ];

1
2
3
4
5
    ECStack = [
        fContext,
        checkscopeContext,
        globalContext
    ];

6.f 函数履行上下文初阶化, 以下跟第 4 步相同:

  1. 复制函数 [[scope]] 属性创制成效域链
  2. 用 arguments 创立活动对象
  3. 初阶化活动对象,即参预形参、函数注脚、变量证明
  4. 将活动指标压入 f 成效域链顶端

fContext = { AO: { arguments: { length: 0 } }, Scope: [AO,
checkscopeContext.AO, globalContext.VO], this: undefined }

1
2
3
4
5
6
7
8
9
    fContext = {
        AO: {
            arguments: {
                length: 0
            }
        },
        Scope: [AO, checkscopeContext.AO, globalContext.VO],
        this: undefined
    }

7.f 函数执行,沿着成效域链查找 scope 值,重返 scope 值

8.f 函数进行完毕,f 函数上下文从进行上下文栈中弹出

ECStack = [ checkscopeContext, globalContext ];

1
2
3
4
    ECStack = [
        checkscopeContext,
        globalContext
    ];

9.checkscope 函数执行达成,checkscope 执行上下文从实施上下文栈中弹出

ECStack = [ globalContext ];

1
2
3
    ECStack = [
        globalContext
    ];

第②段代码就留下大家去尝尝模拟它的进行进度。

var scope = “global scope”; function checkscope(){ var scope = “local
scope”; function f(){ return scope; } return f; } checkscope()();

1
2
3
4
5
6
7
8
9
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
checkscope()();

但是,在下一篇《JavaScript深远之闭包》中也会提及那段代码的施行进程。

职能域链

在《引出成效域链》中说到效率域链本质是一个针对性别变化量对象的指针链表。

当查找变量的时候,会先从脚下上下文的变量对象中摸索,若是没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中搜寻,平昔找到全局上下文的变量对象,也正是全局对象。那样由八个实施上下文的变量对象构成的链表就称为成效域链。

下边以一个例子看看效果域链是怎么创设的:

 1 var a=10;
 2 function run(){
 3   var name='Joel';
 4 function say(){
 5   var content='hello',name=' Word';
 6 
 7   console.log(content+name+','+a);
 8 }
 9 say();
10 }
11 run();//hello Word,10

 编写翻译之后函数内部属性

//编译时各自的[[scope]]
   run.[[scope]] = [
    globalContext.VO //全局变量对象
 ];

   say.[[scope]] = [
   run.VO,
   globalContext.VO
 ];

举行函数
函数执行分为两有的

创立上下文对象,创立vo 变量对象、scope chain 成效域链、this
指针以及把函数对象内部属性[[scope]]的值复制给上下文对象scope chain
属性

run.ec={
     VO:{
     //变量对象初始化
 },
   //scope chain :run.[[scope]],
   scope chain :globalContext.VO,
  this:thisValue
}

实践等级此时上下文被推入环境栈,VO激活为AO,此时VO
已经先导化实现,此时把当下环境的AO 被插入到scope chain 顶端
即 Scope = AO+[[Scope]]  

AO会添加在作用域链的最前边 

Scope = [AO].concat([[Scope]])

函数初阶实践阶段

//执行
run.ec={
   AO:{
   //变量对象初始化
},
    // scope chain:AO+run.[[scope]],
   scope chain:AO+globalContext.VO,
   this:thisValue
}

功用域链 = (动)活动指标(AO) + (静) scope属性 

动指的是进行的时候的变量对象,静指的是词法成效域,即父级变量对象;

函数创造

在《JavaScript深刻之词法成效域和动态作用域》中讲到,函数的成效域在函数定义的时候就决定了。

这是因为函数有三个内部属性[[scope]],当函数成立的时候,就会保留全部父变量对象到里头,你能够清楚[[scope]]不怕拥有父变量对象的层级链。(注意:[[scope]]并不意味着完整的机能域链!)

举个例证:

function foo() { function bar() { … } }

1
2
3
4
5
function foo() {
    function bar() {
        …
    }
}

函数创设时,各自的[[scope]]为:

foo.[[scope]] = [ globalContext.VO ]; bar.[[scope]] = [
fooContext.AO, globalContext.VO ];

1
2
3
4
5
6
7
8
foo.[[scope]] = [
  globalContext.VO
];
 
bar.[[scope]] = [
    fooContext.AO,
    globalContext.VO
];

必刷题

接下去,看那道刷题必刷,面试必考的闭包题:

var data = []; for (var i = 0; i 3; i++) { data[i] = function () {
console.log(i); }; } data[0](); data[1](); data[2]();

1
2
3
4
5
6
7
8
9
10
11
var data = [];
 
for (var i = 0; i  3; i++) {
  data[i] = function () {
    console.log(i);
  };
}
 
data[0]();
data[1]();
data[2]();

答案是都是 3,让我们解析一下缘故:

当执行到 data[0] 函数从前,此时全局上下文的 VO 为:

globalContext = { VO: { data: […], i: 3 } }

1
2
3
4
5
6
globalContext = {
    VO: {
        data: […],
        i: 3
    }
}

当执行 data[0] 函数的时候,data[0] 函数的机能域链为:

data[0]Context = { Scope: [AO, globalContext.VO] }

1
2
3
data[0]Context = {
    Scope: [AO, globalContext.VO]
}

data[0]Context 的 AO 并从未 i 值,所以会从 globalContext.VO 中找找,i
为 3,所以打字与印刷的结果就是 3。

data[1] 和 data[2] 是一致的道理。

就此让大家改成闭包看看:

var data = []; for (var i = 0; i 3; i++) { data[i] = (function (i) {
return function(){ console.log(i); } })(i); } data[0](); data[1]();
data[2]();

1
2
3
4
5
6
7
8
9
10
11
12
13
var data = [];
 
for (var i = 0; i  3; i++) {
  data[i] = (function (i) {
        return function(){
            console.log(i);
        }
  })(i);
}
 
data[0]();
data[1]();
data[2]();

当执行到 data[0] 函数在此以前,此时全局上下文的 VO 为:

globalContext = { VO: { data: […], i: 3 } }

1
2
3
4
5
6
globalContext = {
    VO: {
        data: […],
        i: 3
    }
}

跟没改在此以前同一。

当执行 data[0] 函数的时候,data[0] 函数的成效域链发生了改观:

data[0]Context = { Scope: [AO, 匿名函数Context.AO globalContext.VO]
}

1
2
3
data[0]Context = {
    Scope: [AO, 匿名函数Context.AO globalContext.VO]
}

匿名函数执行上下文的AO为:

匿名函数Context = { AO: { arguments: { 0: 1, length: 1 }, i: 0 } }

1
2
3
4
5
6
7
8
9
匿名函数Context = {
    AO: {
        arguments: {
            0: 1,
            length: 1
        },
        i: 0
    }
}

data[0]Context 的 AO 并从未 i 值,所以会顺着作用域链从匿名函数
Context.AO 中追寻,那时候就会找 i 为 0,找到了就不会往 globalContext.VO
中查找了,即便 globalContext.VO 也有 i
的值(值为3),所以打字与印刷的结果正是0。

data[1] 和 data[2] 是均等的道理。

思考题

在《JavaScript深远之词法功能域和动态效率域》中,建议那样一道思课题:

var scope = “global scope”; function checkscope(){ var scope = “local
scope”; function f(){ return scope; } return f(); } checkscope();

1
2
3
4
5
6
7
8
9
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope();

var scope = “global scope”; function checkscope(){ var scope = “local
scope”; function f(){ return scope; } return f; } checkscope()();

1
2
3
4
5
6
7
8
9
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
checkscope()();

两段代码都会打字与印刷’local
scope’。固然两段代码执行的结果一致,但是两段代码毕竟有怎么着不相同呢?

跟着就在下一篇《JavaScript深远之实施上下文栈》中,讲到了两者的分化在于实践上下文栈的生成不平等,不过,如若是那般笼统的回复,依旧显得不够详细,本篇就会详细的分析执行上下文栈和实践上下文的求实变化历程。

 总结

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图