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



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Первый пример изменяет интерфейс только для этого объекта. Во втором примере изменяется интерфейс для всех объектов этого класса.
Оба сделают функцию 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;.
В большинстве случаев они по сути одинаковы, но вторая версия экономит память, потому что существует только один экземпляр функции вместо отдельной функции для каждого объекта.
Причина использования первой формы - доступ к «частным членам». Например:
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, но не за пределами объекта.
См. Этот пост: stackoverflow.com/a/1441692/654708 для примера того, как получить доступ к закрытым членам через прототипы.
@ GFoley83, этот ответ показывает, что нет - методы прототипа могут получить доступ только к «общедоступным» свойствам данного объекта. Только привилегированные методы (не в прототипе) могут получить доступ к закрытым членам.
Я считаю, что @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. Имя переменной было изменено, чтобы отразить это.
_instance_var как в свойстве initialize и x methods do not refer to the _instance_var` на экземпляре A, но в глобальном. Используйте this._instance_var, если вы хотели использовать свойство _instance_var экземпляра A.
Самое смешное, что Бенри тоже допустил такую ошибку, которая также была обнаружена через два года: p
Как говорили другие в первой версии, использование «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
Как уже упоминалось, есть разные причины для выбора того или иного метода. Мой образец предназначен только для того, чтобы наглядно продемонстрировать разницу.
Это то, чего я ожидал, но когда я создал экземпляр нового объекта после изменения A.x, как указано выше, я все равно отображаю «A», если я не использую A как синглтон. jsbin.com/omida4/2/edit
Это потому, что мой пример был неправильным. Это было неправильно всего два года. Вздох. Но суть все еще в силе. Я обновил пример тем, что действительно работает. Спасибо, что указали на это.
Это статический метод! : D
да ... 'прототип' означает статический уровень или уровень класса ... который будет использоваться всеми созданными экземплярами ... в то время как 'this' - это метод экземпляра, каждый экземпляр которого будет иметь свою собственную копию
Это не статично. Статический, используемый в большинстве объектно-ориентированных языков, подразумевает отсутствие зависимости от объекта this, который является владельцем метода. т.е. у метода нет объекта, который бы являлся его владельцем. В этом случае есть объект this, как показано в классе A в примере.
Прототип - это шаблон класса; что применимо ко всем будущим его экземплярам. Тогда как это конкретный экземпляр объекта.
Конечная проблема с использованием 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.info(subClass.getText()); // Hello BaseClass!
subClass.setText("Hello"); // setText is already overridden
console.info(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.info(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.info("setPrivateVar value set to: " + value);
// param1 is still here, possible memory leak
console.info("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.info(a.publicVar); // 1
Какая разница? => Много.
Думаю, версия 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 можно применить следующим образом:
У разных взрослых разный возраст, но все взрослые имеют одинаковые права. Итак, мы добавляем его с помощью прототипа, а не this.
AdultPerson.prototype.getRights = function() {
// Should be valid
return this.isValid() && ['Booze', 'Drive'];
};
Теперь посмотрим на реализацию.
var p1 = new AdultPerson;
p1.setAge(12); // ( age = false )
console.info(p1.getRights()); // false ( Kid alert! )
p1.setAge(19); // ( age = 19 )
console.info(p1.getRights()); // ['Booze', 'Drive'] ( Welcome AdultPerson )
var p2 = new AdultPerson;
p2.setAge(45);
console.info(p2.getRights()); // The same getRights() method, *** not a new copy of it ***
Надеюсь это поможет.
+1 Гораздо менее запутанный и более наглядный ответ, чем другие. Но вам следует немного уточнить, прежде чем приводить эти (хорошие) примеры.
Я не уверен, что «эта версия используется для включения инкапсуляции, т.е. сокрытия данных». Если свойство внутри функции определено с использованием this, например, this.myProperty = ..., такое свойство не является частным, и к нему можно получить доступ из объектов вне класса с помощью new.
Как обсуждалось в других ответах, это действительно соображение производительности, потому что функция в прототипе используется всеми экземплярами, а не функция, создаваемая для каждого экземпляра.
Я собрал jsperf, чтобы показать это. Существует значительная разница во времени, необходимом для создания экземпляра класса, хотя на самом деле это актуально только в том случае, если вы создаете много экземпляров.
Позвольте мне дать вам более исчерпывающий ответ, который я узнал во время курса обучения 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 представляет собой instanceofCar. Оператор 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.info(Car.prototype.constructor);
console.info(amy.constructor);
console.info(amy instanceof Car);
Некоторые разработчики могут запутаться вначале. См. Пример ниже:
var Dog = function() {
return {legs: 4, bark: alert};
};
var fido = Dog();
console.info(fido instanceof Dog);
Оператор instanceof возвращает false, потому что прототип Dog нельзя найти нигде в цепочке прототипов fido. 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.
Не стесняйтесь добавлять или редактировать. Еще раз завершив, я мог бы сделать это вики сообщества.
Не хочу выбивать очень обстоятельный пост, но я думал, что объектно-ориентированное проектирование и прототипное наследование - это, по сути, разные школы мысли.
Они есть, но можно "делать ОО" с разными техниками / мыслями, не так ли?
Не уверен, правда. Многие просто говорят, что прототипическая философия просто другая, и многие пытаются сравнить ее с объектно-ориентированной архитектурой, потому что это школа мысли, к которой многие привыкли.
Я имею в виду, что если вы хотите практиковать объектно-ориентированный стиль, а язык предлагает набор методов, которые помогают в этом, это не обязательно неправильно.
Каждый объект связан с объектом-прототипом. При попытке получить доступ к несуществующему свойству 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
В заключение в первом примере копия функции назначается каждому экземпляру. Во втором примере одна копия функции используется всеми экземплярами.
Проголосовал за наиболее точный ответ на вопрос.
Мне понравился ваш прямой подход !! пальцы вверх!
Я знаю, что на это есть смертельный ответ, но я хочу показать реальный пример разницы в скорости.
Функция непосредственно на объекте:
function ExampleFn() {
this.print = function() {
console.info("Calling print! ");
}
}
var objects = [];
console.time('x');
for (let i = 0; i < 2000000; i++) {
objects.push(new ExampleFn());
}
console.timeEnd('x');
//x: 1151.960693359375msФункция на прототипе:
function ExampleFn() {
}
ExampleFn.prototype.print = function() {
console.info("Calling print!");
}
var objects = [];
console.time('y');
for (let i = 0; i < 2000000; i++) {
objects.push(new ExampleFn());
}
console.timeEnd('y');
//x: 617.866943359375msЗдесь мы создаем 2000000 новых объектов с помощью метода print в Chrome. Мы храним каждый объект в массиве. Установка print на прототип занимает примерно половину времени.
Хорошая работа, указывая на это! Однако в следующий раз НЕ вставляйте снимки экрана своего кода, а вместо этого скопируйте и вставьте код, чтобы другие могли легко протестировать / использовать его. На этот раз я заменил вам изображения.
Подумайте о статически типизированном языке, вещи на prototype статичны, а вещи на this связаны с экземплярами.
Когда вы используете прототип, функция будет загружена в память только один раз (независимо от количества создаваемых вами объектов), и вы можете переопределить функцию, когда захотите.
связанные: Определение методов через прототип против использования этого в конструкторе - действительно ли разница в производительности?, и хорошие ответы на дубликаты: Объявление метода в конструкторе или прототипе, это против прототипа