Почему цепочка прототипов выполняется по-другому?

Я пытаюсь узнать больше о Javascript и немного покопаться в цепочке прототипов. Я хотел создать небольшое расширение для HTMLElement, когда столкнулся с этой проблемой.

Насколько я понимаю Object.create, объект, который передается ему, используется для создания контекста для нового объекта и что первая ссылка в цепочке прототипов вновь созданного объекта будет указывать на объект, переданный методу Object.create. В этом случае метод расширения, используемый в методе bar ниже, показался мне правильным подходом, поскольку этот вновь созданный объект получит свой HTMLElement в качестве своего контекста.

<!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>

Однако происходит то, что метод foo работает правильно, добавляя новый дочерний элемент div к <span id="test"></span>, а bar — нет.

Когда я открываю инструменты разработчика в своем браузере и пытаюсь следовать цепочкам прототипов двух объектов, для которых вызывается appendChild, они выглядят почти одинаково:

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

В этом примере я создал jsFiddle.

Может кто-нибудь объяснить мне, почему bar не работает? Действительно ли bar более правильный подход? Если да, то как его настроить для правильной работы?

Заранее благодарю за любую помощь!!!


person peinearydevelopment    schedule 31.10.2016    source источник
comment
Несмотря на наличие HTMLSpanElement в вашей цепочке прототипов, ваш объект fooRenderer — это просто объект, а не настоящий узел DOM.   -  person Bergi    schedule 31.10.2016
comment
@Bergi Спасибо, но я не уверен, что ты говоришь. Вы говорите, что javascript фактически проходит цепочку прототипов и находит метод appendChild, но не может его выполнить, потому что в этот момент он выполняется на объекте, а не на узле DOM?   -  person peinearydevelopment    schedule 31.10.2016
comment
Да, точно (вы должны получить сообщение об ошибке, в котором говорится что-то вроде этого). Его пытаются вызвать для объекта, который наследует методы узла, но не был инициализирован как узел (и также не является родным объектом узла DOM).   -  person Bergi    schedule 31.10.2016
comment
@Bergi Итак, хотя Object.create связывает цепочку прототипов и дает доступ ко всем этим методам, на самом деле он вообще не сохраняет ссылку на тот объект, который был передан? Есть ли способ поддерживать ссылку на этот объект таким образом, чтобы вызов appendChild работал, или подход, показанный в foo выше, действительно является правильным подходом к этой проблеме?   -  person peinearydevelopment    schedule 31.10.2016
comment
Звено цепи прототипа является ссылкой. Но нет, вы не вызываете унаследованные методы для исходного объекта, вы вызываете их для нового объекта (что делает их общими). Чтобы поддерживать ссылку, вам просто нужна прямая ссылка let fooRenderer = this (или, как в методе foo, let parentElement = this).   -  person Bergi    schedule 31.10.2016
comment
@Bergi имеет смысл, большое спасибо !!!   -  person peinearydevelopment    schedule 31.10.2016


Ответы (1)


Ни один из этих примеров не является «правильным». В методах-прототипах вы не должны пытаться создать новую копию объекта, к которому вы уже присоединены. Все, что вам нужно сделать, это:

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

Первый пример работает только потому, что foo.parentElement будет действительным собственным HTMLElement, а не созданным пользователем объектом.

См. этот ответ, почему вы не можете вызывать "собственные" методы браузера, такие как appendChild, для пользовательских объектов.

person Andy Ray    schedule 31.10.2016
comment
Я вижу, что мой вопрос не полностью спрашивал, чего я пытался добиться. Я понимаю, что вы говорите. Я пытаюсь создать объект, на который я могу ссылаться для будущих манипуляций с DOM. Тогда в вашем примере я бы либо return this; в конце, либо создал и возразил бы в функции со свойством, которое сохранило бы ссылку на узел DOM и вернуло бы это? Спасибо! - person peinearydevelopment; 31.10.2016