Каждый лидер мнения JS говорит, что расширение нативных объектов - плохая практика. Но почему? Получим ли мы успех в исполнении? Боятся ли они, что кто-то сделает это «неправильно» и добавит перечислимые типы Object
, практически уничтожив все циклы на любом объекте?
Возьмите TJ Holowaychuk «S should.js , например. Он добавляет простой поглотитель к Object
и все работает отлично ( источник ).
Object.defineProperty(Object.prototype, 'should', {
set: function(){},
get: function(){
return new Assertion(Object(this).valueOf());
},
configurable: true
});
Это действительно имеет смысл. Например, можно было продлить Array
.
Array.defineProperty(Array.prototype, "remove", {
set: function(){},
get: function(){
return removeArrayElement.bind(this);
}
});
var arr = [0, 1, 2, 3, 4];
arr.remove(3);
Есть ли аргументы против расширения собственных типов?
javascript
prototype
prototypal-inheritance
buschtoens
источник
источник
Ответы:
Когда вы расширяете объект, вы меняете его поведение.
Можно изменить поведение объекта, который будет использоваться только вашим собственным кодом. Но когда вы меняете поведение чего-то, что также используется другим кодом, есть риск сломать этот другой код.
Когда дело доходит до добавления методов к классам объектов и массивов в javascript, риск взлома очень высок из-за того, как работает javascript. Многолетний опыт научил меня, что подобные вещи вызывают всевозможные ужасные ошибки в javascript.
Если вам нужно настраиваемое поведение, гораздо лучше определить свой собственный класс (возможно, подкласс), а не изменять собственный. Так вы вообще ничего не сломаете.
Возможность изменять способ работы класса без создания подклассов - важная особенность любого хорошего языка программирования, но ее следует использовать редко и с осторожностью.
источник
.stgringify()
будет считаться безопасным?stringify()
метод с другим поведением? На самом деле это не то, что вам следует делать в повседневном программировании ... нет, если все, что вы хотите сделать, это сохранить несколько символов кода здесь и там. Лучше определить свой собственный класс или функцию, которая принимает любой ввод и преобразует его в строки. Большинство браузеров определяютJSON.stringify()
, лучше всего было бы проверить, существует ли он, а если нет, определить его самостоятельно.someError.stringify()
большеerrors.stringify(someError)
. Это просто и идеально соответствует концепции js. Я делаю то, что специально привязано к определенному объекту ErrorObject.Нет ощутимого недостатка вроде снижения производительности. По крайней мере, никто об этом не упомянул. Так что это вопрос личных предпочтений и опыта.
Главный аргумент за: он выглядит лучше и интуитивно понятнее: синтаксический сахар. Это функция, специфичная для типа / экземпляра, поэтому она должна быть специально привязана к этому типу / экземпляру.
Главный аргумент против: код может мешать. Если библиотека A добавляет функцию, она может перезаписать функцию библиотеки B. Это может очень легко взломать код.
Оба правы. Когда вы полагаетесь на две библиотеки, которые напрямую изменяют ваши типы, вы, скорее всего, получите неработающий код, поскольку ожидаемая функциональность, вероятно, не такая же. Я полностью согласен с этим. Макро-библиотеки не должны манипулировать собственными типами. В противном случае вы, как разработчик, никогда не узнаете, что на самом деле происходит за кулисами.
Вот почему я не люблю такие библиотеки, как jQuery, подчеркивание и т. Д. Не поймите меня неправильно; они абсолютно хорошо запрограммированы и работают как шарм, но они большие . Вы используете только 10% из них, а понимаете около 1%.
Вот почему я предпочитаю атомистический подход , когда вам нужно только то, что вам действительно нужно. Таким образом, вы всегда знаете, что происходит. Микробиблиотеки делают только то, что вы хотите, чтобы они не мешали. В контексте того, что конечный пользователь знает, какие функции добавляются, расширение собственных типов можно считать безопасным.
TL; DR В случае сомнений не расширяйте собственные типы. Расширяйте собственный тип только в том случае, если вы на 100% уверены, что конечный пользователь будет знать об этом поведении и захочет его. В не случае манипулировать существующие функции основного языка, поскольку это нарушило бы существующий интерфейс.
Если вы решили расширить тип, используйте
Object.defineProperty(obj, prop, desc)
; если не можете , используйте типprototype
.Первоначально я задал этот вопрос, потому что хотел, чтобы
Error
s можно было отправлять через JSON. Итак, мне нужен был способ их связать.error.stringify()
чувствовал себя лучше, чемerrorlib.stringify(error)
; как предполагает вторая конструкция, я действую над собой,errorlib
а не надerror
собой.источник
MyError extends Error
него может бытьstringify
метод без столкновения с другими подклассами. Вам все равно придется обрабатывать ошибки, не сгенерированные вашим собственным кодом, поэтому он может быть менее полезен,Error
чем с другими.На мой взгляд, это плохая практика. Основная причина - интеграция. Цитата из should.js docs:
Ну откуда автору знать? Что, если мой фреймворк насмешек сделает то же самое? Что, если моя библиотека обещаний делает то же самое?
Если вы делаете это в своем собственном проекте, тогда ничего страшного. Но для библиотеки это плохой дизайн. Underscore.js - пример того, как все сделано правильно:
источник
_(arr).flatten()
пример действительно убедил меня не расширять собственные объекты. Моя личная причина для этого была чисто синтаксической. Но это удовлетворяет мое эстетическое чувство :) Даже использование более обычного имени функции, например,foo(native).coolStuff()
преобразование его в какой-то "расширенный" объект, выглядит великолепно синтаксически. Так что спасибо за это!Перезапись уже созданных методов создает больше проблем, чем решает, поэтому во многих языках программирования принято избегать такой практики. Как разработчики узнают, что функция была изменена?
В этом случае мы не перезаписываем какой-либо известный базовый метод JS, но мы расширяем String. В одном из аргументов в этом посте упоминалось, как новый разработчик узнает, является ли этот метод частью основного JS или где найти документы? Что произойдет, если основной объект JS String получит метод с именем capitalize ?
Что, если вместо добавления имен, которые могут конфликтовать с другими библиотеками, вы использовали модификатор компании / приложения, понятный всем разработчикам?
Если вы продолжите расширять другие объекты, все разработчики столкнутся с ними в дикой природе (в данном случае) с нами , что уведомит их о том, что это расширение, специфичное для компании / приложения.
Это не устраняет коллизии имен, но снижает вероятность. Если вы решите, что расширение основных объектов JS предназначено для вас и / или вашей команды, возможно, это для вас.
источник
Расширять прототипы встроенных модулей - действительно плохая идея.Однако ES2015 представил новую технику, которую можно использовать для получения желаемого поведения:
использующий
WeakMap
s для связывания типов со встроенными прототипамиСледующая реализация расширяет
Number
иArray
прототипов , не касаясь их вообще:Как видите, с помощью этой техники можно расширить даже примитивные прототипы. Он использует структуру карты и
Object
идентичность для связывания типов со встроенными прототипами.Мой пример включает
reduce
функцию, которая ожидает только вArray
качестве единственного аргумента, поскольку она может извлекать информацию о том, как создать пустой аккумулятор и как объединить элементы с этим аккумулятором из элементов самого массива.Обратите внимание, что я мог бы использовать обычный
Map
тип, поскольку слабые ссылки не имеют смысла, когда они просто представляют встроенные прототипы, которые никогда не собираются сборщиком мусора. Однако, aWeakMap
не повторяется и не может быть проверен, если у вас нет правильного ключа. Это желательная функция, поскольку я хочу избежать любой формы отражения типа.источник
xs[0]
пустыми массивами.type(undefined).monoid ...
Object.prototype
таким образом, не может быть вызванArray
), а синтаксис значительно длиннее / уродливее, чем если бы вы действительно расширили прототип. Почти всегда было бы проще просто создавать служебные классы с помощью статических методов; Единственное преимущество этого подхода - ограниченная поддержка полиморфизма.Еще одна причина, по которой вы не должны расширять собственные объекты:
Мы используем Magento, который использует prototype.js и расширяет множество вещей на собственные объекты. Это отлично работает, пока вы не решите добавить новые функции, и здесь начинаются большие проблемы.
Мы представили веб-компоненты на одной из наших страниц, поэтому webcomponents-lite.js решает заменить весь (собственный) объект события в IE (почему?). Это, конечно, нарушает работу prototype.js, что, в свою очередь, нарушает работу Magento. (пока вы не найдете проблему, вы можете потратить много часов, чтобы найти ее)
Если тебе нравятся неприятности, продолжай!
источник
Я вижу три причины не делать этого ( по крайней мере, из приложения ), только две из которых рассматриваются в существующих ответах здесь:
Object.defineProperty
, которое по умолчанию создает неперечислимые свойства.Пункт 3, пожалуй, самый важный. С помощью тестирования вы можете убедиться, что расширения вашего прототипа не вызывают конфликтов с используемыми вами библиотеками, потому что вы сами решаете, какие библиотеки использовать. То же самое нельзя сказать о нативных объектах, если предположить, что ваш код работает в браузере. Если вы определитесь
Array.prototype.swizzle(foo, bar)
сегодня, а завтра Google добавитArray.prototype.swizzle(bar, foo)
Chrome, вы можете столкнуться с недоумением коллег, которые задаются вопросом, почему.swizzle
поведение компании не соответствует тому, что описано в MDN.(См. Также историю о том, как mootools возились с прототипами, которыми они не владели, вынудили переименовать метод ES6, чтобы избежать взлома сети .)
Этого можно избежать, используя префикс для конкретного приложения для методов, добавленных к собственным объектам (например, определить
Array.prototype.myappSwizzle
вместоArray.prototype.swizzle
), но это некрасиво; это так же хорошо разрешимо, используя автономные служебные функции вместо дополнения прототипов.источник
Perf - тоже причина. Иногда вам может потребоваться перебрать ключи. Есть несколько способов сделать это
Я обычно использую,
for of Object.keys()
поскольку он делает правильные вещи и относительно краток, нет необходимости добавлять проверку.Но это намного медленнее .
Просто предположить, что причина
Object.keys
медленной, очевидно,Object.keys()
нужно выделить. Фактически, AFAIK он должен разместить копию всех ключей с тех пор.Возможно, движок JS может использовать какую-то структуру неизменяемых ключей, чтобы
Object.keys(object)
возвращать ссылку, которая выполняет итерацию по неизменяемым ключам иobject.newProp
создает совершенно новый объект неизменяемых ключей, но что бы то ни было, он явно медленнее до 15 разДаже проверка
hasOwnProperty
выполняется в 2 раза медленнее.Суть всего в том, что если у вас есть чувствительный к производительности код и вам нужно перебирать ключи, вы хотите иметь возможность использовать
for in
без вызоваhasOwnProperty
. Вы можете сделать это, только если вы не изменилиObject.prototype
обратите внимание, что если вы используете
Object.defineProperty
для изменения прототипа, если добавляемые вами вещи не перечисляются, они не повлияют на поведение JavaScript в вышеуказанных случаях. К сожалению, по крайней мере, в Chrome 83 они влияют на производительность.Я добавил 3000 неперечислимых свойств, чтобы попытаться вызвать любые проблемы с производительностью. Всего с 30 свойствами тесты были слишком близки, чтобы сказать, есть ли какое-либо влияние на производительность.
https://jsperf.com/does-adding-non-enumerable-properties-affect-perf
Firefox 77 и Safari 13.1 не показали разницы в производительности между классами Augmented и Unaugmented, возможно, v8 будет исправлен в этой области, и вы можете игнорировать проблемы производительности.
Но позвольте мне также добавить, что есть история
Array.prototype.smoosh
. Краткая версия - это популярная библиотека Mootools, созданная самостоятельноArray.prototype.flatten
. Когда комитет по стандартам попытался добавить родной язык,Array.prototype.flatten
они обнаружили, что не могут не сломать множество сайтов. Разработчики, которые узнали о перерыве, предложили назвать метод es5smoosh
шуткой, но люди испугались, не поняв, что это шутка. Они остановилисьflat
вместоflatten
Мораль этой истории в том, что вы не должны расширять родные объекты. Если вы это сделаете, вы можете столкнуться с той же проблемой, что и ваши вещи, и если ваша конкретная библиотека не станет такой же популярной, как MooTools, поставщики браузеров вряд ли решат проблему, которую вы вызвали. Если ваша библиотека действительно станет такой популярной, это будет означать, что все остальные будут работать над проблемой, которую вы вызвали. Итак, пожалуйста, не расширяйте собственные объекты
источник
hasOwnProperty
сообщает вам, находится ли свойство в объекте или в прототипе. Ноfor in
цикл дает вам только перечислимые свойства. Я только что попробовал: добавьте неперечислимое свойство в прототип массива, и оно не будет отображаться в цикле. Нет необходимости не изменять прототип простейшего цикла к работе.