执行上下文EC(Execution Context)

  1. 执行上下文就是一个函数或者全局代码运行时的环境,里面包括执行时需要用到的数据,可以理解为是一个对象;
  2. 函数每次执行都会创建一个新的执行上下文;
  3. 函数每次执行完毕都会销毁这个执行上下文;

执行栈CS(call stack)

  1. 每次有函数执行就会生成一个新的执行上下文,而组织管理执行上下文的栈就是执行栈;
  2. 当一个函数运行时,会在运行前生成一个执行上下文并入栈,函数运行结束就会出栈;
  3. 每次正在运行的函数使用的都是栈顶的上下文;
  4. 栈底永远是全局上下文。

举个例子:

function foo() {
    console.log('foo函数执行了');

    function bar() {
        console.log('bar函数执行了');
    }

    bar();
}

foo();

代码执行时,执行栈如何变化

  1. 创建全局上下文G_EC,并入栈。

  2. 执行foo函数,创建foo的执行上下文foo_EC, 并入栈。

  3. 执行console.log函数,创建console.log函数的执行上下文clg_EC, 并入栈。

  4. console.log执行完毕,打印出 'foo函数执行了',销毁clg_EC,出栈。

  5. 执行bar函数,创建bar的执行上下文bar_EC, 并入栈。

  6. 执行console.log函数,创建console.log函数的执行上下文clg_EC, 并入栈。

    注意:这里的clg_EC是一个全新的上下文,和上一个不一样,函数每次调用都生成一个新的独一无二的执行上下文。

  7. console.log执行完毕,打印出 'bar函数执行了',销毁clg_EC,出栈。

  8. bar函数执行完毕,销毁bar_EC, 出栈。

  9. foo函数执行完毕,销毁foo_EC, 出栈。

  10. 全部代码执行完毕,销毁G_EC, 出栈。

执行上下文中的内容

//用代码表示一个执行上下文
EC = {
    VO = {...},
    SC = [...],
    this = {...}
}

1. 变量对象VO(Varibale Object)

  1. 函数运行前一刻生产;
  2. 执行栈顶部的VO,又被称之为执行对象AO(Active Object)
  3. 执行栈底部的VO,又被称之为全局对象GO(Global Object)

2. 作用域链SC(Scope Chain)

  1. 作用域链(Scope Chain)是一个数组,是执行上下文的集合,每个函数都有一个[[scope]]属性,指向一个数组,数组中保存的是除自己的执行上下文以外的其他执行上下文;
  2. 作用域链是在函数定义时就产生的;
  3. 当一个函数要使用一个变量时,会先从自己的执行上下文中查找,如果找不到,就会沿着作用域链往上找。

3. this

this的指向问题:

  1. 全局环境中,this指向window对象

    console.log(this); //window
  2. 函数中的this,指向window(严格模式中指向undefined

    function foo(){
        console.log(this); //window
    }
    
    foo();
  3. 使用callapply调用,this指向call/apply的第一个参数

    var obj = {a: 1};
    
    function foo(){
        console.log(this); //obj
    }
    
    foo.call(obj);
    foo.apply(obj);
  4. 调用对象中的函数,使用obj.fun方式调用,this指向obj

    var obj = {
        a: 1,
        foo: function(){
            console.log(this);
        }
    };
    
    obj.foo(); //this指向obj
    
    var bar = obj.foo;
    bar(); //相当于将函数放在全局中执行,this指向window

VO的创建过程

在一个函数的VO创建时,js引擎做的事情

  1. 确定形参的值
  2. 变量的声明提升
  3. 将实参的值赋给形参
  4. 函数声明整体提升

举个例子:

function foo(a, b){
    console.log(bar); //ƒ bar(){}
    console.log(a); //2

    function bar(){}

    var a = 1;
}

foo(2, 3);

按照创建步骤生成foo函数的VO——foo_VO

//1. 确定形参的值,一开始有两个形参的值
foo_VO = {
    arguments: {...}, //arguments一开始就会在
    a: undefined,
    b: undefined,
}

//2. 变量声明提升, 内部有一个变量声明a,当前VO对象已经有a属性,所以不变
foo_VO = {
    arguments: {...},
    a: undefined,
    b: undefined,
}

//3. 将实参的值赋给形参,执行foo(2, 3)时传入了实参2, 3,分别赋值给a, b
foo_VO = {
    arguments: {...},
    a: 2,
    b: 3,
}

//4. 函数声明提升, 有一个函数foo,将foo函数作为VO的属性
foo_VO = {
    arguments: {...},
    a: 2,
    b: 3,
    foo: function(){}
}

所以最后foo函数产生的VO对象就是

foo_VO = {
    arguments: {...},
    a: 2,
    b: 3,
    foo: function(){}
}

SC的内容

SC = 上一层执行上下文栈的AO + 上一层执行上下文栈的SC

举个例子:

function foo() {
    function bar() {
        function baz(){

        }
    }
}

全局执行上下文,一开始的SC为空

g_EC = {
    SC: [],
    AO: {...},
    this: {...},
}

foo函数执行上下文,其中SC = 全局上下文的AO + 全局上下文的SC

foo_EC = {
    SC: [g_EC.AO], //[g_EC.AO, ...g_EC.SC]
    AO: {...},
    this: {...},
}

bar函数执行上下文,其中SC = foo函数执行上下文的AO + foo函数执行上下文的SC

bar_EC = {
    SC: [foo_SC.AO, g_EC.AO], //[foo_SC.AO, ...foo_SC.SC],
    AO: {...},
    this: {...},
}

baz函数执行上下文,其中SC = bar函数执行上下文的AO + bar函数执行上下文的SC

baz_EC = {
    SC: [bar_SC.AO, foo_SC.AO, g_EC.AO], //[bar_SC.AO, ...bar_SC.SC],
    AO: {...},
    this: {...},
}

作用域图解:

闭包

当一个函数中的函数被保存到该函数的外部就会形成闭包。

function foo(){
    var a = 1;
    return function bar(){
        return a;
    }
}

var baz = foo();
var qux = baz();

console.log(qux); //1

foo在运行的时候

foo_EC = {
    SC: [GO],
    AO: {
        a: 1,
        ...
    },
    this: {...},
}

foo运行完毕后会销毁自己的执行上下文,其中的AO也被销毁

但是由于bar被保存到了外部, 也就是baz中,而bar的作用域SC中有foo的AO,所以这就解释了为什么形成闭包时,外部的函数可以使用函数内部的变量。

baz_EC = {
    SC: [foo_EC.AO, GO],
    AO: {...},
    this: {...},
}

闭包的作用:

  1. 实现公有变量;
  2. 可以做缓存;
  3. 实现属性私有化;
  4. 模块化开发,防止污染全局变量。
03-05 17:08