0

I'm trying to learn more about Javascript and dig a bit into the prototype chain. I wanted to create a small extension for an HTMLElement when I came across this problem.

The way I understand Object.create is that the object that is passed to it is used to create the context for the new object and that the first link in the prototype chain of the newly created object will point to the object passed to the Object.create method. That being the case, the method of extension used in the bar method below seemed to be the correct approach to me as this newly created object would be given its HTMLElement as its context.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <span id="test"></span>

    <script>
      HTMLElement.prototype.foo = function() {
        let foo = Object.create(null);
        foo.parentElement = this;
        foo.parentElement.appendChild(document.createElement('div'));
      }

      HTMLElement.prototype.bar = function() {
        let fooRenderer = Object.create(this);
        fooRenderer.appendChild(document.createElement('div'));
      }

      document.getElementById('test').foo();
      document.getElementById('test').bar();
    </script>
</body>
</html>

What happens though is that the foo method works correctly by appending a new div child element to <span id="test"></span>, but bar does not.

When I open the developer tools in my browser and try to follow the prototype chains of the two objects that are having appendChild invoked on them, they look almost identical:

foo Object
    .parentElement <span#test2>
        .__proto__ HTMLSpanElementPrototype
            .__proto__ HTMLElementPrototype
                .__proto__ ElementPrototype
                    .__proto__ NodePrototype
                        .appendChild
                        .__proto__ EventTargetPrototype
                            .__proto__ Object
                                .__proto__
                                    get
                                    set

fooRenderer Object
    .__proto__ <span#test2>
        .__proto__ HTMLSpanElementPrototype
            .__proto__ HTMLElementPrototype
                .__proto__ ElementPrototype
                    .__proto__ NodePrototype
                        .appendChild
                        .__proto__ EventTargetPrototype
                            .__proto__ Object
                                .__proto__
                                    get
                                    set

I have created a jsFiddle with this example.

Can someone please explain to me why bar isn't working? Is bar in fact the more correct approach? If so, how should it be setup to work properly?

Thanks in advance for any help!!!

peinearydevelopment
  • 8,782
  • 5
  • 36
  • 65
  • 1
    Despite having an `HTMLSpanElement` in your prototype chain, your `fooRenderer` object is just an object and not a real DOM node. – Bergi Oct 31 '16 at 18:22
  • @Bergi Thanks, but I'm not sure what you are saying. Are you saying that javascript is actually traversing the prototype chain and finding a method `appendChild`, but can't execute it because at that point it is being executed on an object and not a DOM node? – peinearydevelopment Oct 31 '16 at 18:24
  • 1
    Yes, exactly (you should be getting an error that states something like that). It is tried to be called on an object that does inherit the node methods, but was not initialised as a node (and is not a *native* DOM node object as well). – Bergi Oct 31 '16 at 18:28
  • @Bergi So while `Object.create` links the prototype chain and gives access to all of those methods, it doesn't actually retain a reference to that object it was passed at all? Is there a way to maintain a reference to that object in such a way that the call to `appendChild` will work or is the approach shown in `foo` above really the correct approach to this problem? – peinearydevelopment Oct 31 '16 at 18:33
  • 1
    The prototype chain link *is* a reference. But no, you're not calling the inherited methods on the original object, you are calling them on the new object (which makes them shareable). To "maintain a reference", you'd just want a direct reference `let fooRenderer = this` (or like you did in the `foo` method, `let parentElement = this`). – Bergi Oct 31 '16 at 18:43
  • @Bergi makes sense, thank you very much!!! – peinearydevelopment Oct 31 '16 at 18:46

1 Answers1

0

Neither of these examples are "correct." In prototype methods you should not be trying to instantiate a new copy of the object you're already attached to. All you need to do is this:

HTMLElement.prototype.bar = function() {
    let div = document.createElement('div');
    div.innerHTML = 'fooRenderer';
    this.appendChild(div);
}

The first example only works because foo.parentElement will be a valid, native HTMLElement, not a custom created object.

See this answer for why you can't invoke "native" browser methods like appendChild on custom objects.

Community
  • 1
  • 1
Andy Ray
  • 26,451
  • 11
  • 86
  • 123
  • I see that my question didn't fully ask what I was trying to get at. I understand what you are saying. I'm trying to create an object that I can maintain a reference to for future DOM manipulation. In your example then, I would either `return this;` at the end or create and object in the function with a property that would keep a reference to the DOM node and return that? Thanks! – peinearydevelopment Oct 31 '16 at 19:10