最近在看设计模式时被这个问题困扰了一段时间,这里记录一下。
问题
我们首先来看正确的实现,这段代码的作用就是在不破坏原有功能的基础上增加新的功能(装饰器):
// AOP 切面
Function.prototype.before = function(fn) {
const self = this
return function () {
fn.apply(this)
return self.apply(this)
}
}
// 原始对象
const origin = {
val: 'hello',
// 显示 this 中的值
getter() {
console.log(this.val)
}
}
// 为原始对象的 getter 方法添加装饰器
origin.getter = origin.getter.before(() => console.log('self'))
origin.getter() // => self hello
运行之后可以看到 selfBefore
通过使用 apply
解决了 this 的指向问题。这里我遇到了第一个困惑:self
保存了原始的 this 引用,那么为了修复之后执行的 this 指向,不应该 this.apply(self)
么,为什么却反过来了呢?
之后我尝试使用箭头函数重写这段代码:
// 使用箭头函数自动绑定 this
Function.prototype.before = function(fn) {
return () => {
fn()
return this()
}
}
// 添加装饰器
origin.getter = origin.getter.before(() => console.log('arrow'))
origin.getter() // => arrow undefined
这时候 getter
方法居然获取不到 this.val
了。这里我遇到了第二个困惑,为什么箭头函数自动将 return 出去的 function 作用域指向了原有的 this,但是最后还是没有访问到 this.val
呢?
原因
接下来直接说明原因:在 return 之前的 this 指向的并不是 origin
对象,而是 origin.getter
这个方法!我在一开始的理解中过于想当然,错误的认为 self
中保存的就是正确的 this 指向。
现在再来看第一个疑问,self
确实是保存了原始的 this 引用,但是这个引用恰恰是错误的引用(指向了 origin.getter
,并不能访问到 origin
),而 return 出去的 function 里的 this 指向了调用它的人(最后的 origin.getter()
进行了调用,所以 this 就指向了 origin
),所以说需要把正确的引用(return function 中的 this)应用给作用域错误的 self。
然后第二个问题也很明显了,虽然箭头函数可以绑定 this 不假,但是改写后的代码是等同于 this.apply(self)
的,这个写法在第一个疑问里已经可以证明是错误的了。也就是说,我们这种写法 直接放弃了正确的 this 引用,“自动”的把错误的 this 同步到了 return 出来的 function 里。
误区
这个写法中最容易引起误解的就是 this
是有可能指向一个函数的(before
定义在 Function.prototype
上)。一般情况下我们都会默认觉得 this 指向的是该方法所在的对象。所以如果陷入了这个误区里,第一个实现中的 self.apply(this)
就很容易让人困惑:为什么要给一个作用域绑定另一个作用域?实际上,这个 self
只是保存了原始 方法 的引用,方便我们在 return 出去的 function 里进行调用,仅此而已。