执行上下文

执行上下文生命周期
当 JavaScript 代码执行一段可执行代码(executable code)时,会创建对应的执行上下文(execution context)。

可执行代码
这就要说到 JavaScript 的可执行代码(executable code)的类型有哪些了?
其实很简单,就三种,全局代码、函数代码、eval代码。
举个例子,当执行到一个函数的时候,就会进行准备工作,这里的“准备工作”,让我们用个更专业一点的说法,就叫做”执行上下文(execution contexts)”。

对于每个执行上下文,都有三个重要属性:

  • 变量对象(Variable object,VO)
  • 作用域链(Scope chain)
  • this

执行上下文的创建阶段,会分别生成变量对象,建立作用域链,确定this指向,谨记this的指向,是在函数被调用的时候确定的。也就是执行上下文被创建时确定的。且在函数执行过程中,this一旦被确定,就不可更改了

this 并不是取决于他所在的位置,而是取决于他所在的Function是如何调用的
构造函数中的 this ,就是指向即将实例化的那个对象。谨记!

全局上下文

在全局执行上下文中this都指代全局对象。
在浏览器里面this等价于window对象,如果你声明一些全局变量,这些变量都会作为this的属性。

js
1
2
3
4
5
6
7
8
9
10
// 通过this绑定到全局对象
this.a2 = 20;
// 通过声明绑定到变量对象,但在全局环境中,变量对象就是它自身
var a1 = 10;
// 仅仅只有赋值操作,标识符会隐式绑定到全局对象
a3 = 30;
// 输出结果会全部符合预期
console.log(a1);
console.log(a2);
console.log(a3);

函数上下文,函数中的this

  • demo
    js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var a = 20;
    var obj = {
    a: 10,
    c: this.a + 20,
    fn: function () {
    console.log(this)
    return this.a;
    }
    }
    console.log(obj.c);
    console.log(obj.fn());
    注意这里的
    obj: {
    c: this.a + 20
    }

    单独的{}不会形成新的作用域,因此这里的this.a,由于并没有作用域的限制,它仍然处于全局作用域之中。所以这里的this其实是指向的window对象。

直接调用

this指向全局变量, this === window

js
1
2
3
4
function foo(){
return this;
}
console.log(foo() === window); // true

作为对象的一个方法

this指向调用函数的对象

js
1
2
3
4
5
6
7
var person = {
name: "cpp",
getName: function(){
return this.name;
}
}
console.log(person.getName()); // cpp

稍不留心写成了

js
1
2
3
4
5
6
7
var person = {
name: "cpp",
getName: () => {
return this.name;
}
}
console.log(person.getName()); // '' 返回空字符串

这是因为下面要说的箭头函数

箭头函数

所有的箭头函数都没有自己的this,都指向外层,
1.没有自己的this、super、arguments和new.target绑定。
2.不能使用new来调用。
3.没有原型对象。
4.不可以改变this的绑定。
5.形参名称不能重复。
MDN中的解释

An arrow function does not create its own this, the this value of the enclosing execution context is used.
箭头函数会捕获其所在上下文的this值,作为自己的this值。

js
1
2
3
4
5
6
7
8
9
function Person(name){
this.name = name;
this.say = () => {
var name = "xb";
return this.name;
}
}
var person = new Person("sb");
console.log(person.say()); // sb

哪些场景下不能使用箭头函数

1.在一个对象上,定义一个指向函数的属性,当方法被调用时,方法内的this指向方法所属的对象

js
1
2
3
4
5
6
7
8
9
10
const calculator = {
array: [1, 2, 3],
sum: () => {
console.log(this === window); // => true
return this.array.reduce((result, item) => result + item);
}
};
console.log(this === window); // => true
// Throws "TypeError: Cannot read property 'reduce' of undefined"
calculator.sum();

2.定义原型方法
3.定义事件回调函数
4.定义构造函数

作为一个构造函数

this被绑定到正在构造的实例中

js
1
2
3
4
5
6
7
8
9
10
function Person(name){
this.name = name;
this.age = 30;
this.say = function(){
console.log(this.name + ":" + this.age);
}
}
var person = new Person("cpp");
console.log(person.name); // cpp
person.say(); // cpp:30

this绑定优先级

优先级是new 调用 > call、apply、bind 调用 > 对象上的函数调用 > 普通函数调用。

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

var name = 'window';
var person = {
name: 'person',
}
var doSth = function(){
console.log(this.name);
return function(){
console.log('return:', this.name);
}
}
var Student = {
name: '若川',
doSth: doSth
}
// 普通函数调用
doSth(); // window
// 对象上的函数调用
Student.doSth(); // '若川'
// call、apply 调用
Student.doSth.call(person); // 'person'
new Student.doSth.call(person) // VM36:21 Uncaught TypeError: Student.doSth.call is not a constructor

最后一个执行报错
这是因为函数内部有两个不同的方法:[[Call]]和[[Constructor]]。
当使用普通函数调用时,[[Call]]会被执行。当使用构造函数调用时,[[Constructor]]会被执行。call、apply、bind和箭头函数内部没有[[Constructor]]方法。

总结

  • new 调用:绑定到新创建的对象,注意:显示return函数或对象,返回值不是新创建的对象,而是显式返回的函数或对象。
  • call 或者 apply( 或者 bind) 调用:严格模式下,绑定到指定的第一个参数。非严格模式下,null和undefined,指向全局对象(浏览器中是window),其余值指向被new Object()包装的对象。
  • 对象上的函数调用:绑定到那个对象。
  • 普通函数调用: 在严格模式下绑定到 undefined,否则绑定到全局对象。

参考