Object.prototype - это Verboten?

СПРАВКИ:

Хорошо, прошло много времени с тех пор, как я задал этот вопрос. Как обычно, я пошел и добавил Object.prototype во всяком случае, несмотря на все действительные аргументы против этого, приведенные как здесь, так и в других местах в Интернете. Я думаю, что я просто такой упрямый придурок.

Я попытался придумать убедительный способ не допустить, чтобы новый метод испортил любое ожидаемое поведение, что оказалось очень трудным, но информативным занятием.
Я узнал очень много о JavaScript. Ни в коем случае я не буду пытаться делать что-то настолько дерзкое, как возиться с родными прототипами (кроме String.prototype.trim для IE < 9).

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

Изучив этот прототип, я лучше понял саму модель. Я рассматривал прототипы как некую форму гибкого традиционного абстрактного класса, заставляя меня цепляться за традиционное ООП мышление. Эта точка зрения не совсем оправдывает прототип модели. Дуглас Крокфорд написал об этой ловушке, к сожалению, розовый фон не позволил мне прочитать статью полностью.

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


Я знаю, что собираюсь задать вопрос, который существует уже довольно давно, но: почему объектный прототип считается запрещенным? Он есть, и его можно все же увеличить, как и любой другой прототип. Почему бы вам не воспользоваться этим. На мой взгляд, пока вы знаете, что делаете, нет причин избегать прототипа Object.
Возьмите этот метод, например:

if (!Object.prototype.getProperties)
{
    Object.prototype.getProperties = function(f)
    {
        "use strict";
        var i,ret;
        f = f || false;
        ret = [];
        for (i in this)
        {
            if (this.hasOwnProperty(i))
            {
                if (f === false && typeof this[i] === 'function')
                {
                    continue;
                }
                ret.push(i);
            }
        }
        return ret;
    };
}

По сути, это тот же старый for...in цикл, который вы либо сохраните в функции, либо будете писать снова и снова. Я знаю, что он будет добавлен ко всем объектам, и так как почти каждая цепочка наследования в JavaScript может быть прослежена до Object.prototypeНо в моем сценарии я считаю это меньшим из двух зол.

Возможно, кто-то мог бы лучше сказать мне, где я неправ, чем этот парень, среди других.
Ища причины, по которым люди давали НЕ трогать ObjectПрототип, одно всплывало: оно ломает for..in зацикливание, но опять же: многие фреймворки тоже делают, не говоря уже о ваших собственных цепочках наследования. Поэтому плохая практика не включать .hasOwnProperty проверять при просмотре свойств объекта, на мой взгляд.

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

Дело в том, что я не вижу проблемы с приведенным выше кодом. Мне это нравится, очень много им пользуюсь и пока что ни разу не подвело. Я даже думаю о присоединении еще нескольких функций к прототипу Object. Если кто-то не может сказать мне, почему я не должен, то есть.

3 ответа

Решение

Дело в том, что это хорошо, если вы знаете, что делаете и каковы затраты. Но это большое "если". Некоторые примеры затрат:

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

    var obj = {"a": 1, "b": 2};
    var name;
    for (name in obj) {
        console.log(name);
    }
    

    ... с подавляющим соглашением о том, что будут отображаться только "a" и "b", а не "getProperties".

  • Любой, кто работает над кодом, должен быть обучен тому факту, что это соглашение (выше) не соблюдается.

