提问人:Byzod 提问时间:8/9/2023 更新时间:8/9/2023 访问量:71
在 JavaScript 类中声明事件处理程序的正确方法是什么?[复制]
What's the correct way to declare event handler in class of JavaScript? [duplicate]
问:
当我在 MDN 上检查时,类的方法如下所示:
class Foo {
method1 (){
// content of method1
}
}
但是,我发现它对事件处理程序不利
<!doctype html>
<html lang="en">
<head>
<title>test</title>
</head>
<body>
<div class="settings">
<div>
<label for="cb1">checkbox</label>
<input id="cb1" type="checkbox"></input>
</div>
</div>
<script>
'use strict'
class TEST{
box = null;
info = {content: {blah: "blah"}};
init (){
this.box = window.document.querySelector(".settings");
this.box.addEventListener("change", this.handler);
this.box.addEventListener("change", this.handler2);
}
handler = e=> {
console.log("handler this: %o", this);
console.log("handler info: %o", this.info.content);
}
handler2 (e) {
console.log("handler2 this: %o", this);
console.log("handler2 info: %o", this.info.content);
}
}
let t = new TEST();
t.init();
</script>
</body>
</html>
阅读箭头函数的作用域后,我就明白了为什么会有区别。但是使用箭头函数声明类的方法看起来很奇怪,我做对了吗?
更重要的是,由于我不喜欢一个类中有两种函数样式,如果可能的话,我更喜欢对所有其他方法使用箭头函数,但我不确定这是否适用于或是否有任何潜在的故障或安全问题constructor
请对此发表任何意见?
答:
handler()
是一个箭头函数,因此它继承自外部作用域。不用担心。this
但是对于实例原型中的方法,情况就不同了。function
当你将一个方法作为参数传递时,你基本上在没有上下文的情况下单独传递它(在我们的例子中)。有几个修复方法可以保留上下文:this
用:.bind()
this.box.addEventListener("change", this.handler2.bind(this));
使用箭头函数:
this.box.addEventListener("change", e => this.handler2(e));
在构造函数中绑定:this
constructor() {
this.handler2 = this.handler2.bind(this);
}
还可以在构造函数中遍历对象的原型并绑定每个方法。
但更有趣的是,在不修改类的情况下,有一些通用的解决方案。
如果你想深入研究 JS 代理和原型,我们可以提供一个类包装器来自动绑定实例中的所有方法及其原型链(它甚至支持):super
// intercept `new`
const bindThis = what => new Proxy(what, {
construct(_class, args, constructor) {
const obj = Reflect.construct(...arguments);
if (_class.name !== constructor.name) {
return obj; // the base class, skip it
}
const bindContext = _obj => {
for (const [name, def] of Object.entries(Object.getOwnPropertyDescriptors(_obj))) {
if (typeof def.value === 'function' && name !== 'constructor' &&
// avoid overridding by base class methods
!Object.hasOwn(obj, name)) {
// bind context for all the methods
def.value = def.value.bind(obj);
// make look like ordinary props (enumerable)
def.enumerable = true;
Object.defineProperty(obj, name, def);
}
}
};
let context = obj;
do {
// skip Object.prototype for clearness
Object.getPrototypeOf(context) && bindContext(context);
} while (context = Object.getPrototypeOf(context));
return obj;
}
});
const TEST = bindThis(class TEST {
box = null;
info = {
content: {
blah: "blah"
}
};
init() {
this.box = window.document.querySelector(".settings");
this.box.addEventListener("change", this.handler);
this.box.addEventListener("change", this.handler2);
}
handler = e => {
console.log("handler this: %o", this);
console.log("handler info: %o", this.info.content);
}
handler2(e) {
console.log("handler2 this: %o", this);
console.log("handler2 info: %o", this.info.content);
}
});
const CHILD = bindThis(class CHILD extends TEST {
isChild = true;
handler2(e) {
console.log("OVERRIDDEN");
super.handler2(e);
}
});
let t = new TEST();
let c = new CHILD();
t.init();
c.init();
<select class="settings">
<option>-</option>
<option value="1">option 1</option>
</select>
但是使用箭头函数声明类的方法看起来很奇怪,我做对了吗?
是的,这有效,但请注意它们是类字段中的箭头函数,而不是方法。
更重要的是,由于我不喜欢一个类中有两种函数样式,如果可能的话,我更喜欢对所有其他方法使用箭头函数,但我不确定这是否适用于构造函数,或者它是否有任何潜在的故障?
是的,您不能将这种样式用于 ,并且通常不应该使用它,因为它不能与继承一起使用(不能正确覆盖,不能与 一起使用),并且比共享原型方法使用更多的内存 - 箭头函数是按实例创建的。constructor
super
因此,请仅在您真正需要的地方使用它。替代方法是
在构造函数中显式创建箭头函数,不带类字段语法:
class TEST { constructor() { this.box = null; this.info = {content: {blah: "blah"}}; this.handler = e => { console.log("handler this: %o", this); console.log("handler info: %o", this.info.content); }; } init() { this.box = window.document.querySelector(".settings"); this.box.addEventListener("change", this.handler); this.box.addEventListener("change", this.handler2); } handler2(e) { console.log("handler2 this: %o", this); console.log("handler2 info: %o", this.info.content); } }
定义方法并在构造函数中显式地处理它们:
.bind()
class TEST { box = null; info = {content: {blah: "blah"}}; constructor() { this.handler = this.handler.bind(this); } init() { this.box = window.document.querySelector(".settings"); this.box.addEventListener("change", this.handler); this.box.addEventListener("change", this.handler2); } handler(e) { console.log("handler this: %o", this); console.log("handler info: %o", this.info.content); } handler2(e) { console.log("handler2 this: %o", this); console.log("handler2 info: %o", this.info.content); } }
评论
this
this
this.box.addEventListener("change", (e) => { this.handler2(e) });