Использование «прототипа» против «этого» в JavaScript?

776

какая разница между

var A = function () {
    this.x = function () {
        //do something
    };
};

а также

var A = function () { };
A.prototype.x = function () {
    //do something
};
sw234
источник
Понятие ЭТОГО ключевого слова подробно объясняется здесь. scotch.io/@alZami/understanding-this-in-javascript
AL-zami
1
Чтение «этой» ветки показывает, насколько ужасен JS и насколько его принципы неясны для многих разработчиков. Что именно не так с более понятными языками? Я думаю, что пришло время разработчикам поднять свой голос, чтобы отказаться от запутанных технологий, которые не представляют никакой ценности для бизнеса или разработки.
NoChance
На объекте a1.x !== a2.x:; по прототипу:a1.x === a2.x
Хуан Мендес

Ответы:

467

Примеры имеют очень разные результаты.

Прежде чем смотреть на различия, следует отметить следующее:

  • Прототип конструктора предоставляет способ обмена методами и значениями между экземплярами через частное [[Prototype]]свойство экземпляра .
  • Функция это определяется тем, как она вызывается, или использованием bind (здесь не обсуждается). Если функция вызывается для объекта (например myObj.method()), то это внутри метода ссылается на объект. Если это не установлено вызовом или использованием bind , по умолчанию используется глобальный объект (окно в браузере) или в строгом режиме, он остается неопределенным.
  • JavaScript является объектно-ориентированным языком, т.е. большинство значений являются объектами, включая функции. (Строки, числа и логические значения не являются объектами.)

Итак, вот такие фрагменты:

var A = function () {
    this.x = function () {
        //do something
    };
};

В этом случае переменной Aприсваивается значение, которое является ссылкой на функцию. Когда эта функция вызывается с использованием A(), функция this не устанавливается вызовом, поэтому по умолчанию используется глобальный объект, и выражение this.xдействует window.x. В результате ссылка на выражение функции в правой части назначается window.x.

На случай, если:

var A = function () { };
A.prototype.x = function () {
    //do something
};

происходит что-то совсем другое. В первой строке переменной Aприсваивается ссылка на функцию. В JavaScript все объекты функций по умолчанию имеют свойство prototype , поэтому нет отдельного кода для создания объекта A.prototype .

Во второй строке A.prototype.x назначается ссылка на функцию. Это создаст свойство x, если оно не существует, или назначит новое значение, если оно существует. Таким образом, разница с первым примером, в котором свойство x объекта участвует в выражении.

Другой пример ниже. Это похоже на первый (и, возможно, о чем вы хотели спросить):

var A = new function () {
    this.x = function () {
        //do something
    };
};

В этом примере newоператор был добавлен перед выражением функции, поэтому функция вызывается как конструктор. При вызове с newпомощью функции this устанавливается ссылка на новый объект, частное [[Prototype]]свойство которого установлено для ссылки на открытый прототип конструктора . Таким образом, в операторе присваивания xбудет создано свойство для этого нового объекта. При вызове в качестве конструктора функция возвращает свой объект this по умолчанию, поэтому нет необходимости в отдельном return this;операторе.

Чтобы проверить, что A имеет свойство x :

console.log(A.x) // function () {
                 //   //do something
                 // };

Это необычное использование new, поскольку единственный способ ссылаться на конструктор - через A.constructor . Было бы гораздо более распространенным сделать:

var A = function () {
    this.x = function () {
        //do something
    };
};
var a = new A();

Другим способом достижения аналогичного результата является использование выражения, вызываемого немедленно:

var A = (function () {
    this.x = function () {
        //do something
    };
}());

В этом случае Aприсваивается возвращаемое значение вызова функции с правой стороны. Здесь снова, так как это не установлено в вызове, оно будет ссылаться на глобальный объект и this.xбудет эффективным window.x. Так как функция ничего не возвращает, Aбудет иметь значение undefined.

Эти различия между этими двумя подходами также проявляются, если вы сериализуете и десериализуете свои объекты Javascript в / из JSON. Методы, определенные в прототипе объекта, не сериализуются при сериализации объекта, что может быть удобно, когда, например, вы хотите сериализовать только части данных объекта, но не его методы:

var A = function () { 
    this.objectsOwnProperties = "are serialized";
};
A.prototype.prototypeProperties = "are NOT serialized";
var instance = new A();
console.log(instance.prototypeProperties); // "are NOT serialized"
console.log(JSON.stringify(instance)); 
// {"objectsOwnProperties":"are serialized"} 

