知识点
- class内部默认是严格模式
class
定义的原型方法的可枚举行- . Proxy
题目
首先看这样一个引子:
const name = 'window';
let obj = {
name: 'jay',
say() {
console.log(this.name)
}
};
obj.say();
let say = obj.say;
say();
两次执行,分别会输出什么?
显然,第一次会打印出jay
,第二次是window
(,如果在严格模式下会报错),是因为this
指向调用者,第二次的调用者是window
在构建类的时候,同样有这个问题:
class Person {
constructor (name) {
this.name = name
}
sayHi () {
console.log(`I am ${this.name}.`)
}
}
const jerry = new Person('Jerry')
const sayHi = jerry.sayHi
sayHi() // => 报错
注意,这里面是直接报错,因为this
是指向了undefined
而不是window
,因为在类和模块的内部,默认就是严格模式
所以在类似于React.js的组件的事件监听当中我们总是需要手动地进行bind(this)
操作。为了简化这样的操作,请你完成一个方法autoBind
,它可以接受一个类作为参数,并且返回一个类。返回的类的实例和原来的类的实例功能上并无差别,只是新的类的实例所有方法都会自动 bind 到实例上。例如:
const BoundPerson = autoBind(Person)
const lucy = new BoundPerson('Lucy')
const sayHi = lucy.sayHi
sayHi() // => I am Lucy.
注意,如果autoBind
以后给原来的类新增方法,也会自动反映在实例上,例如:
Person.prototype.sayGood = function () {
console.log(`I am ${this.name}. I am good!`)
}
const sayGood = lucy.sayGood
sayGood() // => I am Lucy. I am good!
实现
一上来我就想到了可以使用Proxy来拦截new
的操作符,后来又试了试用extends
方法,都可以实现一半,但是后一半,也就是“自动反映在实力上”这个要求没办法实现
先讲一下我用Proxy的实现,利用handler.construct
方法,拦截了new
的操作
const autoBind = (ToBindClass) => new Proxy(ToBindClass, {
construct(targets, argus, newTarget) {
const self = new targets(...argus);
Object.getOwnPropertyNames(targets.prototype).forEach(v => {
self[v] = targets.prototype[v].bind(new targets(...argus))
});
return self
}
})
在构造函数的方法中,使用了Object.getOwnPropertyNames(targets.prototype)
对原型上的方法遍历进行绑定
要注意的是,class里面定义的原型方法是不可枚举的,这一点与直接定义在prototype
是不同的,直接定义在prototype
是可枚举的
所以遍历class的原型方法不能使用Objecet.keys
方法,可以使用Object.getOwnPropertyNames
但是,这种方法没办法动对新增的方法进行处理。
看了看评论区,可以使用双层的Proxy实现,外层的Proxy不同,里面不直接返回实例,而是用Proxy再次进行处理的实例,对实例的get
方法进行拦截,每次访问的时候再去原型链上找,然后进行绑定。
const autoBind = (ToBindClass) => new Proxy(ToBindClass, {
construct(targets, argus, newTarget) {
const self = new targets(...argus);
return new Proxy(self, {
get(target, key) {
if(typeof target[key] === 'function') {
target[key] = target[key].bind(self)
}
return target[key]
}
})
}
})
外层的Proxy可以用函数自己去拦截构造器,所以上面也可以改写成:
const autoBind = (ToBindClass) => {
return function(...argus) {
const self = new ToBindClass(...argus);
return new Proxy(self, {
get(target, key) {
if(typeof target[key] === 'function') {
target[key] = target[key].bind(self)
}
return target[key]
}
})
}
}
太笨了。