Когда использовать прототип Javascript вместо обычного формата объекта [duplicate]

$ заменить «От» «Кому» - `find / path / to / folder -type f`

(заменить - утилиту замены строк базы данных MySQL)

720
задан CRABOLO 10 October 2015 в 17:19
поделиться

14 ответов

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

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

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

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

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

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

В случае:

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

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

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

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

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

В этом примере оператор new был добавлен перед выражением функции, чтобы вызвать функцию как конструктор. При вызове с new функция этой функции установлена ​​для ссылки на новый объект, чье личное свойство [[Prototype]] установлено для ссылки на общедоступный прототип конструктора . Поэтому в операторе присваивания свойство x будет создано на этом новом объекте. Когда вызывается как конструктор, функция по умолчанию возвращает свой этот объект , поэтому нет необходимости в отдельном заявлении 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 не является языком низкого уровня. Возможно, не очень важно думать о прототипировании или других шаблонах наследования как способ явного изменения способа выделения памяти.

434
ответ дан 27 revs, 9 users 63% 22 August 2018 в 23:57
поделиться
  • 1
    @keparo: Ты ошибаешься. Каждый объект имеет [внутренний] объект-прототип (который может быть null), но это сильно отличается от свойства prototype, которое находится на функциях и к которому прототип всех экземпляров задается, когда они сконструированы с new. Не могу поверить, что это действительно получило 87 upvotes :-( – Bergi 18 September 2012 в 19:56
  • 2
    "The language is functional" Вы уверены, что это то, что означает функциональность? – phant0m 26 September 2012 в 11:10
  • 3
    Я второй, что сказал @Bergi о прототипах. Функции имеют свойство прототипа. Все объекты, включая функции, имеют другое внутреннее свойство, к которому можно получить доступ с Object.getPrototypeOf (myObject) или с myObject .__ proto__ в некоторых браузерах. Свойство proto указывает родителя объекта в цепочке прототипов (или объект, из которого этот объект наследуется). Свойство prototype (только для функций) указывает объект, который станет родителем любых объектов, которые используют эту функцию для создания новых объектов с использованием нового ключевого слова. – Jim Cooper 13 March 2013 в 16:41
  • 4
    Эта статья довольно ошибочна и смущает, как устанавливается this . Работа над переписыванием. – RobG 5 February 2014 в 03:00
  • 5
    Этот ответ довольно странный и, кажется, полностью упускает суть вопроса. Вопрос, кажется, очень распространен в определении свойств типа внутри конструктора по сравнению с протойпе, но половина ответа о том, что произойдет, если вы использовали A как функцию, а другая половина - о неясной и неортодоксальной способы сделать что-то простое. – JLRishe 20 March 2014 в 09:07

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

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

10
ответ дан Arnav Aggarwal 22 August 2018 в 23:57
поделиться
121
ответ дан Community 22 August 2018 в 23:57
поделиться

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

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

17
ответ дан destoryer 22 August 2018 в 23:57
поделиться
  • 1
    Проголосовал за это за самый прямой ответ на вопрос. – Nick Pineda 22 March 2016 в 15:51
  • 2
    Я полюбил ваш прямой подход! пальцы вверх! – Prince Vijay Pratap 26 March 2016 в 21:29

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

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

http://jsperf.com/functions-in- конструктор-против-прототипа

7
ответ дан Devgr 22 August 2018 в 23:57
поделиться

Позвольте мне дать более полный ответ, который я узнал во время учебного курса 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.

В этом примере, хотя

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

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

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

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

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

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

amy.constructor 's поиск завершен и, следовательно, делегирован 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. 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.

Не стесняйтесь добавлять или редактировать. Еще раз, я мог бы сделать это сообщество wiki возможно.

11
ответ дан Ely 22 August 2018 в 23:57
поделиться
  • 1
    Не стучать очень тщательный пост, но я думал, что OO и Prototypical наследования были разными школами мыслей. – Nick Pineda 22 March 2016 в 15:47
  • 2
    Они есть, но можно «делать OO». с разными приемами / мыслями, не так ли? – Ely 22 March 2016 в 16:22
  • 3
    Не уверен. Многие просто говорят, что прототипная философия просто другая, и многие пытаются сравнить ее с ОО, потому что это школа мысли, к которой многие используют. – Nick Pineda 22 March 2016 в 16:25
  • 4
    Я имею в виду, если вы хотите практиковать стиль OO, а язык предлагает набор методов, которые помогут сделать это, это не обязательно неправильно. – Ely 22 March 2016 в 16:29

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

25
ответ дан Glenn 22 August 2018 в 23:57
поделиться
  • 1
    Обе функции сделают функцию 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; – Spencer Williams 30 April 2016 в 18:32

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

11
ответ дан harropriiz 22 August 2018 в 23:57
поделиться

Как говорили другие, первая версия, используя «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

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

224
ответ дан kaiser 22 August 2018 в 23:57
поделиться
  • 1
    Это то, что я ожидал бы, но когда я создал экземпляр нового объекта после изменения A.x, как показано выше, я покажу «A», если я не использую A как одноэлементный. jsbin.com/omida4/2/edit – jellyfishtree 19 October 2010 в 22:08
  • 2
    Это потому, что мой пример был неправильным. Это было не так в течение двух лет. Вздох. Но точка все еще актуальна. Я обновил пример тем, который действительно работает. Спасибо, что указали это. – Benry 23 October 2010 в 09:04
  • 3
    Это статический метод! : D – user 4 May 2014 в 20:51
  • 4
    да ... «прототип» означает статический или классный уровень .., который будет использоваться всеми экземплярами, созданными ... в то время как «this» - это метод экземпляра, который каждый экземпляр будет иметь свою собственную копию – Aneer Anwar 23 March 2015 в 12:34
  • 5
    Это не статично. Статический, используемый в большинстве языков OO, подразумевает, что нет никакой зависимости от объекта this, который является владельцем метода. то есть метод не имеет объекта, который является его владельцем. В этом случае есть объект this, как показано в классе A в примере. – Ragnagord 27 May 2015 в 09:26

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

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

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, но не вне объекта.

54
ответ дан Matthew Crumley 22 August 2018 в 23:57
поделиться
  • 1
    См. Это сообщение: stackoverflow.com/a/1441692/654708 для примера о том, как обращаться к частным членам через прототипы. – GFoley83 12 March 2014 в 02:28
  • 2
    @ GFoley83, который отвечает not , показывает, что методы прототипа могут обращаться только к "public" свойства данного объекта. Только привилегированные методы (а не прототип) могут получить доступ к закрытым членам. – Alnitak 17 December 2015 в 23:56

В чем разница? => Много.

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

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

14
ответ дан oozzal 22 August 2018 в 23:57
поделиться
  • 1
    +1 Менее сложный и более графический ответ, чем другие. Но вы должны разработать немного больше, прежде чем предоставлять эти (хорошие) примеры. – yerforkferchips 31 July 2014 в 13:21
  • 2
    Я не уверен, что «эта версия используется для включения инкапсуляции, т. Е. Скрытия данных». Если свойство внутри функции определено с использованием "this" как в "this.myProperty = ...", такое свойство не является "частным" и могут быть доступны из объектов вне класса с использованием «new». – NoChance 2 October 2017 в 20:42

Конечная проблема с использованием 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
ответ дан tarkabak 22 August 2018 в 23:57
поделиться

Я считаю, что @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); }
};

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

8
ответ дан tvanfosson 22 August 2018 в 23:57
поделиться
  • 1
    _instance_var, как и в свойствах initialize и x methods do not refer to the _instance_var` экземпляра A, но в глобальном. Используйте this._instance_var, если вы хотите использовать свойство _instance_var экземпляра A. – Lekensteyn 8 April 2011 в 17:40
  • 2
    @Lek - oops. исправлено. – tvanfosson 8 April 2011 в 18:46
  • 3
    Самое забавное: Бенри тоже сделал такую ​​ошибку, которая была обнаружена и через два года: p – Lekensteyn 8 April 2011 в 20:04
  • 4
    @Lek - нет модульных тестов на SO. :-( – tvanfosson 8 April 2011 в 21:12

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

3
ответ дан Wayou 22 August 2018 в 23:57
поделиться
Другие вопросы по тегам:

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