​ 在 Java 中,this 关键字指向的是当前对象的引用,对于熟悉 Java 的人来说是再简单不过的东西,因此我一度以为 JavaScript 也是如此,直到有个前辈对我讲起 JavaScript 中 this 的引用问题,他问了几个 this 的问题,我一个也没回答对,显然我对 JS 的了解还只停留在皮毛。

1. this

​ 与我以往接触的 Java 和 C++ 相比,this 关键字在 JavaScript 稍有不同,并且严格模式与非严格模式也存在一定差异,在ES6的箭头函数中,this 的指向又会有一定差异,导致一些菜鸟接触 JavaScript 的 this 关键字时很容易搞混 this 绑定的对象。

this 指向什么,并不单纯取决于在哪个对象中被调用,而是完全取决于在什么地方以什么方式调用

2. this 绑定规则

通过参考网上的资料,this的绑定规则大致分为 4 种:

  1. 默认绑定
  2. 隐形绑定
  3. 显性绑定
  4. new 绑定

优先级从低到高。

先看看在全局环境this 的指向:

1
console.log(this)	//Window

结果显示在不进行任何函数的调用时,this 所指向的 Window 对象

2.1 默认绑定

1
2
3
4
function aaa(){
console.log(this)
}
aaa(); //Window

​ 像aaa()这样直接调用的,使用默认绑定规则,将全局对象 Window 绑定到this,所以最后的输出结果为Window

在严格模式下,全局对象无法进行默认绑定,因此在相同的调用方式下会出现 undefined 的情况:

1
2
3
4
5
function aaa(){
"use strict"
console.log(this)
}
aaa(); //undefined

2.2 隐性绑定

看下面一段代码:

1
2
3
4
5
6
7
8
9
10
11
function aaa(){
console.log(this.n)
}

var obj = {
n : 999,
aaa : aaa
}

aaa(); //undefined
obj.aaa(); //999

由于第一次是直接调用aaa(),所以使用的是默认绑定规则,this指向的是Window,而并没有定义全局变量n,所以输出undefined

第二次调用时,aaa()函数被当作引用属性,添加到obj对象上,此时函数aaa()有了上下文对象,即obj此时,函数里的this默认绑定为上下文对象,最后等于打印obj.n,因此输出999

2.2.1 多层调用链

如果是链性关系,如xx.yy.obj.aaa(),上下文取函数的直接上级,即紧挨着的那个。

1
2
3
4
5
6
7
8
9
10
11
12
13
function aaa (){
console.log(this.n)
}
var n = 0
var obj1 = {
n : 10,
aaa : aaa
}
var obj2 = {
n : 20,
obj1 : obj1
}
obj2.obj1.aaa() //10 this绑定的是obj1

2.2.2 隐式丢失(函数别名)

1
2
3
4
5
6
7
8
9
10
11
12
13
function aaa (){
console.log(this.n)
}

var n = 0

var obj = {
n : 10,
aaa : aaa
}

var bar = obj.aaa
bar() //0 这里的this指向window

obj.aaa赋值给bar,调用bar却没有触发隐式绑定,而是触发了默认绑定,这是为什么?

​ 原因是obj.aaa是引用属性,赋值给bar的是aaa函数本身,所以bar实际上是aaa的一个别名,我们通过bar来找到aaa,直接调用bar函数导致触发了默认绑定。

2.2.3 隐式丢失(回调函数)

1
2
3
4
5
6
7
8
9
10
11
12
function aaa() { 
console.log( this.n );
}

var n = 2;

var obj = {
n: 3,
aaa: aaa
};

setTimeout( obj.aaa, 100 ); // 2 这里的this指向window

明明在obj中进行了隐式绑定,为什么最后this还是指向了window呢?

​ 原因是虽然传入的参数是obj.aaa,但是因为obj.aaa是引用属性,所以实际上传入的参数是aaa函数本身,与obj没有关系,所以最后触发的依旧是默认绑定

2.3 显性绑定

​ 若要使用上面的隐性绑定,则上下文必须包含我们的函数,但实际中若要每个对象都包含这个函数,会使得维护性变差。在下面的显性绑定中,会给函数强制性绑定this

