Подтвердить что ты не робот

Каковы различия между этими тремя шаблонами "классов" в JavaScript?

Существуют ли какие-либо важные/тонкие/существенные различия под капотом при выборе одного из этих четырех шаблонов над другими? И существуют ли какие-либо различия между ними, когда "создается экземпляр" через Object.create() против оператора new?

1) Образец, который CoffeeScript использует при переводе определений класса:

Animal = (function() {

  function Animal(name) {
    this.name = name;
  }

  Animal.prototype.move = function(meters) {
    return alert(this.name + (" moved " + meters + "m."));
  };

  return Animal;

})();

и

2) Образец, который Knockout, кажется, способствует:

var DifferentAnimal = function(name){

    var self = this;

    self.name = name;

    self.move = function(meters){
        return alert(this.name + (" moved " + meters + "m."));
    };

}

и

3) аналогичный простой шаблон, который я часто видел:

var DifferentAnimalWithClosure = function(name){

    var name = name;

    var move = function(meters){

    };

    return {name:name, move:move};

}

и

4) Шаблон, который Backbone способствует:

var OneMoreAnimal= ClassThatAlreadyExists.extend({

    name:'',
    move:function(){}

});

Обновление 1: Изменен шаблон # 2 и добавлен шаблон # 3 в ответ на ответ Elias//незначительное форматирование

4b9b3361

Ответ 1

Просто, чтобы быть ясным: JS не знает классов, просто объектов и пользовательских, самоопределяемых функций-конструкторов, но кроме того.
Чтобы ответить на ваш вопрос короче: да, есть несколько небольших и даже довольно больших различий между различными способами создания нового объекта, который вы публикуете здесь.

CoffeeScript:
Это на самом деле самый четкий и традиционный способ создания собственного конструктора, но он был "оптимизирован" в том смысле, что он был готов к настройке использования (необязательных) замыкающих переменных.
В основном, что делает этот код, используется IIFE, чтобы обернуть как определение конструктора, так и назначения метода proptotype в их собственной частной области, которая возвращает ссылку на новый конструктор. Это просто чистый, простой JS, ничем не отличающийся от того, что вы могли бы написать себе.

Нокаут:
Теперь это немного меня бросило, потому что для меня, по крайней мере, фрагмент, который вы предоставляете, выглядит либо как часть шаблона модуля, либо конструктор мощности. Но поскольку вы не используете strict mode, опускание new будет по-прежнему создавать опасные ситуации, а так как вся функция проходит через проблему создания нового экземпляра DifferentAnimal, то только для того, чтобы затем построить второй объектный литерал, присваивая всем свойствам DifferentAnimal этому второстепенному объекту, я бы сказал, что вам что-то не хватает. Потому что, правда, сказать, что здесь отсутствует последнее утверждение return {};, вероятно, ничего не изменит. Плюс: как вы можете видеть, вы объявляете метод (move) в том, что по сути является конструктором. Это означает, что каждому экземпляру присваивается свой собственный объект функции move, а не получение его из прототипа.
Короче говоря, еще раз посмотрим, откуда вы взяли этот фрагмент, и дважды проверьте, является ли это полной версией, потому что, если это так, я могу видеть только аргументы против этого.

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

function MyConstructor(param)
{
     var paramInit = param/2;//or something
     this.p = paramInit;//this property can change later on, so:
     this.reInit = function()
     {//this method HAS to be inside constructor, every instance needs its own method
         this.p = paramInit;//var paramInit can't, it local to this scope
     };
}
var foo = new MyConstructor(10);
console.log(foo.p);//5
foo.p = 'hi';
console.log(foo.p);//hi
foo.reInit();
console.log(foo.p);//5
console.log(foo.paramInit);//undefined, not available outside object: it a pseudo-private property

Это все, что есть, действительно. Когда вы видите ppl с помощью var that = this; или что-то еще, что часто создает ссылку на основной объект, доступный где угодно, без необходимости иметь дело с головными болями this (что делает this ссылка? когда применяется к объекту, отличному от того, из которого он изначально предназначался? etcetera...)

