默认绑定规则

this的指向在函数定义的时候是无法确定的,只有在函数执行的时候才能确定。

因为浏览器和node.js的全局对象有一些区别。

在浏览器中:

1
2
3
4
5
function A(){
console.log(this.a); // 1
}
var a = 1;
A()

但是在node.js中:

1
2
3
4
5
6
7
function A(){
console.log(this.a); // undefined
console.log(this.global.b); // 2
}
var a = 1;
b = 2;
A()

从上面的代码中可以看出:

  • 浏览器的thiswindow对象,node.jsthis包含的东西更加多,其中有this.global就是node.js中的全局对象。
  • node.js的全局变量必须使用无修饰符b=2或者global.b=2的方式来声明。

补充:

  • 严格模式下use strict,浏览器的函数内部this指向为undefined,而node.jsthis为空对象{}
  • 严格模式下浏览器全局变量只可以使用this.a= 1 window.a=1不能使用a=1,node.js也只能使用global.a=1
  • 更多严格模式,或者浏览器环境的jsnode.js环境的js后面再详细说明吧。

考虑到以上因素,以下全部以浏览器环境为准。。

隐式绑定

大部分情况下,this会指向那个调用它的对象

1
2
3
4
5
6
7
8
function A(){
var a = 11;
var b = 22;
console.log(this.a) // undefined
console.log(this.b) // 2
}
var b = 2;
A();

上面的 A()可以理解为window.A(),所以this指向了window,但是window下面没有a这个属性,所以是undefined.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function A() {
var a = 11;
var b = 22;
console.log(this.a) // 123
console.log(this.b) // 234
}

var obj1 = {
a: 123,
b: 234,
fn: A
}

var obj2 = {
a: 666,
b: 999,
obj: obj1
}

obj1.fn()

obj2.obj.fn()

上面的两种调用方式的结果都是一样的,this.a // 123 this.b // 234

为什么呢,因为如果存在多次调用,对象属性引用链只有一层或者说最后一层在调用位置中起作用。就是说不管调用多少层,this只会指向第一层。

隐式丢失

继续:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function A() {
var a = 11;
var b = 22;
console.log(this.a) // 1
console.log(this.b) // 2
}

var obj1 = {
a: 123,
b: 234,
fn: A
}

var obj2 = {
a: 666,
b: 999,
obj: obj1
}
this.a=1;
this.b=2;
var fn1 = obj1.fn
var fn2 = obj2.obj.fn
fn1()
fn2()

上面的fn1fn2的输出结果都是一样的this.a // 1 this.b // 2

神奇?为什么和上一步的结论不一样呢,为什么这次的多层调用最终this指向了全局???(当然也取决于是否严格模式)

原来这里的fn1fn2都只是拿到了function A的一个引用!!!不信你输出一下他们的值。

所以说这并没有和上面的结论冲突!!核心在调用,调用了才会确定this的指向。

显式绑定

callapplybind、和new绑定等

关于callapplybind详情可以看[译] JavaScript 中至关重要的 Apply, Call 和 Bind,这篇文章我看了几遍,也只懂了个大概,他们三个好像区别不是很大,所以我后面会写一篇简单捋一下三者的区别。

this指向很好理解,绑定了某个对象,this就指向绑定的对象

new绑定

new运算符
官网说使用**new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。new** 关键字会进行如下的操作:

  1. 创建一个空的简单JavaScript对象(即{});** var a = {}**
  2. 链接该对象(即设置该对象的构造函数)到另一个对象 ; a.__proto__ = 构造函数.prototype
  3. 将步骤1新创建的对象作为this的上下文 ;**this 指向 a对象**
  4. 如果该函数没有返回对象,则返回this如果返回的null,还是返回this
1
2
3
4
5
function A() {
this.a = 1;
}
var obj = new A();
console.log(obj.a)

这段代码比较好理解,没什么好说的。

优先级

显示绑定大于隐式绑定是毋庸置疑的。

主要看看,new绑定和apply call bind谁的优先级高?

1
2
3
4
5
6
7
8
9
10
11
function A(a) {
this.a = a
}

