将自定义元素的属性链接到它的属性

Linking property of a custom element to it's attribute

问题和演示


我最近开始使用 custom elements

如您所知,HTMLElement 在文档中有标记,JavaScript object。因此,对于我的自定义元素,我尝试 link JavaScript 对象 properties 与元素的 attributes.

因此,如果其中任何一个更新,另一个也会更新。但这并没有发生,我发誓我已经尝试了一切,也许是我遗漏的一些愚蠢的东西,但对我来说,这段代码的行为方式是一个令人毛骨悚然的谜。

看完下面的代码解释和demo,你应该能明白我的问题了:

I've setup a JSFiddle to illustrate my problem, and I will be going over how the code is supposed to work in this post.


HTML

<e-button color="red" width="250px">RED BUTTON</e-button>

好吧,很少有比这更简单的了。我创建了一个名为 "e-button" 的自定义对象,其中 color=redwidth=250px.

JavaScript

var eButtonProto = Object.create(HTMLElement.prototype);

eButtonProto.createdCallback = function() {
    this.__htmlToJsProp(); //Gets all the HTML attributes and makes them accessible via JS.
    this.__processAttr(); //Makes decision upon predefined attributes.
}

eButtonProto.__htmlToJsProp = function() {
    var attr = this.attributes;
    for (var i = 0; i < attr.length; i++) {
        var current = attr[i];
        var name = current.name;
        var value = current.value;
        this[name] = value;
        Object.defineProperty(this, name, {
            get: function() {
                return this.getAttribute(name);
            },
            set: function(val) {
                this.setAttribute(name, val);
            }
        });
    }
}

eButtonProto.attributeChangedCallback = function(name, oldVal, val) {
    this[name] = val;
    this.__processAttr();
}

eButtonProto.__processAttr = function() {
    var color = this.color || this.defaults.color;
    this.style.backgroundColor = color;
}

eButtonProto.defaults = {
    color: "whitesmoke"
}   

var eButton = document.registerElement("e-button", {
    prototype: eButtonProto
});
window.onload = function() {
    redButton = document.querySelector("e-button[color=red]");
    console.log("button ATTRIBUTES", redButton.getAttribute("color"), redButton.getAttribute("width"));
    console.log("button PROPERTIES", redButton.color, redButton.width);
} < /script>

这里真正重要的代码片段是这些,它们基本上应该使我的想法可行,首先是 __htmlToJsProp() 函数:

eButtonProto.__htmlToJsProp = function() {
    var attr = this.attributes; //Gets the element's attributes.
    for (var i = 0; i < attr.length; i++) {
        var current = attr[i]; //Element attribute name,value pair.
        var name = current.name; //Attribute name.
        var value = current.value; //Attribute value.
        Object.defineProperty(this, name, { //Defines the element property from the attribute name, for simplicity I will be using the color attribute as my example.
            get: function() {
                return this.getAttribute(name); //When accessing element.color you should get element.getAttribute("color")
            },
            set: function(val) {
                this.setAttribute(name, val); //When setting element.color = "red" you should also be doing element.setAttribute("color","red");
            }
        });
        this[name] = value; //Sets element.color = "red"
    }
}

然后是 attributeChangedCallback 函数:

eButtonProto.attributeChangedCallback = function(name, oldVal, val) {
    this[name] = val; //This would be the other way around, if the attribute is updated via setAttribute, or the browser console, the property is updated (works).
    this.__processAttr(); //You can ignore this
}

结论

你测试后看到很多我发现如果你把自己放在for循环中并输出属性值,它会给你element.color = "red"element.width = "250px";

但是如果您在 for 循环之外测试它,它会为您提供 element.color = "250px"element.width = "250px" 属性,但属性会正确更新,即 element.getAttribute("color") = "red"element.getAttribute("width") = "250px".

如果你做到了这一步,那么谢谢,希望你能找到解决这个问题的方法,我似乎真的无法解决这个问题,祝你编码愉快:)

您的问题似乎在 for 循环中,getter 和 setter 稍后调用,因此 i 的值不是您认为的那样,循环完成并设置i 到最新的迭代值。

你会用闭包解决它

eButtonProto.__htmlToJsProp = function () {
     var attr = this.attributes;
     for (var i = 0; i < attr.length; i++) {
         (function(current, self) {
             var name = current.name;
             var value = current.value;
             Object.defineProperty(self, name, {
                 get: function () {
                     return this.getAttribute(name);
                 },
                 set: function (val) {
                     this.setAttribute(name, val); 
                 }
             });
             self[name] = value;
         })(attr[i], this);
     }
 }

FIDDLE