执行上下文和作用域链

156 阅读4分钟

执行上下文

函数执行上下文

一个函数运行之前,会创建一个内存空间,该空间中有执行该函数所需要的数据,从而为函数执行提供支持。

执行上下文栈(call stack):所有执行上下文组成的内存空间。

全局执行上下文

所有JS代码执行之前,都必须有该环境。(只有一个)

JS引擎始终执行的是栈顶的上下文。

执行上下文中的内容

  1. 确定this的指向

    1. 直接调用函数,this指向全局对象
    2. 在函数外,this指向全局对象
    3. 通过对象调用或new一个函数,this指向调用的对象或新对象
  2. 确定VO变量对象

Variable Object: 记录了该环境中所有声明的参数声明的变量声明的函数

对象存在堆中

变量对象如何记录

  1. 确定所有形参值以及arguments。

  2. 确定通过var声明的变量,值为undefined。若第一步有同名变量,则忽略。

  3. 确定函数中通过字面量声明的函数,值为函数值,若上面两步有同名变量,则覆盖。

面试题1

var foo = 1;
function bar(){
    console.log(foo);
    if(!foo){
        var foo = 10;
    }
    console.log(foo);
}
bar();

/* 
1. 全局上下文预编译:
    foo: undefined
    bar: fn
*/

// 开始执行代码

/* 
1. 全局上下文:
    foo: 1
    bar: fn
*/

// bar函数调用,就会创建函数上下文

/* 
2. bar函数上下文:
   foo: undefined 
1. 全局上下文:
    foo: 1
    bar: fn
*/

// 开始执行代码

/* 
看bar函数的上下文
输出undefined
foo:10
输出10
*/

面试题2

var a = 1;
function b(){
    console.log(a);
    a = 10;
    return;
    function a(){}
}
b();
console.log(a);

/**
函数b上下文:
    a: fn
全局上下文:
    a:1
    b:fn
*/

输出结果:function a, 1

面试题3

console.log(foo); // foo["C"]
var foo = "A";
console.log(foo); // "A"
var foo = function () {
    console.log('B');
}
console.log(foo); // foo["B"]
foo(); // "B"

function foo() {
    console.log('C');
}
console.log(foo); // foo["B"]
foo(); // "B"

/**
 * 全局:
 *      foo: fn["B"]
 */

面试题4

var foo = 1;
function bar(a){
    var a1 = a;
    var a = foo;
    function a(){
        console.log(a);
    }
    a1();
}
bar(3);
/**
    函数a1:
        输出1
    函数bar:
        a: 1
        a1:fn[a]
        
    全局:
        foo: 1
        bar: fn
*/

作用域链

用于解释闭包。

  1. 变量对象中包含一个额外的属性,该属性指向创建该变量对象的函数本身。

  2. 每个函数在创建时,会有一个隐藏属性[[scope]],它指向创建该函数的AO。

  3. 当访问一个变量时,会先查找自身VO中是否存在,如果不存在,则依次查找[[scope]]属性。

var g = 0;
function A(){
    var a = 1;
    function B(){
        var b = 2;
        var C = function(){
            var c = 3;
            console.log(c,b,a,g);
        }
        C();
    }
    B();
}
A();

图解:

截屏2022-03-21 下午6.04.10.png

当访问一个变量时,会先查找自身VO中是否存在,如果不存在,则依次查找[[scope]]属性。

上面代码console.log(c,b,a,g)的输出就是根据作用域链来查找的。

代码分析

截屏2022-03-21 下午8.48.54.png

截屏2022-03-21 下午8.49.37.png

截屏2022-03-21 下午8.49.55.png

截屏2022-03-21 下午8.50.11.png

截屏2022-03-21 下午8.50.26.png

截屏2022-03-21 下午8.50.43.png

截屏2022-03-21 下午8.50.59.png

  • 最后输出结果: 1,2,报错(count is not defined)

举一反三

var count = 100;
function A(){
    var count = 0;
    // 返回一个函数,相当于新创建了一个函数,该函数的[[scope]]指向创建该函数的AO上下文
    return function(){
        count++;
        // 看函数声明时的变量
        console.log(count);
    }
}

var test = A();

test();
test();
console.log(count);
  • 输出结果:1,2,100

例1

var a = 1;
// 看函数A声明时
function A(){
    console.log(a);
}
function Special(){
    var a = 5;
    var B = A;
    B();
}
Special();

截屏2022-03-21 下午9.18.20.png

  • 创建函数就形成了闭包

某些浏览器会优化[[scope]]作用域链,将不需要的属性去掉。

面试题1

function func() {
    let i = 0;
    return () => {
        i++;
        return i;
    }
}
const func1 = func();
const func2 = func();


console.log(func1())// 1
console.log(func2())// 1
console.log(func1()) // 2
console.log(func2()) // 2

截屏2022-03-21 下午9.43.22.png

面试题2

var foo = {
    n: 1
};
(function (foo) {
    console.log(foo.n); // 全局的foo n=1
    foo.n = 3; //全局的foo n=3
    var foo = { // 将自己的VO中指向对象{n:2}
        n: 2
    };
    console.log(foo.n); // 自己的VO中的 n=2
})(foo);
console.log(foo.n); // 全局的foo 3

截屏2022-03-22 上午11.14.07.png

面试题3


const obj2 = {
    key: 'value11'
}

function get(obj2){
    obj2 = {
        key: 'value22'
    }
    console.log(obj2); // { key: 'value22' }
}

get(obj2);
console.log(obj2); // { key: 'value11' }

截屏2022-03-22 上午11.34.17.png

面试题4

var food = 'rice';
(function (){
    var food = 'noodle';
    var eat = function(){
        console.log(food);
    }
    eat(); // noodle
})()

面试题5

for (var i = 0; i < 10; i++) {
    setTimeout(function () {
        console.log(i);
    }, 1000)
}

截屏2022-03-22 下午9.52.38.png

截屏2022-03-22 下午9.52.54.png

截屏2022-03-22 下午9.53.06.png

截屏2022-03-22 下午9.53.19.png

截屏2022-03-22 下午9.53.37.png

  • 如何解决上面的闭包问题
for (var i = 0; i < 10; i++) {
    (function (i) {
        setTimeout(function () {
            console.log(i);
        }, 1000)
    })(i);
}