This

JS


JS 中函数内部 this 指向的判断,相对来说场景比较多,这里做一下总结,先上图:

# 箭头函数

箭头函数的 this 是由箭头函数定义时的词法作用域决定的,而不是在运行时

  1. 普通调用浏览器环境下输出 window
let fn = () => {
  console.log(this)
}
fn()
1
2
3
4
  1. 严格模式下为 undefined
function outer() {
  'use strict'

  let fn = () => {
    console.log(this)
  }
  fn()
}
outer() // window
1
2
3
4
5
6
7
8
9
  1. 函数嵌套场景下,箭头函数中的 this 只取决包裹箭头函数的第一个普通函数的 this
let obj = {
  bar() {
    let fn = () => () => {
      console.log(this)
    }
    fn()()
  },
}

obj.bar() // obj
1
2
3
4
5
6
7
8
9
10
  1. 箭头函数的 this 不能被 call/apply/bind 修改
let a = {
  name: 'a',
}
let b = {
  name: 'b',
  bar() {
    let fn = () => {
      console.log(this.name)
    }
    fn.call(a)
  },
}

b.bar() // 'b'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  1. class 中的箭头函数
  • this 依然指向类的实例,静态方法中指向类自身
  • 使用箭头函数定义的方法,是定义在实例上的,而不是原型上
  • 不能使用 super 关键字
  • 不能当做构造器用 new 调用
// ======== this 指向 ========= //
class Person {
  static name = 'Person'
  constructor(name) {
    this.name = name
  }
  static getName = () => {
    console.log(this.name)
  }

  getName = () => {
    console.log(this.name)
  }

  sayHi() {
    console.log('Hi, i am ' + this.name)
  }
}

Person.getName() // 'Person'
let p = new Person('p')
p.getName() // 'p'
// 定义在实例上的,而不是原型上
Reflect.ownKeys(p) // ['getThis', 'name']

// ======== 不能使用super关键字 ========= //
class Man extends Person {
  constructor() {
    super('man')
  }

  sayName = () => {
    super.getName()
  }

  sayHi() {
    super.sayHi()
  }
}
let man = new Man()
man.sayName() // Uncaught TypeError: (intermediate value).getName is not a function
man.sayHi() // 'Hi, i am man'

// ======== 没有arguments对象 ========= //
let fn = () => {
  console.log(arguments)
}

fn() // Uncaught ReferenceError: arguments is not defined

// ======== 不能使用new调用 ========= //
let Bar = (name) => {
  this.name = name
}

new Bar('bar') // Uncaught TypeError: Bar is not a constructor
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

# 普通函数

普通函数的 this 指向比较简单,略过。这里主要看下能够改变函数运行时 this 指向的几个方法的原理

# call 的原理

实现原理

Function.prototype.myCall = function (ctx, ...args) {
  let fn = this
  let key = Symbol()
  ctx[key] = fn
  let res = ctx[key](...args)
  delete ctx[key]
  return res
}
1
2
3
4
5
6
7
8

# apply 的原理

Function.prototype.myApply = function (ctx, args) {
  let fn = this
  let key = Symbol()
  ctx[key] = fn
  let res = ctx[key](args)
  delete ctx[key]
  return res
}
1
2
3
4
5
6
7
8

# bind 的原理

  • bind 方法返回一个新函数,当这个新函数被调用时,它的 this 值是传递给 bind 的第一个参数,
  • 新函数的参数是调用 bind 时传入的剩余参数加上调用这个新函数时传入的参数
  • bind 返回的绑定函数也能使用 new 操作符创建对象
    • 这种行为就像把原函数当成构造器(也就是说 new 出来的实例的原型属性__proto__指向原函数的原型对象 prototype
    • 此时,提供的 this 值被忽略,同时调用时的参数被提供给模拟函数
Function.prototype.myBind = function (ctx, ...args) {
  // 原函数
  let fToBind = this
  // 待返回的函数
  function fBound() {
    args = [...args, ...arguments]
    let res = fToBind.apply(this instanceof fToBind ? this : ctx, args)
  }

  fBound.prototype = Object.create(fToBind.prototype)
  return fBound
}
1
2
3
4
5
6
7
8
9
10
11
12

# new 的原理

如果函数被当做构造器调用,那函数内部的 this 就绑定在了由构造器创建出来的实例上

new 被调用后做了三件事情:

  • 创建一个对象,并让这个对象的原型属性指向构造器的原型对象
  • 以创建的对象为构造器的 this,调用构造器
  • 如果构造器返回了一个对象,则直接返回该对象,否则返回创建的对象
function myNew(Ctor, ...args) {
  if (typeof Ctor !== 'function') return
  let ins = new Object()
  let isObject = (o) => typeof o === 'object' && o !== null
  Object.setPrototypeOf(ins, Object.create(Ctor.prototype))
  let res = Ctor.apply(ins, args)
  return isObject(res) ? res : ins
}
1
2
3
4
5
6
7
8

# 优先级

new > call/apply/bind > 对象.方法 > 直接调用

Last Updated: 10/21/2024, 4:15:17 PM