0%

js关于this

this关键字是JavaScript中最复杂的机制之一。它是一个很特别的关键字,被自动定义在所有的函数作用域中。有两个对this的误解:

  • this指向自己
  • this指向函数的作用域

其实,不完全正确,也不完全错误。

this是在运行时绑定的,而不是在编写时绑定的,它的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,而只取决于函数的调用方式。

当一个函数被调用时,会创建一个活动记录(也成为执行上下文)。这个记录包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this就是记录的其中一个属性,会在函数执行的过程中用到。

调用位置和调用栈

要理解this的绑定过程之前,首先要理解调用位置。调用位置就是函数在代码中被调用的位置(而不是声明的位置)。

重要的是分析调用栈(就是为了到达当前执行位置所调用的所有函数)。我们关心的调用位置就在当前正在执行的函数的前一个调用中。例如:

1
2
3
4
5
6
7
8
9
10
function baz() {
console.log('调用栈:baz');
bar();
}

function bar() {
console.log('调用栈:baz->bar');
}

baz();

上面我们声明了baz()bar()两个函数,然后在baz()里面调用了bar()

然后分析上面的代码中的函数的调用位置以及调用栈。首先我们直接调用了baz(),那么在函数baz中调用栈为baz,调用位置为全局作用域。然后在baz的里面调用了bar(),那么在函数bar中,调用栈为baz->bar,调用位置为baz

this的绑定规则

函数的执行过程中的调用位置决定了this的绑定对象。有四条绑定规则:

  • 默认绑定
  • 隐式绑定
  • 显示绑定
  • new绑定

默认绑定

最常用的函数调用类型:独立函数调用。也可以把这条规则看做为无法应用其他规则时的默认规则。例如:

1
2
3
4
5
6
7
function foo() {
console.log(this.a);
}

var a = 2;

foo(); // 2

在这里this.a被解析为了全局变量中的a。因为在上面代码中,函数调用的地方为全局作用域中,所以this指向了全局对象。

即默认绑定的情况,在非严格模式下,会将this绑定到全局对象上;在严格模式下,会报错未定义属性。

隐式绑定

隐式绑定需要考虑的是调用位置是否有上下文对象,或是说是否被某个对象包含。例如:

1
2
3
4
5
6
7
8
9
10
function foo() {
console.log(this.a);
}

var obj = {
a: 'obj',
foo: foo,
};

obj.foo(); // obj

上面代码,当foo()被调用时,函数的引用上下文对象为obj,也就是说隐式绑定规则会把函数调用中的this绑定到这个上下文对象。因为调用foo()时this被绑定到obj,所以this.aobj.a是一样的。

对象属性引用链中只有最顶层或者说最后一层才会影响调用位置。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function foo () {
console.log(this.a);
}

var obj = {
a: 1,
obj2: obj2,
};

var obj2 = {
a: 22,
foo: foo,
};

obj.obj2.foo(); // 22

显式绑定

如果我们不想在对象内部包含函数引用,而想在某个对象上强制调用函数,我们可以使用call()apply()方法。

这两个方法的第一个参数是一个对象,他们会把这个对象绑定到this,接着在调用函数时指定这个this。这种方式可以直接指定this的绑定对象,因此称之为显式绑定。

例如:

1
2
3
4
5
6
7
function foo () {
console.log(this.a);
}

var obj = { a: 2};

foo.call(obj); // 2

注意,入股哦传入了一个原始值(字符串、布尔值、数字)来当做this的绑定对象,这个原始值会被转换为它的对象形式,比如new Number(xx)。这通常被称为“装箱”。

new绑定

在传统的面向对象语言中,“构造函数”是类中的一种特殊方法,使用new初始化类时会调用类中的构造函数。

JavaScript中也有一个new操作符,使用方法看起来也一样,然而JavaScript中的new的机制实际上和面向对象的语言完全不同。

在JavaScript中,构造函数只是一些使用new操作符时被调用的函数,他们并不属于某个类,也不会实例化一个类。实际上,他们只是被new操作符调用的普通函数而已。

实际上JavaScript中并不存在所谓的“构造函数”,只有对于函数的“构造调用”。

使用new来构造函数时,会执行下面的操作:

  • 创建一个全新的空对象
  • 这个原型对象会被执行原型连接
  • 这个新对象会被绑定到函数调用的this
  • 如果函数没有返回其他对象,那么就自动返回这个新对象。

例如:

1
2
3
4
5
6
7
function foo () {
this.a = 2;
}

var f = new foo();

console.log(f.a); // 2

四种规则的优先级

new绑定、显示绑定 高于 隐式绑定 高于 默认绑定。

判断方式:

  1. 函数是否在new中调用,如果是的话,说明this绑定的是新创建的对象。
  2. 函数是否通过call、apply(显式绑定)?如果是的话,this绑定的是指定对象
  3. 函数是否在某个上下文对象中调用(隐式绑定)?是的话,this绑定的是那个上下文对象。
  4. 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象。
码字辛苦,打赏个咖啡☕️可好?💘