我如何在 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);