阅读 41

js基础深入浅出之:作用域链和执行上下文生命周期

执行上下文生命周期

生命周期有两个阶段

  1. 创建阶段
  • 创建变量对象
  • 确定作用域链
  • 确定this指向

2.执行阶段

  • 变量赋值
  • 函数赋值
  • 代码执行

变量对象

变量对象会保存变量声明(var)、函数参数(arguments)、函数定义(function)

变量对象会首先获得函数的参数变量和值

  • 获取所有用function进行的函数声明,函数名为变量对象的属性名,值为函数对象,如果属性已经存在,值会用新值覆盖 再依次所有的var关键字进行的变量声明,每找到一个变量声明,就会在变量对象上建一个属性,值为undefined,如果变量名已经存在,则会跳过,并不会修改原属性值,let声明的变量并不会在此阶段进行处理
  • 函数声明优先级更高,同名的函数会覆盖函数和变量,但同名var变量并不会覆盖函数.执行阶段重新赋值可以改变原有的值

栗子1:

console.log(a);
var a = 1;
复制代码

var a = undefined;//变量提升

console.log(a);

a = 1;

栗子2:

var a = 1;
function fn(m) { console.log('fn'); }
function fn(m) { console.log('new_fn'); }
function a() { console.log('fn_a'); }
console.log(a);
fn(1);
var fn = 'var_fn';
console.log(fn);
//1
//new_fn
//var_fn
复制代码
// 创建阶段 
// 先创建 函数声明优先级更高
function fn(m) { console.log('fn'); }
function fn(m) { console.log('new_fn'); }
function a() { console.log('fn_a'); }
var a = undefined;
var fn = undefined;
//执行阶段
a = 1;
console.log(a);
fn();
fn = 'var_fn';
console.log(fn);
复制代码

栗子2上下文模拟过程:

// 创建阶段
var globalEC = {
    VO: {
        ...arguments,
        a: () => { console.log('fn_a'); },
        fn: () => { console.log('new_fn'); }
    }
}
var ECStack = [globalEC];
//执行阶段
globalEC.VO.a = 1;
console.log(globalEC.VO.a);
globalEC.VO.fn();
globalEC.VO.fn = 'var_fn';
console.log(globalEC.VO.fn);
复制代码

作用域

作用域

在JS中,作用域是用来规定变量访问范围的规则

function one() {
    var a = 1;
}
console.log(a);
复制代码

作用域链

作用域链是由当前执行环境与上层执行环境的一系列变量对象组成的,它保证了当前执行环境对符合访问权限的变量和函数的有序访问.

function one() {
    var a = 1;
    function two() {
        console.log(a);
    }
    return two;
}
var a = 2;
var two = one();
two();
复制代码

作用域链在函数创建的时候已经确定了,在哪里执行没有关系哦

上下文过程

// 1.创建全局上下文
var globalExecuteContextVO = { one: `()=>{var a = 1;}`, a: undefined, two: undefined } // 创建变量提升,函数优先
var globalExecuteContext = {
    VO: globalExecuteContextVO,
    scopeChain: [globalExecuteContextVO]
}
//2.开始执行
globalExecuteContextVO.a = 2; // 赋值
//3.开始执行one
var oneExecuteContextVO = { a: undefined, two: `()=>{console.log(a)}` }
var oneExecuteContext = {
    VO: oneExecuteContextVO,
    scopeChain: [oneExecuteContextVO, globalExecuteContextVO]
}
oneExecuteContextVO.a = 1;
//4.给two赋值
globalExecuteContextVO.two = oneExecuteContextVO.two;
//5.执行two
var twoExecuteContextVO = {}
var twoExecuteContext = {
    VO: twoExecuteContextVO,
    //scopeChain是在创建此函数据的时候就决定了,跟在哪里执行无关
    scopeChain: [twoExecuteContextVO, oneExecuteContextVO, globalExecuteContextVO]
}
复制代码

上面创建阶段,大家需要注意的是,创建过程中var变量只会声明,但是不会赋值,函数则是优先声明并且赋值, 还有一个就是scopeChain是作用域链,在创建时候创建此函数据的时候就决定了,跟怎么执行或者在哪里执行没有关系,当函数执行完后就会出栈,从栈顶开始,最先进入的最后出,这个我们上节说过,scopeChain最后一个globalExecuteContextVO,这个是全局变量是不会出栈哦