欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JavaScript执行上下文

程序员文章站 2024-03-03 22:31:52
...

JavaScript执行上下文分析

前端开发过程中经常会遇到作用域的问题,变量提升,闭包等等一些列的问题,那么这些问题的是怎么形成的,又是如何实现的,这里通过分析JavaScript中的执行上下文(EC)依次解开。

代码思考

var name = 'globalName';
function F1() {
    console.log(name);
    console.log(F2);
    var name = 'f1ame';
    function F2(argumentName) {
    	console.log(argumentName);
	}
	F2(name);
    console.log(sex);
}
F1();

乍一看可能会认为控制台会输出

 console.log(name); // globalName
 console.log(F2); // not defined
 console.log(argumentName); // f1ame
 console.log(sex); // not defined

其实不然,上面代码执行下来的结果是

 console.log(name); // undefined
 console.log(F2); // F2(name) {console.log(name);}
 console.log(argumentName); // f1ame
 console.log(sex); // not defined

ECS(执行环境栈-Execution Context Stack)

那么输出这些的原因是什么?下面来分析一下,在分析之前,先来看一下JavaScript的压栈规则LIFO(last in first out)。当代码执行时,开始将全局上下文入ECS。当指定到函数调用时,便将函数上下文入ECS。函数上下文可以有多个,当调用时,就将调用的入ECS,运行完成时,再将函数上下文出ECS例如上方的代码,开始执行时,全局上下文入ECS,执行到F1调用时,将函数上下文F1ECS,F1中调用F2时,再将F2ECS,F2执行完成后出ECS等等。具体如下图
JavaScript执行上下文

执行上下文形成

JavaScript中代码的运行环境,也叫执行上下文,也就是传说中的作用域。在执行上下文(以下简称EC)中包含以下内容

  1. 作用域链Scope Chain
  2. 变量对象
  3. this指向

EC有三种,这里主要讲前两种

  1. 全局上下文
    在JavaScript中只拥有一个全局上下文,代码最先进入的环境,在页面关闭时销毁
  2. 函数上下文
    在函数被调用时的执行环境
  3. eval
    此函数会对参数执行,会产生自己的上下文

下面具体讲接下EC如何形成的,EC分为两个阶段,一个创建阶段,一个执行阶段

  • 创建阶段(该阶段主要分为三步)
    1. 创建作用域(Scope Chain),由当前EC和上层EC等一系列变量对象组成的层级链
    2. 创建(变量对象VO)包含参数、函数、变量
    3. this绑定(指向调用的对象)
	第二部分VO(变量对象)创建详情:
 		初始化创建arguments:赋值;
 		声明function:赋值;
 		声明变量:undefined
  • 执行阶段
    1. 顺序执行代码
    2. 变量对象赋值(在这里才赋值到变量)

以上便是一个EC的形成,接下来看一下F1函数上下文在创建阶段时什么样

对比EC过程,进行代码详情解析

F1EC: {
  VO: {
    Arguments: {
      // 这里便是初始化argument,有参数的话,便在这里创建
      length: 0
    },
    // 变量对象
    VO: {
      //声明引用 F2
      F2: <function fn2 reference>,
      //声明name
      name: undefined
    }
  },
  // 确定作用域链
  scopeChain: [F1EC.VO, global],
  //bindthis指向
  this: window
}

当创建阶段完成以后,接下来就是执行阶段,下面具体分析下这段代码

// 创建全局上下文,入上下文执行栈
var name = 'globalName';
function F1() {
  // 第一阶段进行后
  // 这里this指向window
  // name被创建,但是未赋值
  //F2被创建并且引用
  console.log(name); // 那么这里应该是undefined
  console.log(F2); // 这里应该是 F2
  var name = 'f1ame'; // 再此之后,则name可以访问到
  
  function F2(argumentName) {
    console.log(argumentName);
  }

  // 创建F2上下文,入上下文执行栈 同理
  F2(name);

  //F2 出栈
  console.log(sex); //sex未创建,未赋值,且作用域链中也没有,所以报错 sex not defined
}
// 创建F1上下文,入上下文执行栈
F1();
// F1出栈
// 全局上下文出栈

通过上面的分析,这段代码运行结果就正确了,从这段代码中在衍生出几个JavaScript中创建的词汇解析

变量提升

在这里可以看到name与F2都在console.log之后才定义,但是,console.log却能够访问到。通过上面的分析可知,在代码执行之前,就已 经创建了VO对象并且函数已经赋值引用。这里就说明了变量提升的原理

闭包

全局执行上下文中无法访问局部上下文中的变量,但是可以通过闭包来实现。下面F2函数就是闭包,F2作用域链是F2、F1、window

function F1() {
  var name = 'f1name';
  function F2() {
  	// 访问F2、F1、window
  	console.log(name); //f1name
  }
  return F2
}
F1()()
// 访问全局作用域
console.log(name) // not defined

分析两道经典代码片段

var name = "The Window";

var obj = {
  name: "My Object",
  F1: function () {
    console.log(this); //指向调用的obj,这里是obj
    return function () {
      return this.name;
    };
  }
};

var res = obj.F1(); //obj调用

console.log(res()); //window调用,所以this.name === window.name
var name = "The Window";

var obj = {
  name: "My Object",
  F1: function () {
    console.log(this); //指向调用的obj,这里是obj
    var that = this; // 指向obj,这里that指向obj
    return function () {
      // that依赖F1执行上下文,所以这里that.name = My Object
      return that.name;
    };
  }
};

var res = obj.F1();

console.log(res()); //that.name === obj.name
相关标签: JavaScript原理分析