Могу ли я создать подкласс DOM-класса? - PullRequest
7 голосов
/ 18 апреля 2011

Мне было интересно, смогу ли я создать подкласс HTMLDivElement. Вот так.

MyDivElement.prototype.pickColor = function()
{
    return this.picked;
}
function MyDivElement()
{
    this = new HTMLDivElement();
    this.picked = 'unknowd';
}
alert(this.picked); // print: 'unkowd'

Возможно ли (что-то вроде) это? Если нет, то как лучше всего этого добиться?

Ответы [ 4 ]

6 голосов
/ 06 октября 2012

В браузерах, где __proto__ открыт и изменчив, вы можете создавать подклассы DOM-элементов. Это выглядит так:

function CustomEl () {
    var el = document.createElement('div')
    el.__proto__ = CustomEl.prototype
    return el
}
CustomEl.prototype.__proto__ = HTMLDivElement.prototype

Я также играл с ним более подробно на jsFiddle . К сожалению, хотя IE и Opera не разрешают доступ __proto__ и не планируют делать это в будущем, насколько я знаю.

2 голосов
/ 18 апреля 2011

new HTMLDivElement(); выдает ошибку типа "Illegal constructor" в Chrome - так что это невозможно.

Обновление: я тестировал в других современных браузерах, и они выдают различные типы ошибок - но все они выдают.


На самом деле это будет работать:

function MyDivElement() {
    this.picked = 'unknowd';
}

MyDivElement.prototype = document.createElement('div');

var mydiv = new MyDivElement();

Но я не уверен, как вы могли бы использовать этот шаблон ...

1 голос
/ 20 апреля 2016

Я немного экспериментирую с этим. Большая сложность заключается в том, что вам нужен контекст документа для создания элемента. Вы можете использовать window.document по умолчанию, но это скучно.

Вот POC, над которым я работаю:

function CustomNode(type, parent) { 
    if (type instanceof Node) {
        // Decorate a preexisting node appropriately if called that way.
        if (arguments.length === 2 && type.ownerDocument !== parent) {
            // Import the node if it's not owned by the requested document.
            type = parent.importNode(type, true);
        }
        return Object.assign(type, this.__proto__);
    }
    //Normal flow, e.g., new CustomNode("div");
    var d = document;
    if (parent) {
        // Alt document flow, e.g., new CustomNode("div", xmlDoc);
        if (parent.nodeType === 9) {
            d = parent;
        } else {
            // Support for new CustomNode("div", parentElement);
            //  This doesn't append the element, just makes sure 
            //  the documents match
            d = parent.ownerDocument;
        }
    }
    var inst;
    // Creation flags
    if (type[0] === '#') { //text
        inst = d.createTextNode(type.substr(1));
    } else if (type[0] === '?') { //Processing instruction
        type = type.substr(1).split(' ');
        inst = d.createProcessingInstruction(type.shift(), type.join(' '));
    } else if (type[0] === '[') { // CDATA
        inst = d.createCDATASection(type.substr(1));
    } else if (type[0] === '/') { // Comment
        inst = d.createComment(type.substr(1));
    } else { //Everything else gets an element.
        inst = d.createElement(type);
    }
    // DE-COR-ATE
    Object.assign(inst, this.__proto__);
    return inst;
}
// Decorator for customized NodeLists; probably inefficient.  Decorates
//   contents with CustomNode
function CustomNodeList(list) {
    var Self = this.constructor,
        CNode = this.Node;
    return Object.assign([].map.call(list, function (node) {
        return new CNode(node);
    }), this.__proto__);
}
CustomNodeList.prototype = {
    // so we know how to wrap...
    Node: CustomNode,
    text: function () {
        return this[0].textContent;
    }
};


CustomNode.prototype = {
    // So we know how to decorate NodeLists
    NodeList: CustomNodeList,
    // So we know how to decorate Nodes
    Node: CustomNode,
    // Easy make-and-attach
    new: function (type, attach) {
        var CNode = this.Node;
        var ret = new CNode(type, this.ownerDocument);
        if (attach) {
            this.appendChild(ret);
        }
        return ret;
    },
    // NodeLists with ES5 iterators!
    find: function () {
        var CNodeList = this.NodeList;
        return new CNodeList(this.querySelectorAll.apply(this, arguments));
    },
    kids: function () {
        var CNodeList = this.NodeList;
        return new CNodeList(this.childNodes);
    }
};

Имейте в виду, это, вероятно, плохая идея: все в одной и той же области действия этих функций (включая сами элементы) будет никогда собирать мусор, так как GC в большинстве браузеров тупо туп, когда он доходит до элементов, ссылающихся на объекты. Я никогда не буду использовать его для производства по одной только этой причине: это прямая утечка памяти.

1 голос
/ 18 апреля 2011

В некоторых браузерах вы можете расширить прототип, в других - нет.Я позволю тебе угадать те, где ты не можешь.:-) Это на самом деле не то же самое, что расширение элемента DOM, но оно позволяет вам делать определенное подмножество вещей, для которых вы можете захотеть эту возможность.Дело в том, что элементы DOM на самом деле не являются сущностями JavaScript;это только симулякры, предоставляемые системой времени выполнения.(Возможно, когда-нибудь вся работа по jsdom действительно будет реализована.)

Хорошо, я расскажу вам о проблемных браузерах: IE это совсем не нравится.Однако другие делают.Если вы когда-нибудь заглядывали в библиотеку Prototype, вы все время будете сталкиваться с проявлением этого факта через неприятные раздражающие ошибки, связанные только с IE, когда вы забудете Prototype-ize ссылку на элемент DOM.

(IE9 может отличаться, но я в этом немного сомневаюсь.)

Это такая вещь, которую очень просто проверить на jsfiddle .

...