Смежные вопросы :

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

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

keparo
источник
50
@keparo: Вы не правы. Каждый объект имеет [внутренний] объект-прототип (который может быть null), но это очень отличается от prototypeсвойства, которое относится к функциям и для которого устанавливается прототип всех экземпляров, когда они создаются new. Не могу поверить, что это действительно получило 87 голосов :-(
Берги
8
"The language is functional"Вы уверены, что это то, что означает функционал?
phant0m
23
Я второе, что @Bergi сказал о прототипах. Функции имеют свойство прототипа. Все объекты, включая функции, имеют другое внутреннее свойство, к которому можно обращаться с помощью Object.getPrototypeOf (myObject) или с myObject .__ proto__ в некоторых браузерах. Свойство proto указывает на родительский объект в цепочке прототипов (или объект, от которого этот объект наследуется). Свойство prototype (только для функций) указывает на объект, который станет родителем всех объектов, которые используют функцию для создания новых объектов с использованием ключевого слова new.
Джим Купер
11
Эта статья довольно ошибочна и сбивает с толку, как это установить. Работаю над переписью.
RobG
37
Этот ответ довольно странный и, похоже, совершенно не соответствует сути вопроса. Похоже, что вопрос об определении свойств типа внутри конструктора вместо прототипа является очень распространенным, но половина ответа о том, что произойдет, если вы используете Aфункцию, а другая половина о неясных и неортодоксальных способах выполнения. что-то простое.
JLRishe
236

Как говорили другие версии первой версии, использование «this» приводит к тому, что каждый экземпляр класса A имеет свою собственную независимую копию метода функции «x». Принимая во внимание, что использование «прототипа» будет означать, что каждый экземпляр класса А будет использовать одну и ту же копию метода «х».

Вот некоторый код, чтобы показать эту тонкую разницу:

// x is a method assigned to the object using "this"
var A = function () {
    this.x = function () { alert('A'); };
};
A.prototype.updateX = function( value ) {
    this.x = function() { alert( value ); }
};

var a1 = new A();
var a2 = new A();
a1.x();  // Displays 'A'
a2.x();  // Also displays 'A'
a1.updateX('Z');
a1.x();  // Displays 'Z'
a2.x();  // Still displays 'A'

// Here x is a method assigned to the object using "prototype"
var B = function () { };
B.prototype.x = function () { alert('B'); };

B.prototype.updateX = function( value ) {
    B.prototype.x = function() { alert( value ); }
}

var b1 = new B();
var b2 = new B();
b1.x();  // Displays 'B'
b2.x();  // Also displays 'B'
b1.updateX('Y');
b1.x();  // Displays 'Y'
b2.x();  // Also displays 'Y' because by using prototype we have changed it for all instances

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

Benry
источник
5
Это то, что я ожидаю, но когда я создаю новый объект после смены Ax, как описано выше, я все равно отображаю «A», если я не использую A как одиночный. jsbin.com/omida4/2/edit
медуза
19
Это потому, что мой пример был неверным. Это было неправильно только в течение двух лет. Вздох. Но суть все еще в силе. Я обновил пример с тем, который действительно работает. Спасибо за указание на это.
Бенри
4
Это статический метод! : D
6
да ... «прототип» означает статический уровень или уровень класса .. который будет использоваться всеми созданными экземплярами ... в то время как «это» является методом экземпляра, каждый экземпляр которого будет иметь свою собственную копию
Aneer Dev
7
Это не статично. Статика, используемая в большинстве языков ОО, подразумевает отсутствие зависимости от thisобъекта, который является владельцем метода. т.е. метод не имеет объекта, который является его владельцем. В этом случае есть thisобъект, как показано в классе A в примере.
CJStuart
152

Возьмите эти 2 примера:

var A = function() { this.hey = function() { alert('from A') } };

против

var A = function() {}
A.prototype.hey = function() { alert('from prototype') };

Большинство людей здесь (особенно ответы с самым высоким рейтингом) пытались объяснить, чем они отличаются, не объясняя ПОЧЕМУ. Я думаю, что это неправильно, и если вы сначала поймете основы, разница станет очевидной. Давайте сначала попробуем объяснить основы ...

а) Функция - это объект в JavaScript. КАЖДЫЙ объект в JavaScript получает внутреннее свойство (то есть, вы не можете получить к нему доступ, как и к другим свойствам, за исключением, может быть, в браузерах, таких как Chrome), которое часто называют __proto__(вы можете ввести anyObject.__proto__Chrome, чтобы увидеть, на что он ссылается. Это просто так. , свойство, ничего более. Свойство в JavaScript = переменная внутри объекта, ничего более. Что делают переменные? Они указывают на вещи.

Так на что же __proto__указывает это свойство? Ну, обычно другой объект (мы объясним, почему позже). Единственный способ заставить JavaScript для __proto__свойства НЕ указывать на другой объект - это использовать var newObj = Object.create(null). Даже если вы сделаете это, __proto__свойство STILL существует как свойство объекта, просто оно не указывает на другой объект, оно указывает на null.

Вот где большинство людей запутываются:

Когда вы создаете новую функцию в JavaScript (которая также является объектом, помните?), В момент ее определения JavaScript автоматически создает новое свойство для этой функции, которое вызывается prototype. Попробуй это:

var A = [];
A.prototype // undefined
A = function() {}
A.prototype // {} // got created when function() {} was defined

A.prototypeПОЛНОСТЬЮ ОТЛИЧАЕТСЯ от __proto__собственности. В нашем примере «A» теперь имеет ДВА свойства, называемые «prototype» и __proto__. Это большая путаница для людей. prototypeи __proto__свойства никак не связаны, это разные вещи, указывающие на отдельные значения.

Вы можете задаться вопросом: почему JavaScript имеет __proto__свойство, созданное для каждого отдельного объекта? Ну, одним словом: делегирование . Когда вы вызываете свойство объекта, а у объекта его нет, JavaScript ищет объект, на который ссылается объект, __proto__чтобы узнать, есть ли у него его. Если у него его нет, то он смотрит на __proto__свойство этого объекта и так далее ... пока цепочка не закончится. Так называется прототип цепочки . Конечно, если JavaScript __proto__не указывает на объект и вместо этого указывает на nullнеудачу, JavaScript это понимает и вернет вам undefinedсвойство.

Вы также можете задаться вопросом, почему JavaScript создает свойство, вызываемое prototypeдля функции, когда вы определяете функцию? Потому что он пытается обмануть вас, да обмануть вас, что он работает как языки на основе классов.

Давайте продолжим с нашим примером и создадим «объект» из A:

var a1 = new A();

Что-то происходит на заднем плане, когда это случилось. a1обычная переменная, которой был присвоен новый пустой объект.

Тот факт, что вы использовали оператор newперед вызовом функции, A()сделал что-то ДОПОЛНИТЕЛЬНОЕ в фоновом режиме. newКлючевое слово создается новый объект , который в настоящее время ссылки a1и этот объект пуст. Вот что происходит дополнительно:

Мы говорили, что в каждом определении функции есть новое созданное свойство с именем prototype(к которому вы можете получить доступ, в отличие от __proto__свойства)? Ну, это свойство сейчас используется.

Итак, мы находимся в точке, где у нас есть свежеиспеченный пустой a1объект. Мы говорили, что все объекты в JavaScript имеют внутреннее __proto__свойство, которое указывает на что-то ( a1также имеет), является ли оно нулевым или другим объектом. То , что newоператор делает то , что он устанавливает , что __proto__свойство точки к функции в prototypeсобственность. Прочитайте это снова. Это в основном это:

a1.__proto__ = A.prototype;

Мы сказали, что A.prototypeэто не что иное, как пустой объект (если мы не изменим его на что-то еще до определения a1). Так что теперь, в основном, a1.__proto__указывает на то же самое, на что A.prototypeуказывает этот пустой объект. Они оба указывают на один и тот же объект, который был создан, когда произошла эта строка:

A = function() {} // JS: cool. let's also create A.prototype pointing to empty {}

Теперь, когда var a1 = new A()оператор обрабатывается , происходит еще одна вещь . В основном A()выполняется, и если A что-то вроде этого:

var A = function() { this.hey = function() { alert('from A') } };

Все эти вещи внутри function() { }будут выполнены. Когда вы достигаете this.hey..линии, thisменяется на a1и вы получаете это:

a1.hey = function() { alert('from A') }

Я не буду освещать, почему thisизменения, a1но это отличный ответ, чтобы узнать больше.

Итак, подведем итог: когда вы делаете, var a1 = new A()на заднем плане происходят 3 вещи:

  1. Совершенно новый пустой объект создается и назначается a1.a1 = {}
  2. a1.__proto__свойство назначается так, чтобы оно указывало на то же, что и указатель A.prototype(другой пустой объект {})

  3. Функция A()выполняется с thisустановленным новым пустым объектом, созданным на шаге 1 (прочитайте ответ, на который я ссылался выше, чтобы узнать, почему он thisменяется a1)

Теперь давайте попробуем создать еще один объект:

var a2 = new A();

Шаги 1,2,3 повторится. Вы что-то замечаете? Ключевое слово - повторить. Шаг 1: a2будет новым пустым объектом, шаг 2: его __proto__свойство будет указывать на то же, на что A.prototypeуказывает и, самое главное, шаг 3: функция A()выполняется снова, а это значит, что a2она получит heyсвойство, содержащее функцию. a1и a2имеют два названных свойства SEPARATE, heyкоторые указывают на 2 отдельные функции! Теперь у нас есть дубликаты функций в одних и тех же двух разных объектах, которые делают одно и то же, упс ... Вы можете представить, как это повлияет на память, если у нас будет создано 1000 объектов new A, после того как все объявления функций занимают больше памяти, чем что-то вроде числа 2. Итак, как мы можем предотвратить это?

Помните, почему __proto__свойство существует на каждом объекте? Таким образом, если вы извлекаете yoManсвойство a1(которое не существует), к его __proto__свойству обращаются, и если оно является объектом (и в большинстве случаев так оно и есть), оно проверит, содержит ли оно yoMan, а если нет, он будет обращаться к этому объекту и __proto__т. д. Если это произойдет, он примет значение этого свойства и отобразит его вам.

Поэтому кто-то решил использовать этот факт + тот факт, что при создании a1его __proto__свойство указывает на тот же (пустой) объект, на который A.prototypeуказывает и делает это:

var A = function() {}
A.prototype.hey = function() { alert('from prototype') };

Круто! Теперь, когда вы создаете a1, он снова проходит все 3 шага, описанных выше, и на шаге 3 он ничего не делает, так function A()как не имеет ничего для выполнения. И если мы сделаем:

a1.hey

Он увидит, что a1он не содержит, heyи проверит свой __proto__объект свойства, чтобы увидеть, есть ли он, что имеет место.

При таком подходе мы исключаем часть из шага 3, где функции дублируются при каждом создании нового объекта. Вместо того , чтобы a1и a2наличие отдельного heyимущества, в настоящее время никто из них не имеет его. Который, я полагаю, ты уже сам понял. Это хорошо ... если вы понимаете, __proto__и Function.prototypeтакие вопросы будут довольно очевидными.

ПРИМЕЧАНИЕ. Некоторые люди склонны не вызывать внутреннее свойство Prototype, поскольку __proto__я использовал это имя в публикации, чтобы четко отличить его от Functional.prototypeсвойства как две разные вещи.

daremkd
источник
1
Действительно исчерпывающий и информативный ответ. Я провел несколько тестов памяти с использованием описанных выше структур объектов (A.prototype.hey vs object this.hey) и создал 1000 экземпляров каждого из них. Объем памяти при подходе к объектным свойствам был на 100 КБ больше по сравнению с прототипом. Затем я добавил еще одну функцию с той же целью, которая называется «глупая», и она линейно увеличилась до 200 КБ. Не важно, но не арахис тоже.
17
Что более интересно, метод-прототип был немного медленнее, чем метод свойства объекта, работающий локально. В целом, я не уверен, что javascript следует использовать для манипулирования данными с объектами с номерами свыше 10 Кб, поэтому устраняются любые причины для изменения подходов на основе потенциальных эффектов памяти. В этот момент работа должна быть выгружена на сервер.
17
Дело в том, __proto__и .prototypeэто совершенно разные вещи.
Wayou
1
Я не чувствую себя удовлетворенным, чтобы просто дать вам голос ... Хорошо сделано!
Кристианмитк
58

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

Причиной использования первой формы является доступ к "частным пользователям". Например:

var A = function () {
    var private_var = ...;

    this.x = function () {
        return private_var;
    };

    this.setX = function (new_x) {
        private_var = new_x;
    };
};

Из-за правил области видимости javascript private_var доступен для функции, назначенной this.x, но не за пределами объекта.

Мэтью Крамли
источник
1
Смотрите этот пост: stackoverflow.com/a/1441692/654708 для примера о том, как получить доступ к закрытым членам через прототипы.
GFoley83
@ GFoley83 этот ответ не показывает, что - методы-прототипы могут получить доступ только к «публичным» свойствам данного объекта. Только привилегированные методы (не в прототипе) могут получить доступ к закрытым членам.
Альнитак
28

Первый пример изменяет интерфейс только для этого объекта. Второй пример изменяет интерфейс для всех объектов этого класса.

Гленн
источник
Оба сделают функцию xдоступной для всех объектов, прототипу которых назначен новый экземпляр A:function B () {}; B.prototype = new A(); var b = new B(); b.x() // Will call A.x if A is defined by first example;
Спенсер Уильямс
21

Основная проблема с использованием thisвместо этого prototypeзаключается в том, что при переопределении метода конструктор базового класса будет по-прежнему ссылаться на переопределенный метод. Учти это:

BaseClass = function() {
    var text = null;

    this.setText = function(value) {
        text = value + " BaseClass!";
    };

    this.getText = function() {
        return text;
    };

    this.setText("Hello"); // This always calls BaseClass.setText()
};

SubClass = function() {
    // setText is not overridden yet,
    // so the constructor calls the superclass' method
    BaseClass.call(this);

    // Keeping a reference to the superclass' method
    var super_setText = this.setText;
    // Overriding
    this.setText = function(value) {
        super_setText.call(this, "SubClass says: " + value);
    };
};
SubClass.prototype = new BaseClass();

var subClass = new SubClass();
console.log(subClass.getText()); // Hello BaseClass!

subClass.setText("Hello"); // setText is already overridden
console.log(subClass.getText()); // SubClass says: Hello BaseClass!

против:

BaseClass = function() {
    this.setText("Hello"); // This calls the overridden method
};

BaseClass.prototype.setText = function(value) {
    this.text = value + " BaseClass!";
};

BaseClass.prototype.getText = function() {
    return this.text;
};

SubClass = function() {
    // setText is already overridden, so this works as expected
    BaseClass.call(this);
};
SubClass.prototype = new BaseClass();

SubClass.prototype.setText = function(value) {
    BaseClass.prototype.setText.call(this, "SubClass says: " + value);
};

var subClass = new SubClass();
console.log(subClass.getText()); // SubClass says: Hello BaseClass!

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

var A = function (param1) {
    var privateVar = null; // Private variable

    // Calling this.setPrivateVar(param1) here would be an error

    this.setPrivateVar = function (value) {
        privateVar = value;
        console.log("setPrivateVar value set to: " + value);

        // param1 is still here, possible memory leak
        console.log("setPrivateVar has param1: " + param1);
    };

    // The constructor logic starts here possibly after
    // many lines of code that define methods

    this.setPrivateVar(param1); // This is valid
};

var a = new A(0);
// setPrivateVar value set to: 0
// setPrivateVar has param1: 0

a.setPrivateVar(1);
//setPrivateVar value set to: 1
//setPrivateVar has param1: 0

против:

var A = function (param1) {
    this.setPublicVar(param1); // This is valid
};
A.prototype.setPublicVar = function (value) {
    this.publicVar = value; // No private variable
};

var a = new A(0);
a.setPublicVar(1);
console.log(a.publicVar); // 1
tarkabak
источник
20

Каждый объект связан с объектом-прототипом. При попытке получить доступ к несуществующему свойству JavaScript будет искать в этом объекте-прототипе это свойство и возвращать его, если оно существует.

prototypeСвойство функции конструктора относится к объекту прототипа всех экземпляров , созданных с этой функцией при использовании new.


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

var A = function () {
    this.x = function () {
        //do something
    };
};

var a = new A();    // constructor function gets executed
                    // newly created object gets an 'x' property
                    // which is a function
a.x();              // and can be called like this

Во втором примере вы добавляете свойство к объекту-прототипу, на которое Aуказывают все экземпляры, созданные с помощью .

var A = function () { };
A.prototype.x = function () {
    //do something
};

var a = new A();    // constructor function gets executed
                    // which does nothing in this example

a.x();              // you are trying to access the 'x' property of an instance of 'A'
                    // which does not exist
                    // so JavaScript looks for that property in the prototype object
                    // that was defined using the 'prototype' property of the constructor

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

pishpish
источник
1
Проголосовал за то, что был самым прямым ответом на вопрос.
Ник Пинеда
1
Мне понравился твой прямой подход !! пальцы вверх!
Принц Виджай Пратап
16

Какая разница? => Много.

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

Давайте посмотрим на следующий пример:

var AdultPerson = function() {

  var age;

  this.setAge = function(val) {
    // some housekeeping
    age = val >= 18 && val;
  };

  this.getAge = function() {
    return age;
  };

  this.isValid = function() {
    return !!age;
  };
};

Теперь prototypeструктура может быть применена следующим образом:

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

AdultPerson.prototype.getRights = function() {
  // Should be valid
  return this.isValid() && ['Booze', 'Drive'];
};

Давайте посмотрим на реализацию сейчас.

var p1 = new AdultPerson;
p1.setAge(12); // ( age = false )
console.log(p1.getRights()); // false ( Kid alert! )
p1.setAge(19); // ( age = 19 )
console.log(p1.getRights()); // ['Booze', 'Drive'] ( Welcome AdultPerson )

var p2 = new AdultPerson;
p2.setAge(45);    
console.log(p2.getRights()); // The same getRights() method, *** not a new copy of it ***

Надеюсь это поможет.

oozzal
источник
3
+1 Гораздо менее запутанный и более наглядный ответ, чем остальные. Но вы должны уточнить немного, прежде чем приводить эти (хорошие) примеры.
yerforkferchips
1
Я не уверен насчет «эта версия используется для включения инкапсуляции, то есть сокрытия данных». Если свойство внутри функции определено с использованием «this», как в «this.myProperty = ...», такое свойство не является «private» и может быть доступно из объектов вне класса с помощью «new».
NoChance
14

Прототип - это шаблон класса; что относится ко всем будущим экземплярам этого. В то время как это конкретный экземпляр объекта.

harropriiz
источник
14

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

Функция непосредственно на объекте

Функция по прототипу

Здесь мы создаем 2 000 000 новых объектов с помощью printметода в Chrome. Мы храним каждый объект в массиве. Установка printпрототипа занимает около 1/2 времени.

Арнав Аггарвал
источник
13

Позвольте мне дать вам более полный ответ, который я узнал во время курса обучения JavaScript.

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

В общем, нет правильного или неправильного, это скорее вопрос вкуса или дизайнерского решения в зависимости от ваших требований. Прототип, однако, является техникой, которая используется для разработки объектно-ориентированного подхода, как я надеюсь, вы увидите в конце этого ответа.

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

Шаблон декоратора объектов

Не уверен, что эта модель все еще актуальна в наше время, но она существует. И это полезно знать об этом. Вы просто передаете объект и свойство в функцию декоратора. Декоратор возвращает объект со свойством и методом.

var carlike = function(obj, loc) {
    obj.loc = loc;
    obj.move = function() {
        obj.loc++;
    };
    return obj;
};

var amy = carlike({}, 1);
amy.move();
var ben = carlike({}, 9);
ben.move();

Функциональные классы

Функция в JavaScript является специализированным объектом. Помимо вызова, функция может хранить свойства, как и любой другой объект.

В этом случае Carэто функция ( также думать объект ), которая может быть вызвана, как вы привыкли делать. У него есть свойство methods(которое является объектом с moveфункцией). Когда Carвызывается, вызывается extendфункция, которая совершает какое-то волшебство и расширяет Carфункцию (думаю объект) с помощью методов, определенных внутри methods.

Этот пример, хотя и отличается, ближе всего подходит к первому примеру в вопросе.

var Car = function(loc) {
    var obj = {loc: loc};
    extend(obj, Car.methods);
    return obj;
};

Car.methods = {
    move : function() {
        this.loc++;
    }
};

var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

Занятия по прототипам

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

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

Однако есть один интересный момент: у каждого prototypeобъекта есть удобное свойство constructor, которое указывает на функцию (думайте объект), к которой он присоединен.

Относительно последних трех строк:

В этом примере Carссылки на prototypeобъект, который связывает через constructorк Carсебе, то есть Car.prototype.constructorэто Carсамо по себе. Это позволяет выяснить, какая функция конструктора построила определенный объект.

amy.constructorпоиск завершается неудачно и, таким образом, делегируется Car.prototype, у которого есть свойство конструктор. И вот amy.constructorэто Car.

Кроме того, amyявляется instanceof Car. instanceofОператор работает, видя , если объект - прототип правого операнда ( Car) можно найти в любом месте в прототипе левого операнда ( amy) цепи.

var Car = function(loc) {
    var obj = Object.create(Car.prototype);
    obj.loc = loc;
    return obj;
};

Car.prototype.move = function() {
        this.loc++;
};

var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

console.log(Car.prototype.constructor);
console.log(amy.constructor);
console.log(amy instanceof Car);

Некоторые разработчики могут быть смущены в начале. Смотрите ниже пример:

var Dog = function() {
  return {legs: 4, bark: alert};
};

var fido = Dog();
console.log(fido instanceof Dog);

В instanceofоператор возвращается false, потому что Dog«прототип сек не может быть найден где - нибудь в fido» S цепи прототипов. fidoэто простой объект, который создается с литералом объекта, то есть он просто делегирует Object.prototype.

Псевдоклассические паттерны

На самом деле это просто еще одна форма шаблона-прототипа в упрощенной форме, и она более привычна для тех, кто программирует на Java, например, так как она использует newконструктор.

Он делает то же самое, что и в паттерне-прототипе, это просто синтаксический сахар поверх макета-прототипа.

Однако основное отличие состоит в том, что в механизмах JavaScript реализованы оптимизации, которые применяются только при использовании псевдоклассического шаблона. Подумайте о псевдоклассическом паттерне, возможно, о более быстрой версии прототипного паттерна; объектные отношения в обоих примерах одинаковы.

var Car = function(loc) {
    this.loc = loc;
};

Car.prototype.move = function() {
        this.loc++;
};

var amy = new Car(1);
amy.move();
var ben = new Car(9);
ben.move();

Наконец, не должно быть слишком сложно понять, как можно сделать объектно-ориентированное программирование. Есть два раздела.

Один раздел, который определяет общие свойства / методы в прототипе (цепочке).

И еще один раздел, где вы помещаете определения, которые отличают объекты друг от друга ( locпеременные в примерах).

Это то, что позволяет нам применять такие понятия, как суперкласс или подкласс в JavaScript.

Не стесняйтесь добавлять или редактировать. Как только я закончу, я могу сделать это вики-сообществом.

Ely
источник
Не очень-то пост, но я думал, что ОО и прототипическое наследование - это, по сути, разные школы мысли.
Ник Пинеда
Они есть, но можно «делать ОО» с помощью различных техник / мыслей, не так ли?
Ely
Не уверен, правда. Многие просто говорят, что философия прототипа отличается, и многие пытаются сравнить ее с ОО, потому что это школа мысли, к которой многие привыкли.
Ник Пинеда
Я имею в виду, что если вы хотите практиковать ОО-стиль и язык предлагает набор техник, которые помогают это сделать, это не обязательно неправильно.
Ely
11

Я считаю, что @Matthew Crumley прав. Они функционально , если не структурно, эквивалентны. Если вы используете Firebug для просмотра объектов, созданных с использованием new, вы увидите, что они одинаковы. Тем не менее, мое предпочтение будет следующим. Я предполагаю, что это больше похоже на то, к чему я привык в C # / Java. То есть, определить класс, определить поля, конструктор и методы.

var A = function() {};
A.prototype = {
    _instance_var: 0,

    initialize: function(v) { this._instance_var = v; },

    x: function() {  alert(this._instance_var); }
};

РЕДАКТИРОВАТЬ Не имелось в виду, что область действия переменной была закрытой, я просто пытался проиллюстрировать, как я определяю свои классы в javascript. Имя переменной было изменено, чтобы отразить это.

tvanfosson
источник
2
_instance_var как в свойстве initializeи x methods do not refer to the _instance_var` для Aэкземпляра, но для глобального. Используйте, this._instance_varесли вы намеревались использовать _instance_varсвойствоA экземпляра.
Лекенштейн
2
Самое смешное, что Бенри тоже допустил такую ​​ошибку, которая была обнаружена и через два года: p
Lekensteyn
10

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

Я собрал jsperf, чтобы показать это. Существует существенное различие во времени, которое требуется для создания экземпляра класса, хотя на самом деле это актуально, только если вы создаете много экземпляров.

http://jsperf.com/functions-in-constructor-vs-prototype

Devgr
источник
8

Подумайте о статически типизированном языке, вещи prototypeстатичны, а вещи thisсвязаны с экземплярами.

Wayou
источник