Измените прототипы всех возможных элементов DOM.

Обновлен заголовок, чтобы лучше отражать то, что я пытаюсь сделать.

Короче говоря, для разных элементов dom существуют разные конструкторы, и не все они имеют общий прототип. Я ищу способ добавить свойство функции к каждому элементу DOM, изменив эти прототипы, но я не уверен, как их найти.

Например, я мог бы сделать что-то вроде этого:

function enhanceDom (tagNames, methods) {
  var i=-1, tagName;
  while (tagName=tagNames[++i]) {
    var tag=document.createElement(tagName);
    if (!(tag && tag.constructor)) continue;
    for (var methodName in methods) {
      tag.constructor.prototype[methodName]=methods[methodName];
    }
  }
}

var thingsToEnhance = ['a','abbr','acronym','address'/* on and on... */];

enhance(thingsToEnhance, {
  doStuff : function(){
    /* ... */
  },
  doOtherStuff : function(){
    /* ... */
  } 
  /* ... */
});

Конечно, я хотел бы сделать это, не перечисляя каждый элемент html. Может ли кто-нибудь придумать лучший способ?

(исходный вопрос следует)

Цель – заставить getElementsByClassName работать на любом узле DOM в любом браузере.

Это было сделано раньше (вроде), но вот мой шанс.

У меня есть вопрос: есть ли хороший способ заставить это работать с динамически создаваемыми элементами? Кажется, что элементы HTML DOM не имеют общего предсказуемого прототипа, куда можно было бы добавить getElementsByClassName... Или я что-то упустил?

Вот что у меня есть на данный момент (редактировать — обновляется по мере обсуждения).

(function(){

  var fn = 'getElementsByClassName'; 
  // var fn = 'gEBCN'; // test

  if (typeof document[fn] != 'undefined') return;

  // This is the part I want to get rid of...
  // Can I add getByClass to a single prototype
  // somewhere below Object and be done with it?

  document[fn]=getByClass;
  withDescendants(document, function (node) {
    node[fn]=getByClass;
  });

  function withDescendants (node, callback, userdata) {
    var nodes = node.getElementsByTagName('*'), i=-1;
    while (node=nodes[++i]) {
      callback(node, userdata);
    }
    return userdata;
  }

  function getByClass (className) {
    return withDescendants(this, getMatches, {
      query:new RegExp('(^|\\s+)' + className + '($|\\s+)'), 
      found:[]
    }).found;
  }

  function getMatches (node, data) {
    if (node.className && node.className.match(data.query)) {
      data.found.push(node);
    }
  }

}());

Он хорошо работает с контентом, загруженным до загрузки скрипта, но новые динамически созданные элементы не получат метод getElementsByClassName. Любые предложения (кроме setInterval, пожалуйста)?


person Dagg Nabbit    schedule 06.09.2010    source источник
comment
Есть ли какая-то причина, по которой вы решили не использовать для этого такой фреймворк, как jQuery? Если это так, вы можете указать это в своем вопросе, иначе вы получите много пользы от jQuery! ответы.   -  person Greg Hewgill    schedule 06.09.2010
comment
Надеюсь, будет очевидно, что я не собираюсь использовать для этого jQuery. Скрещенные пальцы.   -  person Dagg Nabbit    schedule 06.09.2010
comment
@Greg Без кода JQuery в опубликованном коде и без тега JQuery, я думаю, достаточно ...   -  person Garis M Suero    schedule 06.09.2010
comment
Возможно, вам стоит взглянуть на Sizzle github.com/jeresig/sizzle/blob/ мастер/sizzle.js   -  person Caspar Kleijne    schedule 07.09.2010
comment
@no: я не понимаю, ваш код инкапсулирован в функцию, поэтому я полагаю, вы вызываете ее только ОДИН РАЗ. Очевидно, что к вновь созданному узлу DOM не будет применена функция getElementsByClassName, поскольку они создаются ПОСЛЕ того, как ваш код уже запущен. Вам нужно будет снова вызвать свой код. Более того, я действительно не понимаю, почему вы проходите через DOM вместо того, чтобы просто получать все элементы DOM с помощью getElementsByTagName('*'), этот последний даже намного быстрее, чем рекурсивный обход всех узлов DOM, как вы.   -  person Marco Demaio    schedule 07.09.2010
comment
@Marco Demaio: его нужно было бы вызывать только один раз, если бы он мог добавить «getElementsById» к прототипам всех конструкторов элементов dom. Тем не менее, кажется, что существует много конструкторов для всех различных элементов, и я предполагаю, что они будут несколько отличаться от браузера к браузеру. Я пытаюсь придумать хороший способ найти эти прототипы кросс-браузерным способом и дополнить их. Хорошая мысль о getElementsByTagName('*'), я никогда не думал об этом... на самом деле я мог бы использовать e.getElementsByTagName('*')||e.all и поддерживать все до IE4, я думаю :)   -  person Dagg Nabbit    schedule 07.09.2010
comment
@no: если вы планируете поддерживать что-либо до IE6, я ничем не могу вам помочь. Предположим, что в один прекрасный день вы передумаете и решите поддерживать только браузеры после IE6, другим исправлением вашего кода может быть использование простого атрибута className, это кросс-браузерный. Я не понимаю 4 ИЛИ, которые вы поставили: node['class'] || узел['имя_класса'] || ...   -  person Marco Demaio    schedule 07.09.2010
comment
@no: в вашем последнем комментарии я думаю, вы имели в виду «getElementsByClassName», а не «getElementById». Я думаю, что все равно нет возможности, вы могли бы посмотреть, как работает фреймворк, например, прототип.js. Он расширяет DOM, но я думаю, что и в прототипе.js вам нужно расширять вновь созданный узел DOM или использовать функции, предоставляемые самой структурой, для создания нового узла DOM, который будет создан в таком случае уже расширенным. И использование setInterval совершенно БЕЗУМНО!   -  person Marco Demaio    schedule 07.09.2010
comment
@Caspar Kleijne: Sizzle выглядит хорошо. Слай выглядит еще лучше. Я просто никогда не использовал запросы такой сложности; Обычно у меня достаточно контроля над разметкой, чтобы я мог присвоить соответствующему элементу идентификатор или класс. Я обдумываю идею создания библиотеки совместимости для определенных функций браузера; это своего рода пробный запуск. @Marco Demaio: я не уверен, что вы имеете в виду; Я никогда не упоминал getElementById.   -  person Dagg Nabbit    schedule 07.09.2010
comment
@Marco Demaio: также спасибо за совет по className, не знал, что он так широко поддерживается.   -  person Dagg Nabbit    schedule 07.09.2010
comment
(вставьте обязательное использование jQuery! ответ здесь)   -  person Pekka    schedule 07.09.2010
comment
Если все, что вам нужно, это получить по имени класса, а не по более сложной селекторной библиотеке, рассмотрите возможность повторного использования функции getElementsByClassname из YUI: developer.yahoo.com/yui/dom/#quickstart . Он не использует прототип и, таким образом, избегает всей проблемы доступности для динамически создаваемых элементов или нет. Это может быть менее привлекательно, чем использование прототипа, но выполняет свою работу :)   -  person Ben Regenspan    schedule 07.09.2010
comment
Я думаю, что мой исходный пост был неясен. Обновлено...   -  person Dagg Nabbit    schedule 07.09.2010
comment
Вы смотрели, что делают такие фреймворки, как MooTools? MooTools добавляет функции к каждому элементу. mootools.net/docs/core/Element/Element   -  person Daniel Lo Nigro    schedule 07.09.2010


