Использование «прототипа» против «этого» в JavaScript? - PullRequest
752 голосов
/ 22 ноября 2008

В чем разница между

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

и

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

Ответы [ 14 ]

450 голосов
/ 22 ноября 2008

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

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

  • Прототип конструктора предоставляет способ обмена методами и значениями между экземплярами с помощью частного свойства [[Prototype]] экземпляра.
  • Функция this устанавливается в зависимости от того, как вызывается функция, или с помощью bind (здесь не обсуждается). Если функция вызывается для объекта (например, myObj.method()), тогда это в методе ссылается на объект. Где this не устанавливается вызовом или использованием 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 устанавливается для ссылки на новый объект, у которого свойство private [[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"} 

Похожие вопросы :

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

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

229 голосов
/ 22 ноября 2008

Как уже говорили другие версии первой версии, использование «this» приводит к тому, что каждый экземпляр класса A имеет свою собственную независимую копию метода функции «x». Принимая во внимание, что использование «прототипа» будет означать, что каждый экземпляр класса 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

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

138 голосов
/ 22 января 2016

Возьмите эти 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__ этого объекта и так далее ... до тех пор, пока цепочка не закончится. При этом название прототип цепи . Конечно, если __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 будут повторяться. Вы что-то замечаете? Ключевое слово repeat. Шаг 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') };

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

a1.hey

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

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

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

57 голосов
/ 22 ноября 2008

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

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

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, но не за пределами объекта.

26 голосов
/ 22 ноября 2008

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

20 голосов
/ 21 сентября 2012

Основная проблема с использованием 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
19 голосов
/ 11 февраля 2016

Каждый объект связан с объектом-прототипом. При попытке получить доступ к несуществующему свойству 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

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

15 голосов
/ 06 января 2014

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

Я думаю, версия 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 ***

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

13 голосов
/ 22 ноября 2008

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

12 голосов
/ 28 июля 2017

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

Function directly on object

Function on prototype

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

...