Object.prototype - Verboten?
СПРАВКИ:
Хорошо, прошло некоторое время с тех пор, как я задал этот вопрос. Как обычно, я все равно пошел и добавил Object.prototype
, несмотря на все допустимые аргументы против него, которые даны как здесь, так и в другом месте в Интернете. Наверное, я просто такой упрямый рывок.
Я пытался придумать неоспоримый способ предотвратить новый метод из-за любого ожидаемого поведения, которое оказалось очень сложным, но информативным.
Я многое узнал о JavaScript. Не в последнюю очередь, я не буду пытаться ничего такого же дерзкого, как возиться с родными прототипами (кроме String.prototype.trim
для IE < 9).
В этом конкретном случае я не использую libs, поэтому конфликты не были моей главной задачей. Но, вырвавшись немного глубже в возможные неудачи, когда вы играете с родными прототипами, я вряд ли попытаюсь использовать этот код в сочетании с любой lib.
Изучая этот прототипный подход, я пришел к лучшему пониманию самой модели. Я рассматривал прототипы как некоторую форму гибкого традиционного абстрактного класса, заставляя меня цепляться за традиционное мышление ООП. Эта точка зрения на самом деле не делает образцовой модели прототипа. Дуглас Крокфорд написал об этой ловушке, к сожалению, розовый фон не позволил мне прочитать полную статью.
Я решил обновить этот вопрос вовремя, люди, которые читают это, испытывают соблазн увидеть самих себя. Все, что я могу сказать, это: во что бы то ни стало. Надеюсь, вы узнаете несколько опрятных вещей, как я, прежде чем решиться отказаться от этой довольно глупой идеи. Простая функция может работать так же хорошо или даже лучше, особенно в этом случае. В конце концов, настоящая красота заключается в том, что добавив всего 3 строки кода, вы можете использовать ту же самую функцию для создания прототипов конкретных объектов все равно.
Я знаю, что я собираюсь задать вопрос, который был вокруг довольно долгое время, но: Почему Object.prototype считается отключенным? Это там, и это может быть дополнено все равно, как и любой другой прототип. Почему бы вам не воспользоваться этим. На мой взгляд, до тех пор, пока вы знаете, что вы делаете, нет причин избегать прототипа объекта.
Возьмите этот метод, например:
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
, который вы либо сохранили бы в функции, либо напишите снова и снова. Я знаю, что он будет добавлен к объектам all, и поскольку почти каждая цепочка наследования в JavaScript можно проследить до Object.prototype
, но в моем script я считаю это меньшим из двух зол,
Возможно, кто-то мог бы лучше сказать, где я ошибаюсь, чем этот парень и другие.
При поиске причин, по которым люди не прикасались к прототипу Object
, одна вещь продолжала возникать: она прерывает цикл for..in
, но опять же: многие фреймворки тоже не говоря уже о ваших собственных цепочки наследования. Поэтому, по моему мнению, неправильная практика не включать проверку .hasOwnProperty
при переходе через свойства объекта.
Я также нашел этот довольно интересным. Опять же: один комментарий совершенно недвусмыслен: расширение родных прототипов - это плохая практика, но если люди V8 это делают, кто я такой, чтобы сказать, что они неправы?
Я знаю, этот аргумент не совсем складывается.
Дело в том, что я не вижу проблемы с указанным выше кодом. Мне это нравится, его много использовать, и до сих пор он меня не подвел. Я даже подумываю о присоединении еще нескольких функций к прототипу объекта. Если кто-нибудь не скажет мне, почему я не должен, то есть.
Ответы
Ответ 1
Дело в том, что это прекрасно, пока вы знаете, что делаете и каковы издержки. Но это большой "if". Некоторые примеры затрат:
-
Вам нужно провести тестирование обширное с любой библиотекой, которую вы хотите использовать, с средой, которая увеличивает 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
(который вы не должны использовать for..in
в любом случае (если вы не используете защитник hasOwnProperty
и, скорее всего, String(Number(name)) === name
).
... если люди V8 делают это, кто я такой, чтобы сказать, что они неправы?
На V8 вы можете положиться на Object.defineProperty
, потому что V8 - полностью совместимый с ES5 движок.
Обратите внимание, что даже если свойства не перечислимы, возникают проблемы. Несколько лет назад прототип (косвенно) определил функцию filter
на Array.prototype
. И он делает то, что вы ожидаете: вызывает функцию итератора и создает новый массив на основе элементов, которые выбирает функция. Затем появился ECMAScript5 и определил Array.prototype.filter
, чтобы сделать то же самое. Но там руб: Много то же самое. В частности, подпись функций-итераторов, вызываемых по-разному (ECMAScript5 содержит аргумент, который не использовал Prototype). Это могло быть намного хуже, чем это (и я подозреваю, но не могу доказать, что TC39 знал о прототипе и намеренно избегал слишком большого конфликта с ним).
Итак: Если вы собираетесь это сделать, будьте в курсе рисков и затрат. Уродливые, крайние ошибки, с которыми вы можете столкнуться в результате попыток использовать готовые библиотеки, действительно могут стоить вам времени...
Ответ 2
Если фреймворки и библиотеки, как правило, делали то, что вы предлагаете, очень скоро произойдет, что две разные структуры определят две разные функции как один и тот же метод Object
(или Array
, Number
... или любой существующих прототипов объектов). Поэтому лучше добавить такие новые функции в свое собственное пространство имен.
Например, представьте себе, что у вас будет библиотека, которая будет сериализовать объекты в json и библиотеку, которая будет сериализовать их в XML, и оба будут определять их функциональность как
Object.prototype.serialize = function() { ... }
и вы сможете использовать только тот, который был определен позже. Так что лучше, если они этого не сделают, а вместо этого
JSONSerializingLibrary.seralize = function(obj) { ... }
XMLSerializingLibrary.seralize = function(obj) { ... }
Также может случиться так, что новая функциональность определена в новом стандарте ECMAscript или добавлена поставщиком браузера. Представьте себе, что ваши браузеры также добавят функцию serialize
. Это снова вызовет конфликт с библиотеками, которые определяют одну и ту же функцию. Даже если функциональность библиотек была такой же, как и встроенная в браузер, интерпретируемые функции script переопределили бы нативную функцию, которая, по сути, была бы быстрее.
Ответ 3
См. 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);
});
Я использовал это прежде всего, чтобы добавить стандартную определенную функцию в реализацию, которая их не поддерживает.