Путаница о том, как создавать классы в JavaScript
В прошлом при создании "классов" в JavaScript я делал это следующим образом:
function Dog(name){
this.name=name;
this.sound = function(){
return "Wuf";
};
}
Однако я просто видел, что кто-то делает это так:
var Dog = (function () {
function Dog(name) {
this.name = name;
}
Dog.prototype.sound = function () {
return "Wuf";
};
return Dog;
})();
Можете ли вы сделать это в обоих направлениях или так, как я сделал это неправильно? В таком случае, почему? И в чем же разница между двумя с точки зрения того, в чем мы заканчиваем? В обоих случаях мы можем создать объект, сказав:
var fido = new Dog("Fido");
fido.sound();
Надеюсь, кто-то просветит меня.
Ответы
Ответ 1
Есть два важных отличия между вашим путем и их.
- Обтекание функцией самозапуска (
(function() { ... })();
)
- Использование свойства
.prototype
над this.
для методов.
Облечение вещей в функцию self-invoking, а затем назначение результата (как определено в инструкции return
для переменной, называется модулем pattern Это общий шаблон для обеспечения большей контролируемости области.
Использование Dog.prototype.sound = function() {}
предпочтительнее до this.sound = function()
. Разница в том, что Dog.prototype.sound
определяется один раз для всех объектов с конструктором Dog
, а this.sound = function() {}
определяется снова для каждого созданного объекта.
Правило большого пальца: Вещи, которые являются индивидуальными для объекта (обычно его свойства), должны быть определены на this
, тогда как вещи, которые являются общими для всех объектов одного и того же типа (обычно это функции), должны быть определены на прототипе.
Ответ 2
С помощью вашего кода вы создаете новую функцию sound
для каждого нового создаваемого экземпляра Dog
. Javascript prototype
избегает этого, создавая только одну функцию, которую разделяют все экземпляры объекта; в основном классическое наследование.
Во втором коде вы показываете, что это просто дополнительно завернуто в IIFE, что в этом случае мало что делает.
Ответ 3
Первый - это традиционный метод создания конструктора. Второе сразу вызывается выражение функции, которое возвращает конструктор. Этот метод позволяет сохранять переменные в модуле без утечки в глобальную область, которая может быть проблемой.
И в чем же разница между двумя в терминах того, что мы заканчиваем?
Оба они, как вы видели, имеют одинаковый результат. Другие говорили о prototype
, поэтому я не буду упоминать об этом здесь.
Ответ 4
Второй предпочтительнее, потому что он использует механизм прототипа на основе Javascript.
Прототипы
Наследование Javascript является причиной путаницы, но на самом деле это довольно просто: каждый объект имеет прототип, который является объектом, который мы будем проверять при попытке доступа к свойству, а не к исходному объекту. Прототип сам будет иметь прототип; в простом случае, например Dog
, это, вероятно, будет Object.prototype
.
В обоих примерах из-за того, как работает оператор new
мы получим цепочку прототипов, которая выглядит так:: fido->Dog.prototype->Object.prototype
. Итак, если мы попытаемся найти свойство name
на Fido, мы найдем его прямо на объекте. Если, с другой стороны, мы ищем свойство hasOwnProperty
, мы не сможем найти его на Fido, не найдем его на Dog.prototype
, а затем достигнем Object.prototype
, где мы его найдем.
В случае sound
ваши примеры определяют его в двух разных местах: в первом случае fido
и каждая другая собака, которую мы создаем, будет иметь свою собственную копию функции. Во втором случае Dog.prototype
будет иметь одну копию функции, к которой будут обращаться отдельные собаки при вызове метода. Это позволяет избежать потерь ресурсов при сохранении дубликатов функции sound
.
Это также означает, что мы можем расширить цепочку прототипов; возможно, нам нужен класс Corgi
, который наследует функцию sound
от Dog
. Во втором случае мы можем просто убедиться, что Dog.prototype
находится в цепочке прототипов Corgi.prototype
; в первом случае нам нужно будет создать настоящую Собака и поместить ее в цепочку прототипов.