Недостатки наследования прототипов JavaScript, каковы они? - PullRequest
21 голосов
/ 05 июля 2011

Недавно я смотрел презентации Дугласа Крокфорда по JavaScript , где он восхищается наследованием прототипов JavaScript, как будто это лучшее, что есть с нарезанного белого хлеба. Учитывая репутацию Крокфорда, это вполне может быть.

Может кто-нибудь сказать, в чем состоит недостаток наследования прототипов JavaScript? (по сравнению с наследованием классов в C # или Java, например)

Ответы [ 5 ]

8 голосов
/ 05 июля 2011

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

то есть:

function MyObject() {
    var foo = 1;
    this.bar = 2;
}

MyObject.prototype.getFoo = function() {
    // can't access "foo" here!
}

MyObject.prototype.getBar = function() {
    return this.bar; // OK!
}

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

5 голосов
/ 05 июля 2011

Вещи, которые я пропускаю, когда подклассифицирую существующий объект в Javascript и наследую от класса в C ++:

  1. Нет стандартного (встроенного в язык) способа написания, который выглядит одинаково, независимо от того, какой разработчик написал его.
  2. Написание вашего кода не приводит к определению интерфейса, как это делает файл заголовка класса в C ++.
  3. Не существует стандартного способа делать защищенные и закрытые переменные или методы-члены. Есть некоторые соглашения для некоторых вещей, но опять же разные разработчики делают это по-разному.
  4. Там нет шага компилятора, чтобы сказать вам, когда вы допустили глупые ошибки при наборе в вашем определении.
  5. Там нет безопасности типов, когда вы этого хотите.

Не поймите меня неправильно, существует множество преимуществ того, как наследование прототипов javascript работает по сравнению с C ++, но в некоторых случаях я считаю, что javascript работает менее плавно.

4 и 5 не связаны строго с наследованием прототипов, но они вступают в игру, когда у вас есть проект значительного размера со множеством модулей, множеством классов и множеством файлов, и вы хотите провести рефакторинг некоторых классов. В C ++ вы можете изменить классы, изменить столько вызывающих, сколько сможете найти, а затем позволить компилятору найти все оставшиеся для вас ссылки, которые нужно исправить. Если вы добавили параметры, изменили типы, изменили имена методов, перемещенные методы и т. Д. ... компилятор покажет, что вам нужно что-то исправить.

В Javascript нет простого способа обнаружить все возможные фрагменты кода, которые необходимо изменить, не выполняя буквально каждый возможный путь кода, чтобы увидеть, пропустили ли вы что-то или сделали какую-то опечатку. Хотя это общий недостаток javascript, я обнаружил, что он особенно проявляется при рефакторинге существующих классов в проекте большого размера. Я подошел к концу цикла выпуска в JS-проекте значительного размера и решил, что мне НЕ следует делать рефакторинг, чтобы исправить проблему (даже если это было лучшее решение), потому что существует риск не найти все возможные последствия это изменение было намного выше в JS, чем в C ++.

Таким образом, следовательно, я считаю более рискованным вносить некоторые типы изменений, связанных с ОО, в проекте JS.

2 голосов
/ 05 июля 2011

Я думаю, что главная опасность состоит в том, что несколько сторон могут отвергать прототипные методы друг друга, что приводит к неожиданному поведению.

Это особенно опасно , потому что очень многие программисты в восторге от "наследования" прототипа (я бы назвал это расширение ) и поэтому начали использовать его повсеместно, добавляя методы влево и вправо, которые могут иметь неоднозначное или субъективное поведение. В конечном итоге, если этот флажок не будет проверен, этот вид «распространения метода-прототипа» может привести к очень сложному в обслуживании коду.

Популярный пример - метод trim. Это может быть реализовано примерно так одной стороной:

String.prototype.trim = function() {
    // remove all ' ' characters from left & right
}

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

Или другая сторона переопределяет метод для удаления ' ' символов и других форм пробела (например, табуляции, разрывов строк). Это может остаться незамеченным в течение некоторого времени, но привести к странному поведению в будущем.

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

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

1 голос
/ 10 ноября 2011

Мне не хватает возможности отделить интерфейс от реализации.В языках с системой наследования, которая включает в себя такие понятия, как abstract или interface, вы можете, например, объявить свой интерфейс на уровне своего домена, но поместить реализацию на уровень своей инфраструктуры.(См. луковая архитектура .) Система наследования JavaScript не имеет возможности сделать что-то подобное.

0 голосов
/ 05 июля 2011

Я хотел бы знать, совпадает ли мой интуитивный ответ с мнением экспертов.

Меня беспокоит то, что если у меня есть функция в C # (для обсуждения), которая принимает параметрлюбой разработчик, который пишет код, вызывающий мою функцию, сразу же узнает из сигнатуры функции, какие параметры он принимает и какой тип значения он возвращает.

С помощью «утки» в JavaScript кто-то может унаследовать один из моихобъекты и изменения его функций-членов и значений (да, я знаю, что функции являются значениями в JavaScript) практически любым способом, который можно себе представить, так что объект, который они передают в мою функцию, не имеет никакого сходства с объектом, который я ожидаю, что моя функция будет передана.

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

...