我如何在 Javascript 中创建元素包装器?
How could I create an element wrapper in Javascript?
出于纯粹的教育和好奇心目的,我正在尝试创建一个元素包装器对象,它允许我将自己的属性和方法附加到一个元素。我试图模拟的行为基本上是这样的:
// get a button element to wrap
const button = document.querySelector('button');
// some function that wraps new properties/methods around a given element
function wrap(element) {
this.customName = 'John';
this.customAge = 100;
this.printName = function() {
console.log(this.customName);
}
// ...
// ...somehow inherit element fields...
// ...
}
// wrap the button element
const customElement = new wrap(button);
// custom behavior:
console.log(customElement.customAge) // output => 100
customElement.printName() // output => 'John'
// legacy behavior
console.log(customElement.clientHeight) // output => client height
customElement.remove() // => should still call 'remove' on the element
所以,在这里我应该可以添加我自己的 methods/properties 但仍然可以正常访问原始字段。这可能吗?
我在这里使用构造函数作为示例只是为了演示预期的行为,但实际上我不知道这是否与解决方案相关。我是 Javascript 的新手,我对原型和 类 进行了大量研究,但我仍然对我将在这里采用什么方法感到困惑。
编辑: 正如 Brad 在评论中指出的那样,我也使用 类:
尝试了此实现
class MyButton extends HTMLButtonElement {
constructor() {
super();
this.customName = 'John';
this.customAge = 100;
}
printName() {
console.log(this.customName);
}
}
const myBtn = new MyButton();
但这导致了错误:
Uncaught TypeError: Illegal constructor
我还没有测试过,但可能是这样的:
// get a button element to wrap
const button = document.querySelector('button');
// some function that wraps new properties/methods around a given element
function wrap(element) {
Object.defineProperties(element, {
customName: {value:"John"},
customAge: {value:100},
printName:{value: () => console.log(element.customName)}
})
return element
}
// wrap the button element
const customElement = wrap(button);
// custom behavior:
console.log(customElement.customAge) // output => 100
customElement.printName() // output => 'John'
// legacy behavior
console.log(customElement.clientHeight) // output => client height
customElement.remove() // => should still call 'remove' on the element
<button>Hello world!</button>
另一种方法是将元素包装到 proxy()
如果 属性 不存在,这将允许 return 自定义数据,并在属性更改时发送通知:
const customElement = function (element, properties = {})
{
this.element = element;
this.customName = 'John';
this.customAge = 100;
this.printName = function() {
console.log(this.customName);
}
//override default properties
for(let i in properties)
{
if (i in element)
element[i] = properties[i];
else
this[i] = properties[i];
}
return new Proxy(this, {
get(target, prop)
{
if (prop in target.element) //is property exists in element?
{
if (target.element[prop] instanceof Function)
return target.element[prop].bind(target.element);
return target.element[prop];
}
else if (prop in target) //is property exists in our object?
return target[prop];
else
return "unknown property"; //unknown property
},
set(target, prop, value, thisProxy)
{
const oldValue = thisProxy[prop];
if (prop in target.element)
target.element[prop] = value;
else
target[prop] = value;
// send notification
target.element.dispatchEvent(new CustomEvent("propertyChanged", {
detail: {
prop,
oldValue,
value
}
}));
}
});
}
const button = new customElement(document.createElement("button"), {customName: "Not John"});
button.addEventListener("propertyChanged", e =>
{
console.log("property changed", e.detail);
});
button.printName();
console.log("age:", button.customAge);
console.log("height:", button.clientHeight);
console.log("blah:", button.blah);
button.blah = "ok";
console.log("blah:", button.blah);
出于纯粹的教育和好奇心目的,我正在尝试创建一个元素包装器对象,它允许我将自己的属性和方法附加到一个元素。我试图模拟的行为基本上是这样的:
// get a button element to wrap
const button = document.querySelector('button');
// some function that wraps new properties/methods around a given element
function wrap(element) {
this.customName = 'John';
this.customAge = 100;
this.printName = function() {
console.log(this.customName);
}
// ...
// ...somehow inherit element fields...
// ...
}
// wrap the button element
const customElement = new wrap(button);
// custom behavior:
console.log(customElement.customAge) // output => 100
customElement.printName() // output => 'John'
// legacy behavior
console.log(customElement.clientHeight) // output => client height
customElement.remove() // => should still call 'remove' on the element
所以,在这里我应该可以添加我自己的 methods/properties 但仍然可以正常访问原始字段。这可能吗?
我在这里使用构造函数作为示例只是为了演示预期的行为,但实际上我不知道这是否与解决方案相关。我是 Javascript 的新手,我对原型和 类 进行了大量研究,但我仍然对我将在这里采用什么方法感到困惑。
编辑: 正如 Brad 在评论中指出的那样,我也使用 类:
尝试了此实现class MyButton extends HTMLButtonElement {
constructor() {
super();
this.customName = 'John';
this.customAge = 100;
}
printName() {
console.log(this.customName);
}
}
const myBtn = new MyButton();
但这导致了错误:
Uncaught TypeError: Illegal constructor
我还没有测试过,但可能是这样的:
// get a button element to wrap
const button = document.querySelector('button');
// some function that wraps new properties/methods around a given element
function wrap(element) {
Object.defineProperties(element, {
customName: {value:"John"},
customAge: {value:100},
printName:{value: () => console.log(element.customName)}
})
return element
}
// wrap the button element
const customElement = wrap(button);
// custom behavior:
console.log(customElement.customAge) // output => 100
customElement.printName() // output => 'John'
// legacy behavior
console.log(customElement.clientHeight) // output => client height
customElement.remove() // => should still call 'remove' on the element
<button>Hello world!</button>
另一种方法是将元素包装到 proxy()
如果 属性 不存在,这将允许 return 自定义数据,并在属性更改时发送通知:
const customElement = function (element, properties = {})
{
this.element = element;
this.customName = 'John';
this.customAge = 100;
this.printName = function() {
console.log(this.customName);
}
//override default properties
for(let i in properties)
{
if (i in element)
element[i] = properties[i];
else
this[i] = properties[i];
}
return new Proxy(this, {
get(target, prop)
{
if (prop in target.element) //is property exists in element?
{
if (target.element[prop] instanceof Function)
return target.element[prop].bind(target.element);
return target.element[prop];
}
else if (prop in target) //is property exists in our object?
return target[prop];
else
return "unknown property"; //unknown property
},
set(target, prop, value, thisProxy)
{
const oldValue = thisProxy[prop];
if (prop in target.element)
target.element[prop] = value;
else
target[prop] = value;
// send notification
target.element.dispatchEvent(new CustomEvent("propertyChanged", {
detail: {
prop,
oldValue,
value
}
}));
}
});
}
const button = new customElement(document.createElement("button"), {customName: "Not John"});
button.addEventListener("propertyChanged", e =>
{
console.log("property changed", e.detail);
});
button.printName();
console.log("age:", button.customAge);
console.log("height:", button.clientHeight);
console.log("blah:", button.blah);
button.blah = "ok";
console.log("blah:", button.blah);