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

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

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

и

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

концепция этого ключевого слова подробно объясняется здесь scotch.io/@alZami/understanding-this-in-javascript

AL-zami 10.09.2017 22:11

Чтение «этой» ветки показывает, насколько ужасен JS и насколько его принципы непонятны многим разработчикам. Что не так с более легкими для понимания языками? Я думаю, что пора разработчикам поднять свой голос, чтобы отвергнуть запутанные технологии, которые не приносят или не имеют большого значения ни для бизнеса, ни для разработки.

NoChance 02.10.2017 20:09

На объекте: a1.x !== a2.x; на прототипе: a1.x === a2.x

Juan Mendes 03.06.2018 14:57
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
797
4
120 571
13

Ответы 13

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

Оба сделают функцию 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.04.2016 21:32

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

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

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 12.03.2014 04:28

@ GFoley83, этот ответ показывает, что нет - методы прототипа могут получить доступ только к «общедоступным» свойствам данного объекта. Только привилегированные методы (не в прототипе) могут получить доступ к закрытым членам.

Alnitak 18.12.2015 01:56

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

Lekensteyn 08.04.2011 20:40

Самое смешное, что Бенри тоже допустил такую ​​ошибку, которая также была обнаружена через два года: p

Lekensteyn 08.04.2011 23:04

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

jellyfishtree 20.10.2010 01:08

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

Benry 23.10.2010 12:04

Это статический метод! : D

user1980175 04.05.2014 23:51

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

Aneer Dev 23.03.2015 14:34

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

JimmyMcHoover 27.05.2015 12:26

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

Конечная проблема с использованием 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 Гораздо менее запутанный и более наглядный ответ, чем другие. Но вам следует немного уточнить, прежде чем приводить эти (хорошие) примеры.

yerforkferchips 31.07.2014 16:21

Я не уверен, что «эта версия используется для включения инкапсуляции, т.е. сокрытия данных». Если свойство внутри функции определено с использованием this, например, this.myProperty = ..., такое свойство не является частным, и к нему можно получить доступ из объектов вне класса с помощью new.

NoChance 02.10.2017 23:42

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

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

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

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

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

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

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

Шаблон Object Decorator

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

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.

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

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

Nick Pineda 22.03.2016 17:47

Они есть, но можно "делать ОО" с разными техниками / мыслями, не так ли?

Ely 22.03.2016 18:22

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

Nick Pineda 22.03.2016 18:25

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

Ely 22.03.2016 18:29

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

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

Проголосовал за наиболее точный ответ на вопрос.

Nick Pineda 22.03.2016 17:51

Мне понравился ваш прямой подход !! пальцы вверх!

Prince Vijay Pratap 26.03.2016 23:29

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

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

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 на прототип занимает примерно половину времени.

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

frzsombor 10.11.2020 22:14

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

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

Другие вопросы по теме