Вы можете смягчить вышеуказанное, используя Object.defineProperty (и аналогичные), если они поддерживаются, но имейте в виду, что даже в 2014 году браузеры, такие как IE8, которые не поддерживают его должным образом, по-прежнему широко используются (хотя мы можем надеяться, что это быстро изменится теперь, когда XP официально EOL'd). Это потому, что с помощью Object.defineProperty, вы можете добавить не перечисляемые свойства (те, которые не отображаются в for-in циклы), и поэтому у вас будет гораздо меньше проблем (в этот момент вас больше всего волнуют конфликты имен), но это работает только на системах, которые правильно реализуют Object.defineProperty (и правильная реализация не может быть "переложена").

В вашем примере я бы не добавил getProperties в Object.prototype; Я бы добавил это к Object и принять объект в качестве аргумента, как ES5 делает для getPrototypeOf и тому подобное.

Имейте в виду, что библиотека Prototype получает много возможностей для расширения Array.prototype из-за того, как это влияет for..in петли. И это только Array s (который вы не должны использовать for..in в любом случае (если вы не используете hasOwnProperty охранник и вполне вероятно String(Number(name)) === name также).

... если люди V8 делают это, кто я такой, чтобы сказать, что они не правы?

На V8 вы можете положиться Object.defineProperty потому что V8 - полностью ES5-совместимый двигатель.

Обратите внимание, что даже когда свойства не перечисляются, возникают проблемы. Несколько лет назад Прототип (косвенно) определил filter функция на Array.prototype, И он делает то, что вы ожидаете: вызывает функцию итератора и создает новый массив на основе элементов, выбранных функцией. Затем ECMAScript5 пришел и определил Array.prototype.filter делать то же самое. Но есть загвоздка: во многом то же самое. В частности, сигнатура вызываемых функций итератора отличается (ECMAScript5 содержит аргумент, которого не было в Prototype). Это могло бы быть намного хуже (и я подозреваю - но я не могу доказать - что TC39 знал о Prototype и намеренно избегал слишком большого конфликта с ним).

Итак: если вы собираетесь это сделать, помните о рисках и затратах. Ужасные, крайние ошибки, с которыми вы можете столкнуться в результате попытки использовать готовые библиотеки, могут действительно стоить вам времени...

Если бы фреймворки и библиотеки обычно делали то, что вы предлагаете, очень скоро случится так, что две разные фреймворки определят две разные функциональности как один и тот же метод Object (или же Array, Number... или любой из существующих прототипов объекта). Поэтому лучше добавить такую ​​новую функциональность в собственное пространство имен.

Например... представьте, у вас будет библиотека, которая сериализует объекты в json, и библиотека, которая сериализует их в XML, и обе будут определять их функциональность как

Object.prototype.serialize = function() { ... }

и вы сможете использовать только тот, который был определен позже. Так что лучше, если они этого не сделают, а вместо этого

JSONSerializingLibrary.seralize = function(obj) { ... }
XMLSerializingLibrary.seralize = function(obj) { ... }

Также может случиться так, что новая функциональность будет определена в новом стандарте ECMAscript или добавлена ​​поставщиком браузера. Так что представьте, что ваши браузеры также добавят serialize функция. Это снова вызовет конфликт с библиотеками, которые определили ту же функцию. Даже если бы функциональность библиотек была такой же, как и встроенная в браузер, интерпретируемые функции сценария переопределяли бы встроенную функцию, что на самом деле было бы быстрее.

См. http://www.websanova.com/tutorials/javascript/extending-javascript-the-right-way

Что касается некоторых, но не всех, возражений. Возражение против различных библиотек, создающих конфликтующие методы, может быть смягчено, вызывая исключение, если доменный метод уже присутствует в Object.prototype. Это, по крайней мере, даст предупреждение, когда произойдет это нежелательное событие.

Вдохновленный этим постом, я разработал следующее, что также доступно в комментариях на цитируемой странице.

!Object.implement && Object.defineProperty (Object.prototype, 'implement', {
  // based on http://www.websanova.com/tutorials/javascript/extending-javascript-the-right-way
  value: function (mthd, fnc, cfg) { // adds fnc to prototype under name mthd
      if (typeof mthd === 'function') { // find mthd from function source
        cfg = fnc, fnc = mthd;
        (mthd = (fnc.toString ().match (/^function\s+([a-z$_][\w$]+)/i) || [0, ''])[1]);
      }
      mthd && !this.prototype[mthd] && 
        Object.defineProperty (this.prototype, mthd, {configurable: !!cfg, value: fnc, enumerable: false});
    }
});

Object.implement (function forEach (fnc) {
  for (var key in this)
    this.hasOwnProperty (key) && fnc (this[key], key, this);
});

Я использовал это прежде всего для добавления стандартной определенной функции к реализации, которая их не поддерживает.

Другие вопросы по тегам