Ответы (2)


Я думаю, что то, что вы хотите, может быть достигнуто путем создания прототипа Element интерфейс, например

Element.prototype.getElementsByClassName = function() {
    /* do some magic stuff */
};

но не делайте этого. Он не работает надежно во всех основных браузерах.

То, что вы делаете в примере в своем вопросе, тоже не рекомендуется. На самом деле вы расширяете хост-объекты. Еще раз, пожалуйста, не делайте этого.

Вы попадете именно в те ловушки, с которыми столкнулся Prototype.

Я не хочу просто копировать статью Кангакса, поэтому, пожалуйста, прочитайте Что не так с расширением DOM.

Почему вы хотите это в первую очередь? Какова ваша цель?

person Marcel Korpel    schedule 06.09.2010
comment
Отличная статья. Я знаю о некоторых опасностях изменения хост-объектов. Общая цель этого эксперимента — создать слой совместимости, чтобы все сценарии можно было тестировать в одном браузере и работать в разных браузерах без изменений. Не создавать новую библиотеку, просто расширить браузеры, у которых нет определенных функций, чтобы все они работали так же, как браузер, в котором тестируют наши разработчики. Она будет использоваться только для внутренних вещей (некоторые из которых уже накопились), не как библиотека GP. - person Dagg Nabbit; 07.09.2010
comment
Element или HTMLElement, вероятно, то, что я искал. Плохо насчет кросс-браузерной поддержки. Отметив это правильно. - person Dagg Nabbit; 07.09.2010

Кажется, это работает, но это некрасиво. Интересно, работает ли он в IE?

(function(){

  enhanceDom('a abbr acronym address applet area b base basefont bdo big blockquote body br button caption center cite code col colgroup dd del dfn dir div dl dt em fieldset font form frame frameset h1 h2 h3 h4 h5 h6 head hr html i iframe img input ins isindex kbd label legend li link map menu meta noframes noscript object ol optgroup option p param pre q s samp script select small span strike strong style sub sup table tbody td textarea tfoot th thead title tr tt u ul var'
  ,{
    getElementsByClassName : getByClass
    /* , ... */
  });

  function enhanceDom (tagNames, methods) {
    var i=-1, tagName;
    if (tagNames==''+tagNames) {
      tagNames=tagNames.split(' ');
    }
    for (var methodName in methods) {
      setIfMissing(document, methodName, methods[methodName]);
      while (tagName=tagNames[++i]) {
        var tag=document.createElement(tagName);
        if (tag || !tag.constructor) continue;
        var proto=tag.constructor.prototype;
        setIfMissing(proto, methodName, methods[methodName]);
      }
    }
  }

  function setIfMissing (obj, prop, val) {
    if (typeof obj[prop] == 'undefined') {
      obj[prop]=val;
    }
  }

  function withDescendants (node, callback, userdata) {
    var nodes=node.getElementsByTagName('*'), i=-1;
    while (node=nodes[++i]) {
      callback(node, userdata);
    }
    return userdata;
  }

  function getByClass (className) {
    return withDescendants(this, getMatches, {
      query:new RegExp('(^|\\s+)' + className + '($|\\s+)'), 
      found:[]
    }).found;
  }

  function getMatches (node, data) {
    if (node.className && node.className.match(data.query)) {
      data.found.push(node);
    }
  }

}());
person Dagg Nabbit    schedule 07.09.2010