Backbone:
Здесь мы имеем дело с другим случаем: расширение объектов (IE: использование методов, свойств существующего "класса" (конструктора) или конкретного экземпляра) - это не то же самое, что просто создать объект.
Как вам хорошо известно, объектам JS можно назначать новые свойства в любой момент времени. Эти свойства также могут быть удалены. Иногда свойства прототипа могут быть переопределены на самом экземпляре (маскирование прототипического поведения) и т.д. Итак, все зависит от того, что вы хотите, чтобы получившийся объект (вновь созданный объект, который расширяет данный экземпляр) выглядел так: хотите ли вы взять все свойства из экземпляра или вы хотите, чтобы оба объекта использовали один и тот же прототип где-то в очереди?
Обе эти вещи могут быть достигнуты с помощью простого JS, но они просто прикладывают немного больше усилий, чтобы написать себя. Однако, если вы пишете, например:

function Animal(name)
{
    this.name = name;
}
Animal.prototype.eat= function()
{
    console.log(this.name + ' is eating');
};

Это можно было бы считать эквивалентом написания:

var Animal = Object.extend({name:'',eat:function()
{
    console.log(this.name + ' is eating');
}});

Гораздо короче, но отсутствует конструктор.

new vs Object.create
Ну, это просто: Object.create просто намного мощнее, чем new: вы можете определить методы прототипа, свойства (в том числе погоду или нет, они перечислимы, могут быть записаны и т.д.) Прямо в то время, когда вам нужно создайте объект, вместо того, чтобы писать конструктор и прототип, или создать объектный литерал и обходиться со всеми этими строками Object.defineProperty.
Недостатки: некоторые люди по-прежнему не используют ECMA5-совместимые браузеры (IE8 все еще не совсем мертв). По моему опыту: через некоторое время становится довольно сложно отлаживать значимые сценарии: хотя я предпочитаю использовать конструкторы власти больше, чем обычные конструкторы, я все еще определяю их на самой вершине моего script, с четкими, четкие и довольно описательные имена, тогда как объектно-литералы - это вещи, которые я просто создаю "на лету". Используя Object.create, я заметил, что я склонен создавать объекты, которые на самом деле слишком сложны, чтобы квалифицировать как фактические литералы объектов, как если бы они были объектными литералами:

//fictional example, old:
var createSomething = (function()
{
    var internalMethod = function()
    {//method for new object
        console.log(this.myProperty || '');
    };
    return function(basedOn)
    {
        var prop, returnVal= {};
        returnVal.myProperty = new Date();
        returnVal.getCreated = internalMethod;//<--shared by all instances, thx to closure
        if (!basedOn || !(basedOn instanceof Object))
        {//no argument, or argument is not an object:
            return returnVal;
        }
        for (prop in basedOn)
        {//extend instance, passed as argument
            if (basedOn.hasOwnProperty(prop) && prop !== '_extends')
            {
                returnVal[prop] = basedOn[prop];
            }
        }
        returnVal._extends = basedOn;//<-- ref as sort-of-prototype
        return returnVal;
    };
}());

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

var createSomething = Object.create(someObject, {getCreated:function()
{
    console.log(this.myProperty);
},
myProperty:new Date()});

Но IMO, это усложняет вам отслеживание того, какой объект создается там (в основном потому, что Object.create является выражением и не будет поднят.
Ах, да, это далеко не окончательный аргумент конечно: у обоих есть свои профи и con: я предпочитаю использовать модульные паттеры, блокировки и конструкторы власти, если вы этого не делаете, просто отлично.

Надеюсь, что это очистило вещь или 2 для вас.

Ответ 2

Первый пример помещает функцию перемещения в прототип, который будет использоваться всеми экземплярами Animal.

Второй пример создает новую функцию перемещения для каждого экземпляра животного.

Третий пример генерирует класс Animal с функцией перемещения в прототипе, аналогичном первому примеру, но с меньшим количеством кода. (В вашем примере имя также разделяется между всеми экземплярами, которые вам, вероятно, не нужны)

Помещение функции в прототип ускоряет создание экземпляров Животные и из-за того, что JIT-двигатели работают даже быстрее, чем выполнение функции.