This
Alphawq JS
JS 中函数内部 this 指向的判断,相对来说场景比较多,这里做一下总结,先上图:
# 箭头函数
箭头函数的 this 是由箭头函数定义时的词法作用域决定的,而不是在运行时
- 普通调用浏览器环境下输出 window
let fn = () => {
console.log(this)
}
fn()
1
2
3
4
2
3
4
- 严格模式下为 undefined
function outer() {
'use strict'
let fn = () => {
console.log(this)
}
fn()
}
outer() // window
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
- 函数嵌套场景下,箭头函数中的 this 只取决包裹箭头函数的第一个普通函数的 this
let obj = {
bar() {
let fn = () => () => {
console.log(this)
}
fn()()
},
}
obj.bar() // obj
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
- 箭头函数的 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
2
3
4
5
6
7
8
9
10
11
12
13
14
- 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
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
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
2
3
4
5
6
7
8
# bind 的原理
- bind 方法返回一个新函数,当这个新函数被调用时,它的 this 值是传递给 bind 的第一个参数,
- 新函数的参数是调用 bind 时传入的剩余参数加上调用这个新函数时传入的参数
- bind 返回的绑定函数也能使用 new 操作符创建对象
- 这种行为就像把原函数当成构造器(也就是说 new 出来的实例的原型属性
__proto__
指向原函数的原型对象prototype
) - 此时,提供的 this 值被忽略,同时调用时的参数被提供给模拟函数
- 这种行为就像把原函数当成构造器(也就是说 new 出来的实例的原型属性
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
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
2
3
4
5
6
7
8
# 优先级
new > call/apply/bind > 对象.方法 > 直接调用