document.registerElement - Why do we need to speci

2019-04-10 16:43发布

问题:

Consider I want to extend the native button element, and create my own super-button element. As I know, it must follow the following pattern:

var SuperButton = document.registerElement('super-button', {
  prototype: Object.create(HTMLButtonElement.prototype),
  extends: 'button'
});

It looks strange to me - doesn't the prototype and extends parameters say the same thing? If I explicitly say that my super-button use the HTMLButtonElement prototype, why do I also need to specify that it extends the button element? isn't it redundant? For me it looks like exactly the same information.

回答1:

From the Custom Elements specification:

In general, the name of the element being extended cannot be determined simply by looking at what element interface it extends, as many elements share the same interface (such as q and blockquote both sharing HTMLQuoteElement).

In other words, while it may be redundant for <button> elements, it isn't redundant in general and the spec needs to support the general case.

I would argue that it isn't even redundant for <button> though, as there is nothing preventing you from doing:

var SuperButton = document.registerElement('super-button', {
  prototype: Object.create(HTMLButtonElement.prototype),
  extends: 'a'
});


回答2:

To clarify Paulpro's answer: the extends creates the is= syntax. If q and blockquote both share HTMLQuoteElement, and you extend q, then you can write <q is="super-button"> but cannot write <blockquote is="super-button">.

Regarding what means to extend an a link with another element's prototype, this would create a link that does not have the appropriate functions and properties, which is bad:

See: https://jsfiddle.net/3g0pus8r/

<a is="anchor-one" href="google.com">1st link</a>
<a is="anchor-two" href="google.com">2nd link</a>

var proto1 = Object.create(HTMLAnchorElement.prototype, {
  createdCallback: {
    value: function() { 
    console.log("1st:");
    console.log(HTMLAnchorElement.prototype.isPrototypeOf(this));
    console.log(this.hostname);
    console.log(this.toString());
    }
  }
});

var proto2 = Object.create(HTMLButtonElement.prototype, {
  createdCallback: {
    value: function() {        
    console.log("\n2nd:");
    console.log(HTMLAnchorElement.prototype.isPrototypeOf(this));
    console.log(this.hostname);
    console.log(this.toString());
    }
  }
});

document.registerElement("anchor-one",{prototype:proto1, extends: "a"});
document.registerElement("anchor-two",{prototype:proto2, extends: "a"});

The result is:

1st:
true
fiddle.jshell.net
https://fiddle.jshell.net/_display/google.com

2st:
false
undefined
[object HTMLButtonElement]

However, you may still want to:

  • Extend MyOtherAnchor which in turn extends HTMLAnchorElement;
  • Extend HTMLElement and implement all the anchor interface yourself.
  • Create an anchor with a different, non-standard, interface.


回答3:

Actually, its allows you to distinguish between Custom Tags declaration versus Type Extensions declaration (as suggested by Bergi's second comment), as both share the same method document.registerElement().

Type Extensions: use extends

document.registerElement( "button-v2",  { 
    prototype: Object.create( HTMLButtonElement.prototype ),
    extends: 'button'
} )

<button-v2> will continue to act as a <button> (ie keeps its semantics).

Custom Tags: don't use extends

document.registerElement( "not-button",  { 
    prototype: Object.create( HTMLButtonElement.prototype )
} )

<not-button> inherits from HTMLButtonElement interface but lost its semantics (ie won't act as a <button>)


NB: If the only reason was really because you cannot always infer an <element> from its prototype chain, an optional parameter would have been proposed to disambiguate such rare cases. Here they have choosen a common, synthetic method, which is confusing at first sight you're right!