This的指向解析

2021/4/23 JavaScript

JavaScript中的this有时候比较难以理解,需要总结一下规律;并详细说明apply、call、bind的区别。

# This的指向问题

this 的绑定规则总共有一下几种形式:

  • 默认绑定
  • 隐式绑定
  • 显示绑定
  • new绑定
  • 箭头函数绑定

优先级:new 绑定 -> 显式绑定 -> 隐式绑定 -> 默认绑定

# 默认绑定

在全局环境下直接定义的函数中调用this,会指向全局对象。

// 非严格模式
function a(){
  console.log(this) // window | 全局对象
}
1
2
3
4

但是严格模式下,只能绑定到undefined。

// 严格模式
'use strict'
function a(){
 console.log(this) // undefined
}
1
2
3
4
5

# 隐式绑定

函数调用时会指向上下为对象。

function _b(){
  console.log(this, 'b');
}
var obj = {
  a: '123',
  b: function(){
    console.log(this, 'obj.b');  // obj // 隐式绑定,上下文对象是obj
    _b() // window // 隐式绑定丢失,查找到上一级(例子里刚好是全局对象)
  },
  c: null,
  d: _b
}
obj.c = _b
console.log(obj.b()); // window
console.log(obj.c()); // obj
console.log(obj.d()); // obj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

例子中打印的log如下:

this.html:23 {a: '123', b: *ƒ, c: *ƒ*, d: *ƒ*}'obj.b' this.html:18Window {window: Window, self: Window, document: document, name: '', location: Location, …}'b' this.html:30 undefined this.html:18{a: '123', b: *ƒ*, c: *ƒ*, d: *ƒ*}* 'b' this.html:31 undefined this.html:18 {a: '123', b: *ƒ, c: *ƒ*, d: *ƒ*}* 'b' this.html:32 undefined

c、d的两种赋值方法都指向obj,只有b的调用会指向window,是因为b在函数内部调用_b时 this指向丢失,于是链式得向上一级的上下文环境中取this,获得到了window

# 显示绑定

apply、call、bind可以显示的修改函数的this,具体的区别在于参数传递形式,实际功能一致

# new 绑定

使用new来调用函数,执行的步骤如下:

function d(val){
  this.val = val
  console.log(this); // d
}
var d1 = new d()
1
2
3
4
5
  1. 创建一个对象
  2. 这个对象会执行prototype的设置
  3. 新的对象会绑定到函数调用的this
  4. 如果函数没有返回对象,那么new表达式中函数调用会返回这个新函数

下面再举一个例子来说明函数有了返回对象时的this指向被覆盖的情景

function f(){
  this.val = 'f'
}
f.prototype.log = function(){
  console.log(this.val, 'f.log');
}
function e(e){
  this.val = 'e'
  return new f()
}
e.prototype.log = function(){
  console.log(this.val, 'log');
}
var e1 = new e()
console.log(e1.log()); // f f.log
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 箭头函数绑定

箭头函数会丢失this,this就会向上层查询this指向,直到全局变量

var g = {
  a: '1',
  b: function(){
    console.log(this.a, 'b');
  },
  c(){
    console.log(this.a, 'c');
  },
  d: ()=>{
    console.log(this, this.a, 'd');
  }
}
g.b() // 1
g.c() // 1
g.d() // window undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# apply与call与bind

apply、call、bind同样都是用来修改函数中的this指向的,区别在于:

apply、call 不同点是: 第一个参数一致,call的第2到N个参数相当于apply的第二个参数(数组,包含了call的第2到N个参数);相同点是:返回的是函数的执行结果

bind、call不同点是:bind返回函数,call返回执行结果;相同点是传参形式一样

var _obj = {
  val: 'aaa',
  a: function(){
    var str = [...arguments].reduce((sum, e)=>{
      return sum  + ' - ' + e
    })

    return this.val + '~' + str;
  }
}
var data = {
  val: 'bbb'
}
var __a = _obj.a('111')
var __b = _obj.a.call(data, '2', '3', '4')
var __c = _obj.a.apply(data, ['2', '3', '4'])
var __d = _obj.a.bind(data, '2', '3', '4')
console.log(__a, __b, __c, __d, __d());
/*
aaa~111 bbb~2 - 3 - 4 bbb~2 - 3 - 4 ƒ (){
  var str = [...arguments].reduce((sum, e)=>{
    return sum  + ' - ' + e
  })

  return this.val + '~' + str;
} bbb~2 - 3 - 4
*/
// 即 __b == __c == __d()
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