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

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

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

и

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

Ответы [ 14 ]

12 голосов
/ 10 мая 2015

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

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

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

Я верю, что @ Мэтью Крамли прав. Они функционально , если не структурно, эквивалентны. Если вы используете 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. Имя переменной было изменено, чтобы отразить это.

9 голосов
/ 14 августа 2014

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

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

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

6 голосов
/ 19 декабря 2017

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

...