var obj1 = {a:123}
var foo = A.bind(obj1)
foo(1)
console.log(obj1)
var newFoo = new foo(12)
console.log(obj1.a)
console.log(newFoo.a)

可以看到,new操作修改了bind的绑定。

所以最终结果是:new绑定 > 显示绑定(call、apply) > 隐式绑定 > 默认绑定

如何判断this绑定对象

如果要判断一个运行中函数的this绑定,就需要找到这个函数的直接调用位置。找到后可以按照顺序应用下面这四条规则来判断this的绑定对象。

  1. 由new调用?绑定到新创建对象。
  2. 由call或者apply(或者bind)调用?绑定到指定对象。
  3. 由上下文对象调用?绑定到那个上下文对象。
  4. 默认:在严格模式下绑定到undefined,否则绑定到全局对象

箭头函数

说完了 this,但是发现好像漏掉了什么,在es6中引入的箭头函数,也和this的指向有一定的关系。

这里只探讨一下箭头函数的this,对于箭头函数的其他特性,有机会在写。

箭头函数默认绑定外层的this对象

1
2
3
4
5
6
7
8
9
10
var obj = {
a: function () {
console.log(this)
},
b: () => console.log(this)
}
obj.a(); // obj对象,因为方法a由Obj对象调用
obj.b(); // window对象,严格模式会输出undefined


上面的代码没什么好分析的,在看一段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
var obj1 = {
a: {
fna: function () {
console.log(this) // obj1.a对象
}
},
b: {
fnb: () => console.log(this) // window对象
},
c: function fnc() {
return () => console.log(this) // obj1 对象
},
d: () => {
return () => console.log(this)
},
e: function a() {
console.log(this); // obj.e 对象
var f1 = function () { console.log(this) } // window对象
f1()
}
f: function ff() {
console.log(this) // obj1对象
function fff() {
console.log(this) // window对象
var ffff = () => console.log(this); // window对象
ffff()
}
fff()
}
}
obj1.a.fna();
obj1.b.fnb();
obj1.c()();
obj1.d()();
obj1.e();
obj1.f()
  • 第一个:调用fna()方法的为obj1.a这个对象
  • 第二个:箭头函数没有被非箭头函数包含,无论嵌套多少层,都是中指向最外层的this,即window
  • 第三个:箭头函数被非箭头函数包含,this指向调用非箭头函数的对象
  • 第四个:同第二个,无论嵌套多少箭头函数或者对象, this始终指向最外层
  • 第五个:非箭头函数嵌套,里面的函数没有被,所以this指向 全局对象,箭头函数的出现就是为了解决这一类问题
  • 第六个:虽然箭头函数被非箭头函数包含,但是包含它的非箭头函数没有被一个对象调用,所以他们的this指向的都是全局

下面看下bind apply call 能否修改箭头函数的指向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var obj4 = {
a: () => {
console.log(this)
},
b: function () {
var f1 = () => console.log(this)
f1()
}
}
obj4.a() // window
obj4.b() // obj4
var obj5 = {}
obj4.a.call(obj5) // window
obj4.b.call(obj5) // obj5
  • call无法修改箭头函数的指向,由于this在箭头函数中已经按照词法作用域绑定了,无法修改。
  • 但是call可以修改包含箭头函数的非箭头函数的this指向,箭头函数内的this也同时被修改。
  • 由此证明,箭头函数的this 是绑定在包含它的那个非箭头函数上面。

最后留一个问题:

1
2
3
4
5
6
7
8
9
10
11
12
var obj4 = {
a: () => {
console.log(this)
},
b: function () {
var f1 = () => console.log(this)
f1()
}
}

var bar = obj4.b
bar()

上面的代码会输出什么呢?

虽然是参考的别人的文章,但是我是从头到尾结合很多篇文章捋了一遍的,例子的代码也全部是手敲执行的。。。

参考链接:

https://juejin.im/post/5c049e6de51d45471745eb98

https://www.cnblogs.com/pssp/p/5216085.html

https://www.liaoxuefeng.com/wiki/1022910821149312/1031549578462080