2016-10-31 4 views
15

Я прочитал несколько статей, которые предполагают, что расширение встроенных объектов в JavaScript - плохая идея. Скажем, например, добавить first функции Array ...Расширение встроенных типов JavaScript - это зло?

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?

+0

Нельзя защищать от столкновений. Лучшее, что вы можете сделать, - назвать вас сущностями как можно более уникальными и иметь как можно меньше глобальных элементов, например, с пространствами имен. И пересечь пальцы. –

+1

См. Также [Могу ли я безопасно расширять встроенные классы javascript?] (Http://stackoverflow.com/q/8262751/1048572), [Расширение встроенных встроенных функций] (http://perfectionkills.com/extending-native-builtins), [JavaScript: Какие опасности возникают при расширении Array.prototype?] (Http://stackoverflow.com/q/8859828/1048572), [Использует Prototype для расширения собственных объектов?] (Http://stackoverflow.com/q/10197174/1048572), [Почему расширение родных объектов является плохой практикой?] (Http://stackoverflow.com/q/14034180/1048572), [Повторное расширение родных прототипов после ECMAScript 5] (http://stackoverflow.com/q/11781878/1048572) и многое другое. – Bergi

+0

Подсказка: ваш метод 'first' уже доступен для массивов под именем [' find'] (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find) начиная с ES6. – Bergi

ответ

14

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

(function() { 
    let Collection = ... 
})(); 

Если и только если вам нужно определить глобальные объекты, например, потому что вы библиотека, и вы хотите использовать на 3-й стороны, вы должны определить имя, которое крайне маловероятно, конфликтовать, например, потому что это название компании:

new google.maps.Map(...) 

Каждый раз, когда вы определяете глобальный объект, который включает в себя новые методы существующих типов, вы рискуя какой-то другой библиотекой или каким-то будущим стандартом ECMAScript, пытающимся кооптировать имя в будущем.

6

Проблема с первым подходом заключается в том, что вы продлеваете прототип для всех сценариев на вашей странице.

Если вы хотите включить сторонний скрипт, который опирается на новый, родной ES-20xx Array.prototype.first метод, вы можете разбить его на свой код.

Второй пример - это действительно проблема, если бы вы использовали глобальные переменные (которые я надеюсь, что вы не делаете ...). Тогда может произойти одна и та же проблема.

Дело в том, spec authors are increasingly wary of not destroying the existing web, поэтому им нужно переименовать будущие функции, если слишком много существующих сайтов сломается.(И поэтому вы не должны создавать полисы для спецификаций веб-платформы, которые еще не завершены, BTW)

Проблема, безусловно, больше, если вы создаете библиотеку или фреймворк, который расширяет собственные объекты и используется 100 или 1000 тысяч сайтов. Именно тогда вы начинаете препятствовать процессу стандартов.

Если вы используете переменные внутри функции или области блока, и вы не полагаетесь на будущие функции, вы должны быть в порядке.

Функция Пример Объем:

(function() { 
    var Enumerable = {}; 
})(); 

Блок Объем пример:

{ 
    const Enumerable = {}; 
} 
2

ВНИМАНИЕ: Opinionated ответ.

Я не верю, что расширение объектов JavaScript является «злым». Очевидно, что есть опасности, и вам нужно быть в курсе этих (как вы видите). Мое единственное предостережение в этом утверждении состоит в том, что если вы это сделаете, вы должны ввести метод оповещения себя о конфликтах.

Другими словами, вместо того, чтобы просто определять функцию first в прототипе массива, вы должны сначала определить, определено ли Array.prototype.first, а если оно есть, а затем бросить (или предупредить себя об этом конфликте каким-либо другим способом). Только если он не определен, если вы позволите своему коду определить его или вы замените определение для всех.

+1

Инструмент (расширение объектов Javascript) по определению нравственно нейтрален. Престижность за это. – Mindwin

-1

Этот вопрос и эти ответы кажутся ориентированными на 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 на одной длинной странице, но если вы можете избежать этого, лучше стандартная практика.

+0

JavaScript всегда был языком OO, и наследование всегда было возможным. Классы ES6 являются просто более сильным синтаксисом. – deceze

+0

Правильно ... Итак, нижняя сторона - это потому, что альтернатива не имеет значения? Расширение - это точка моего ответа. Каждый ответ о прототипировании, как и вопрос, и мой ответ заключается в том, что они построили хороший синтаксис, чтобы вы могли расширить встроенный тип, не разрушая его. – EvSunWoodard

+1

Как этот класс 'myArray' равен любому из примеров OP? Преимущество первого примера кода OP состоит в том, что вы можете делать '[1, 2, 3] .first()', что, очевидно, невозможно с вашим кодом. Кроме того, ваш ответ не отвечает на реальный вопрос. – idmean

3

Опасность здесь гораздо более тонкая, чем столкновение с будущим стандартом ES20xx. По крайней мере, там вы можете удалить свой собственный код и обработать последствия любых возможных поведенческих различий.

Опасность состоит в том, что вы и кто-то другой решили расширить встроенный тип с непоследовательным поведением.

Рассмотрим что-то вроде этого

// In my team's codebase 
// Empty strings are empty 
String.prototype.isEmpty = function() { return this.length === 0; } 

// In my partner team's 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); } 
1

В качестве альтернатива всем удивительным ответам, которые были представлены, вы можете сделать то же самое, что и полиполны.

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

Вот пример:

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]; 
    }; 
})(); 

Это имеет benefict всех решений, предусмотренных ранее.

Если вы хотите уменьшить размер загруженного кода, можно динамически загружать необходимые сценарии.
Если создан новый Enumerable, вы можете использовать тот, который имеет браузер, или ваш собственный, если его нет.
Это экстремально зависит от вашей кодовой базы.

+0

Тем не менее, нет стандартного метода 'first', поэтому вы не можете * polyfill * его, и подход к его перезаписи только тогда, когда он не существует, не работает, если он позже стандартизован для чего-то другого. – Bergi

+0

@ Bergi Это недостаток:/Но с половиной достойного инструментария (консоль браузера) вы можете попытаться отследить все ошибки, которые происходят. В зависимости от того, насколько огромный код, это можно найти с умеренной сложностью. –

Смежные вопросы