Расширение встроенных типов JavaScript - это зло?
Я прочитал несколько статей, которые предполагают, что расширение встроенных объектов в JavaScript - плохая идея. Скажем, например, я добавляю функцию first
к Array
...
Array.prototype.first = function(fn) {
return this.filter(fn)[0];
};
Отлично, теперь я могу получить первый элемент, основанный на предикате. Но что происходит, когда ECMAScript-20xx решает добавить сначала в спецификацию и реализовать ее по-другому? - ну, вдруг, мой код предполагает нестандартную реализацию, разработчики теряют веру и т.д.
Итак, я решил создать свой собственный тип...
var Enumerable = (function () {
function Enumerable(array) {
this.array = array;
}
Enumerable.prototype.first = function (fn) {
return this.array.filter(fn)[0];
};
return Enumerable;
}());
Итак, теперь я могу передать массив в новый Enumerable и сначала вызвать экземпляр Enumerable. Большой! Я уважал спецификацию ECMAScript-20xx, и я все еще могу делать то, что хочу.
Затем выдается спецификация ES20XX + 1, которая вводит тип Enumerable
, который даже не имеет первого метода. Что происходит сейчас?
Суть этой статьи сводится к этому; Насколько тяжело распространять встроенные типы и как мы можем избежать столкновения реализации в будущем?
Примечание. Использование пространств имен может быть одним из способов справиться с этим, но опять же, это не так!
var Collection = {
Enumerable: function () { ... }
};
Что происходит, когда спецификация ECMAScript вводит Collection
?
Ответы
Ответ 1
Именно поэтому вы должны попытаться как можно меньше загрязнить глобальное пространство имен. Единственный способ полностью избежать любых столкновений - это определить все в рамках IIFE:
(function () {
let Collection = ...
})();
Если вам нужно определить глобальные объекты, например, потому что вы являетесь библиотекой и хотите, чтобы ее использовали третьи стороны, вы должны определить имя, которое крайне маловероятно, чтобы столкнуться, например, потому что это ваша компания имя:
new google.maps.Map(...)
Каждый раз, когда вы определяете глобальный объект, который включает в себя новые методы для существующих типов, вы рискуете какой-то другой библиотекой или каким-то будущим стандартом ECMAScript, пытающимся в будущем кооптировать имя.
Ответ 2
Проблема с первым подходом заключается в том, что вы расширяете прототип для всех скриптов на своей странице.
Если вы включили сторонний script, который опирается на новый, собственный ES-20xx Array.prototype.first
метод, вы можете разбить его на свой код.
Второй пример - это действительно проблема, если вы должны использовать глобальные переменные (которые, надеюсь, вы не делаете...). Тогда может возникнуть одна и та же проблема.
Дело в том, что авторы все чаще опасаются не уничтожать существующую сеть, поэтому им нужно переименовать будущие функции, если слишком много существующих сайтов сломается. (И именно поэтому вы не должны создавать полисы для спецификаций веб-платформы, которые еще не завершены, BTW)
Проблема, безусловно, больше, если вы создаете библиотеку или фреймворк, который расширяет собственные объекты и используется 100 или 1000-ю сайтами. Это, когда вы начинаете препятствовать процессу стандартов.
Если вы используете переменные внутри функции или области блока, и вы не полагаетесь на будущие функции, вы должны быть в порядке.
Пример области действия функции:
(function() {
var Enumerable = {};
})();
Пример области видимости блока:
{
const Enumerable = {};
}
Ответ 3
Опасность здесь намного более тонкая, чем столкновение с будущим стандартом ES20xx. По крайней мере, там вы можете удалить свой собственный код и обработать последствия любых возможных поведенческих различий.
Опасность состоит в том, что вы и кто-то другой решили расширить встроенный тип с непоследовательным поведением.
Рассмотрим что-то вроде этого
// In my team codebase
// Empty strings are empty
String.prototype.isEmpty = function() { return this.length === 0; }
// In my partner team codebase
// Strings consisting any non-whitespace chars are non-empty
String.prototype.isEmpty = function() { return this.trim().length === 0; }
У вас есть два одинаково допустимых определения isEmpty
и последний в выигрыше. Представьте себе, что у вас будет боль, когда у вас пройдет год, когда проходят тесты вашего подразделения, пройдут тесты модулей вашего партнера, но вы не сможете объединить свои кодовые базы на одной веб-странице, не получая действительно странных сбоев, в зависимости от того, чья библиотека загружается последний. Это кошмар ремонтируемости.
Затем вы попадаете в комнату со своей командой-партнером и в течение шести часов рассуждаете о том, чье определение isEmpty
является "правильным", будет принято произвольное решение, и один из вас посмотрит на каждый отдельный экземпляр isEmpty
в вашей кодовой базе, чтобы определить, как заставить его работать с новой функцией. И вы, вероятно, представите некоторые новые ошибки в этом процессе.
Затем повторите весь этот процесс, когда вы обнаружите, что вы оба хотели функцию toInteger
для чисел, но не думали проводить общее собрание компаний о том, что делать с отрицательными цифрами.
// -3.1 --> -4
Number.prototype.toInteger = function() { return Math.floor(this); }
// -3.1 --> -3
Number.prototype.toInteger = function() { return Math.trunc(this); }
Ответ 4
ПРЕДУПРЕЖДЕНИЕ: Мнение.
Я не считаю, что расширение объектов JavaScript является "злым". Очевидно, что есть опасности, и вам нужно быть в курсе этих (как вы видите). Мое единственное предостережение в этом утверждении состоит в том, что если вы это сделаете, вы должны ввести метод оповещения себя о конфликтах.
Другими словами, вместо того, чтобы просто определять функцию first
в прототипе массива, вы должны сначала увидеть, определено ли Array.prototype.first
, а если оно, а затем бросить (или предупредить себя об этом конфликте каким-либо другим способом), Только если он не определен, если вы разрешите коду определять его или вы замените определение для всех.
Ответ 5
В качестве альтернативы всем удивительным ответам, которые были представлены, вы можете сделать то же самое, что и polyfills.
Обычно, когда кто-то пишет что-то, что беспорядок с прототипами, один проверяет, определено ли уже значение.
Вот пример:
if(!Array.prototype.first) {
Array.prototype.first = function(fn) {
return this.filter(fn)[0];
};
}
Это отлично подходит для небольших фрагментов кода, но может вызвать проблемы с большими библиотеками.
Реализация анонимной функции самосознания может помочь в этом, например:
(function(){
// Leave if it is already defined
if(Array.prototype.first) {
return;
}
Array.prototype.first = function(fn) {
return this.filter(fn)[0];
};
})();
Это преимущество всех предлагаемых решений.
Если вы хотите уменьшить размер загруженного кода, можно динамически загружать необходимые сценарии.
Если создается новый Enumerable
, вы можете использовать тот, который имеет браузер, или ваш собственный, если его нет.
Это крайне зависит от вашей кодовой базы.
Ответ 6
Этот вопрос и эти ответы кажутся ориентированными на ECMA5, а не 6. Использование прототипов довольно бесполезно, когда ECMA6 приносит классы с ним. Существует не так много огромных причин не добавлять к прототипу встроенного, но в большей степени это скорее случай, "если вы можете избежать этого, вы должны".
Во-первых, вы можете сломать старые браузеры при добавлении к встроенному типу, также вы можете сломать формат "for (i in array)" из-за того, как для... in вызывается базовый уровень, он может иметь неожиданные результаты.
Основная причина, по моему мнению, заключается в том, что в ECMA6 есть очень хорошая альтернатива, которая является подклассом. Вместо использования какого-либо прототипа просто используйте это:
class myArray extends array {
constructor() { ... }
getFirst() {
return this[0];
}
// Or make a getter
get first() {
return this[0];
}
}
Вы можете поместить любую функцию в геттер, но она по-прежнему является независимым кодом, который не вторгается в будущие пространства имен. Но, поскольку вы являетесь подразделением, вам не нужно переписывать массив. Таким образом, это меньше, чем чистое зло, но более того, он лучше кодирует, чтобы не делать этого, почти так же, как не обязательно зло писать весь ваш javascript на одной длинной странице, но если вы можете избежать этого, лучше стандартная практика.