一文总结JavaScript的执行上下文

1,046 阅读3分钟

前言

Javascript的执行上下文,其实也就是JavaScript执行过程中的环境,环境中就包含了执行中需要访问的变量。总的来说,它包含了下面四个方面。

JavaScript 的执行上下文.jpg

JavaScript执行上下文的组成

1.环境变量

简单来说,变量环境就是用var标识符定义的变量。

JavaScript一开始定义变量是只有var一种标识符,因为var定义的变量是没有块级作用域的,所以,一开始JS中变量只有全局作用域和函数作用域。

var a = 1;
function b () {
  a = 2;
  console.log(a);
}
b();// 2

console.log(a);// 2

b函数的执行改变了全局变量a的值。

环境变量1.png

如果在b函数中用var重新声明一个a变量,那么这个a变量的作用访问仅限于b函数内。

var a = 1;
function b () {
  var a = 2;
  console.log(a);
}
b();// 2

console.log(a);// 1

环境变量2.png

通过断点,可以看出,b函数作用域有一个变量a,其值为2,全局作用域也有一个变量a,其值为1。

2.词法环境

词法环境指的是用let,const定义的变量。

letconst的出现是为了解决JavaScript中没有块级作用域的问题,而程序的世界,最重要的一点就是要兼容老版本,为了保持兼容不能动var的逻辑,但是又要增加块级作用域,所以就新开了一条路,这条路就叫词法环境。

理论上来说,变量查找时,词法环境的优先级大于变量环境,但实际上如果我们用var和let定义了同名变量,会直接报错SyntaxError: Identifier 'a' has already been declared

重复命名报错.png

letconst会和{}一起组成块级作用域,限定变量的访问范围。不在该块级作用域内的访问会报错。

{
  let a = 2;
}
console.log(a);// ReferenceError: a is not defined
function b () {
  console.log(a);
}
b();// ReferenceError: a is not defined

需要注意的是,letconst不存在变量提升,在声明前访问会报错。

在定义前引用.png

3.作用域链(outer)

作用域链指的是,查找变量时,会在当前作用域,一层层地向上查找。

function a() {
  let a = 1;
  function b() {
    let b = 2;
    function c() {
      console.log('a', a);// 1
      console.log('b', b);// 2
    }
    c();
  }
  b();
}

a();

需要注意的是,如果是返回了子函数的情况,子函数查找作用域是依据定义时的作用域嵌套关系。

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

var c = 2;
let foo = a();
foo();// 1

foo函数内的变量c用的是函数定义时,其父级作用域的c。

4.this指向

this指向有4种可能,默认指的是全局对象,也就是window。

var a = 1;
function foo () {
  debugger
  this.a = 2;
  console.log(this.a)// 2
}
foo();
console.log(a);// 2

this指向是可以改的,可以通过call、apply、bind方法显示的更改。

let obj1 = {
  name:'obj1'
}

let obj2 = {
  name:'obj2'
}

function foo (other) {
  console.log(this.name,other)
}

foo.call(obj2,1);// obj2 1
foo.apply(obj1, [2]);// obj1 2
foo.bind(obj2)(3);// obj2 3

this指向还有两种隐式的,在JS引擎层面有默认的绑定规则的。

第一条是,通过对象访问的,this指向指向这个对象本身,

let obj1 = {
  name:'obj1'
}

let obj2 = {
  name:'obj2'
}

let obj3 = {
  name: 'obj3',
  foo: function(other) { 
    console.log(this.name, other);
  }
}

obj3.foo(3);// obj3 3

第二条是,通过new 构造函数调用,返回的默认对象,该对象的this指向构造函数的原型。

function foo () {
}

foo.prototype.name = 'hello world';

let b = new foo();
console.log(b.name);// hello world

写在最后

总的来说,变量环境、词法环境、作用域链、this指向这四个点涵盖了JavaScript执行上下文的环境变量,搞懂了这几个规则,就能理解具体变量在执行的时候会取哪个值,写出如我们预期一样的JavaScript代码。