1
2
3
4
5
6
7
8
function aaa(){
console.log(this.n)
}
var obj = { n : 999 }

aaa.call(obj) //999
aaa.apply(obj) //999
aaa.bind(obj)() //999

call()applybind()三者比较类似,都是用来改变函数的this指向,三个函数的第一个参数都是this所指向的对象,后面的参数是函数的参数。三者aaa()函数的this指向obj对象,所以aaa中打印的this.n实际上是obj对象中的n

call()apply()在改变函数的this的同时会直接调用函数;而bind()比较特殊,它不会立刻执行,只是将一个值绑定到this上,并将绑定好的函数返回;当在bind()后面再加一个括号时,才会立即执行。

​ 在显示绑定中,对于 null 和 undefined 的绑定将不会生效。

1
2
3
4
5
function aaa (){
console.log(this)
}
aaa.call(null) //window
aaa.call(undefined) //window

2.4.1 硬绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function aaa() { 
console.log( this.n );
}

var n = 2;

var obj1 = {
n: 3,
};

var obj2 = {
n: 4,
};

var bar = function(){
aaa.call( obj1 );
}

setTimeout( bar, 100 ); // 3 this绑定obj1

bar.call( obj2 ); // 3 this绑定obj1

第一次调用setTimeout函数,由于在bar中使用了显示绑定,所以this绑定了obj1

第二次调用时虽然将bar显式调用到obj2上,但是aaa已经被显示绑定到obj1上,所以在aaa中,this指向obj1,并不会因为bar函数内指向obj2而改变。

2.4 new 绑定

2.4.1 关键字 new

​ 在 javascript 中,关键字 new 与在 Java、C++ 等语言中的作用一样,都是创建一个新对象,但是创建的机制却有所不同。

​ 在其他的一些面向对象的语言中,创建一个新对象总是少不了构造函数这个概念,要创建对象时使用 new ClassName()的形式自动调用构造函数,而在JS中则有些不同。

js 中只要是用 new 关键字修饰的函数就是构造函数。那么在调用构造函数后,js帮我们做了什么呢:

  1. 创建一个新对象;[ var obj = {} ]
  2. 把这个新对象的__proto__属性指向 原函数的prototype属性。(即继承原函数的原型)
  3. 将这个新对象绑定到 此函数的this上
  4. 返回新对象,如果这个函数没有返回其他对象

2.4.2 new 绑定

1
2
3
4
5
6
7
8
function aaa(){
this.n = 999
console.log(this)
}
aaa() //Window对象
console.log(window.n) //999
var obj = new aaa() //aaa函数对象
console.log(obj.n) //999

第一次直接调用aaa()函数,默认绑定Window对象,此时this.n等同于window.n;后面以aaa()函数作为obj的构造函数,此时输出的thisaaa函数对象,此时的this.n等同于obj.n

2.5 绑定优先级

1
new 绑定 > 显示绑定 > 隐式绑定 > 默认绑定

2.6 箭头函数的 this 绑定

​ 在ES6 中,通过“=>” 而非 function 创建的函数,叫做箭头函数。它的 this 绑定取决于外层(函数或全局)作用域并且四种绑定规则对箭头函数并不生效

1
2
3
4
5
6
7
var aaa = (()=>console.log(this.n))
var obj = {
aaa: aaa
}
aaa() //window 外层作用域
obj.aaa() //window 隐式绑定不生效
aaa.call(obj) //window 显示绑定不生效
1
2
3
4
5
6
7
8
var obj = {
aaa: function(){
return ()=>console.log(this)
},
bbb: ()=>console.log(this)
}
obj.aaa()() //obj
obj.bbb() //window

​ 我们发现同样的箭头函数,在aaa中额外套了一层function,而在bbb中没有,使得两次调用得到的this是不一样的。

  1. 我们先要搞清楚一点,obj的当前作用域是window
  2. 如果不用functionfunction有自己的函数作用域)将其包裹起来,那么默认绑定的父级作用域就是window
  3. function包裹的目的就是将箭头函数绑定到当前的对象上。函数的作用域是当前这个对象,然后箭头函数会自动绑定函数所在作用域的this,即obj

参考链接

https://segmentfault.com/a/1190000011194676