Недостатки наследства прототипа JavaScript, каковы они?

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

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

Ответ 1

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

  • Нет стандартного (встроенного в язык) способа записи, который выглядит одинаково независимо от того, какой разработчик написал его.
  • Написание кода, естественно, не дает определения интерфейса, как это делает файл заголовка класса на С++.
  • Нет стандартного способа делать защищенные и частные переменные-члены или методы. Для некоторых есть некоторые соглашения, но опять же разные разработчики делают это по-другому.
  • Нет никакого шага компилятора, чтобы рассказать вам, когда вы сделали глупые ошибки ввода в своем определении.
  • Там нет безопасности типа, если вы этого хотите.

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

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

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

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

Ответ 2

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

то есть:.

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!
}

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

Ответ 3

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

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

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

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

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

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

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

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

Ответ 4

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

Ответ 5

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

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

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

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