Как работает JavaScript .prototype?


2041

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

var obj = new Object();
obj.prototype.test = function() { alert('Hello?'); };
var obj2 = new obj();
obj2.test();

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

Но какова точная цель этого свойства .prototype в JavaScript? Как это относится к созданию объектов?

Обновление: правильный путь

var obj = new Object(); // not a functional object
obj.prototype.test = function() { alert('Hello?'); }; // this is wrong!

function MyObject() {} // a first class functional object
MyObject.prototype.test = function() { alert('OK'); } // OK

Также эти слайды очень помогли.


78
У Джона Резига есть несколько слайдов о прототипах функций, которые были мне полезны при изучении темы (вы также можете внести изменения в код и посмотреть, что произойдет ...) http://ejohn.org/apps/learn/#64
Джон Фостер

5
Отличный справочный материал, для сохранения информативности этого вопроса, возможно, разместите некоторые комментарии с сайта Джона на ваш ответ на случай, если его сайт изменится так, что ваша ссылка больше не будет доступна. В любом случае +1, помог мне.
Крис

95
+1 за вашу ссылку на слайд № 64 JavaScript Джона Резига . Начало было очень полезным, и я чувствую, что правильно понимаю прототипы.
платный ботаник

4
Нужен ли нам действительно функциональный объект для применения прототипа? если да, то почему?
Anshul

6
Это может помочь вам: webdeveasy.com/javascript-prototype
Naor

Ответы:


1007

Каждый объект JavaScript имеет внутренний «слот», который называется [[Prototype]]либо, nullлибо значение object. Вы можете представить слот как свойство объекта, внутреннего для движка JavaScript, скрытого от кода, который вы пишете. Квадратные скобки вокруг [[Prototype]]являются преднамеренными и являются соглашением спецификации ECMAScript для обозначения внутренних слотов.

Значение, на которое указывает [[Prototype]]объект, в просторечии известно как «прототип этого объекта».

Если вы обращаетесь к свойству через точку ( obj.propName) или скобку ( obj['propName']), и у объекта нет такого свойства напрямую (т. Е. Собственное свойство , которое можно проверить с помощью obj.hasOwnProperty('propName')), среда выполнения ищет свойство с этим именем в объекте, на который ссылаются самого [[Prototype]]вместо этого. Если свойство [[Prototype]] также не имеет такого свойства, [[Prototype]]оно проверяется по очереди и так далее. Таким образом, цепочка прототипов исходного объекта обходится, пока не будет найдено совпадение или пока не будет достигнут его конец. В верхней части цепочки прототипов находится nullстоимость.

Современные реализации JavaScript предоставляют доступ для чтения и / или записи [[Prototype]]в следующие способы:

  1. new операторе (конфигурирует прототип цепь на объекте по умолчанию возвращается из функции конструктора),
  2. extendsКлючевое слово (настраивает цепочку прототипов при использовании синтаксиса класса),
  3. Object.create установит предоставленный аргумент как [[Prototype]] качестве результирующего объекта,
  4. Object.getPrototypeOfи Object.setPrototypeOf(получить / установить [[Prototype]] после создания объекта), и
  5. Свойство стандартизированного метода доступа (т.е. getter / setter) по имени __proto__(аналогично 4.)

Object.getPrototypeOfи Object.setPrototypeOfпредпочтительнее __proto__, отчасти потому , что поведение o.__proto__ является необычным когда объект имеет прототип null.

Объект [[Prototype]] изначально устанавливается при создании объекта.

Если вы создаете новый объект через new Func(), [[Prototype]]по умолчанию объект будет установлен на объект, на который ссылается Func.prototype.

Обратите внимание, что, следовательно, все классы и все функции, которые могут использоваться с newоператором, имеют свойство, названное .prototypeв дополнение к их собственному [[Prototype]]внутреннему слоту. Это двойное использование слова «прототип» является источником бесконечной путаницы среди новичков в языке.

Использование newфункций конструктора позволяет нам моделировать классическое наследование в JavaScript; хотя система наследования JavaScript, как мы уже видели, прототипична, а не основана на классах.

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

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

Вот один из способов:

function Child() {}
function Parent() {}
Parent.prototype.inheritedMethod = function () { return 'this is inherited' }

function inherit(child, parent) {
  child.prototype = Object.create(parent.prototype)
  child.prototype.constructor = child
  return child;
}

Child = inherit(Child, Parent)
const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'

... и вот еще один способ:

function Child() {}
function Parent() {}
Parent.prototype.inheritedMethod = function () { return 'this is inherited' }

function inherit(child, parent) {
    function tmp() {}
    tmp.prototype = parent.prototype
    const proto = new tmp()
    proto.constructor = child
    child.prototype = proto
    return child
}

Child = inherit(Child, Parent)
const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'

Синтаксис класса, представленный в ES2015, упрощает работу, предоставляя extends «единственно верный способ» конфигурировать цепочку прототипов для имитации классического наследования в JavaScript.

Итак, аналогично приведенному выше коду, если вы используете синтаксис класса для создания нового объекта следующим образом:

class Parent { inheritedMethod() { return 'this is inherited' } }
class Child extends Parent {}

const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'

... результирующий объект [[Prototype]]будет установлен на экземпляр Parentкоторого [[Prototype]], в свою очередь, является Parent.prototype.

Наконец, если вы создадите новый объект через Object.create(foo), результирующий объект [[Prototype]]будет установлен в foo.


1
Итак, я делаю что-то не так, определяя новые свойства для свойства prototype в моем коротком фрагменте?
Джон Лейдгрен

3
Я думаю, что это означает, что функциональные объекты должны быть первоклассными гражданами.
Джон Лейдгрен

8
Я ненавижу нестандартные вещи, особенно в языках программирования, почему даже есть прото, когда оно явно не нужно?
Джон Лейдгрен

1
@John __proto__ необходим для внутреннего использования только интерпретатором JS. Каждый объект должен знать, какие свойства и методы являются частью прототипа, а какие - частью самого объекта. Интерпретатор JS должен иметь возможность вызывать прототипные методы объекта.
BMiner

17
обратите внимание, что использование [[Prototype]] является преднамеренным - ECMA-262 заключает имена внутренних свойств в двойные квадратные скобки
Christoph

1798

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

В JavaScript вы сначала создаете объект (нет понятия класса), затем вы можете дополнить свой собственный объект или создать из него новые объекты. Это не сложно, но немного чуждо и трудно усваивается для того, кто привык к классическому образу жизни.

Пример:

//Define a functional object to hold persons in JavaScript
var Person = function(name) {
  this.name = name;
};

//Add dynamically to the already defined object a new getter
Person.prototype.getName = function() {
  return this.name;
};

//Create a new object of type Person
var john = new Person("John");

//Try the getter
alert(john.getName());

//If now I modify person, also John gets the updates
Person.prototype.sayMyName = function() {
  alert('Hello, my name is ' + this.getName());
};

//Call the new method on john
john.sayMyName();

До сих пор я расширял базовый объект, теперь я создаю другой объект, а затем наследую от Person.

//Create a new object of type Customer by defining its constructor. It's not 
//related to Person for now.
var Customer = function(name) {
    this.name = name;
};

//Now I link the objects and to do so, we link the prototype of Customer to 
//a new instance of Person. The prototype is the base that will be used to 
//construct all new instances and also, will modify dynamically all already 
//constructed objects because in JavaScript objects retain a pointer to the 
//prototype
Customer.prototype = new Person();     

//Now I can call the methods of Person on the Customer, let's try, first 
//I need to create a Customer.
var myCustomer = new Customer('Dream Inc.');
myCustomer.sayMyName();

//If I add new methods to Person, they will be added to Customer, but if I
//add new methods to Customer they won't be added to Person. Example:
Customer.prototype.setAmountDue = function(amountDue) {
    this.amountDue = amountDue;
};
Customer.prototype.getAmountDue = function() {
    return this.amountDue;
};

//Let's try:       
myCustomer.setAmountDue(2000);
alert(myCustomer.getAmountDue());

Хотя, как сказано, я не могу вызвать setAmountDue (), getAmountDue () для Person.

//The following statement generates an error.
john.setAmountDue(1000);

352
Я думаю, что ответы на stackoverflow интересны не только оригинальному постеру, но также и большому сообществу других людей, скрывающихся или прибывающих из поисков. И я был одним из них, и я получил выгоду от старых постов. Я думаю, что мог бы внести свой вклад в другие ответы, добавив несколько примеров кода. По поводу вашего вопроса: если вы пропустите новое, это не сработает. когда я вызываю myCustomer.sayMyName (), возвращается «myCustomer.sayMyName не является функцией». Самый простой способ - поэкспериментировать с firebug и посмотреть, что получится.
stivlo

7
Насколько я понимаю, var Person = function (name) {...}; определяет функцию конструктора, способную создавать объекты Person. Таким образом, Объекта пока нет, только Person назначена функция анонимного конструктора. Это очень хорошее объяснение: helephant.com/2008/08/how-javascript-objects-work
stivlo

17
ВНИМАНИЕ: Этот ответ игнорирует тот факт, что конструктор родительского класса не вызывается для каждого экземпляра. Единственная причина, по которой это работает, заключается в том, что он сделал одно и то же (установил имя) как в дочернем, так и в родительском конструкторе. Более подробное объяснение распространенных ошибок, допущенных при попытке наследования в JavaScript (и окончательное решение), см. В этом посте о переполнении стека
Аарен Кордова,

3
Я заметил, что в этом ответе также не упоминается, что при использовании «new Person ()» в качестве прототипа вы фактически устанавливаете свойство экземпляра «name» для «Person» как статическое свойство «Customer» (так что все Customer экземпляры будут иметь такое же свойство). Хотя это хороший базовый пример, НЕ ДЕЛАЙТЕ ЭТОГО. :) Создайте новую анонимную функцию, которая будет действовать как «мост», установив для ее прототипа «Person.prototype», затем создайте из него экземпляр и установите вместо него «Customer.prototype» для этого анонимного экземпляра.
Джеймс Уилкинс

10
В отношении Customer.prototype = new Person();строки MDN показывает пример использования Customer.prototype = Object.create(Person.prototype)и заявляет, что «Распространенной ошибкой здесь является использование« new Person () »» . источник
Рафаэль Эйнг

186

Это очень простая объектная модель на основе прототипа, которая будет рассмотрена в качестве примера при объяснении без комментариев:

function Person(name){
    this.name = name;
}
Person.prototype.getName = function(){
    console.log(this.name);
}
var person = new Person("George");

Есть несколько важных моментов, которые мы должны рассмотреть, прежде чем перейти к концепции прототипа.

1- Как на самом деле работают функции JavaScript:

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

Допустим, мы хотим создать Personобъектную модель. но в этом шаге я буду пытаться сделать то же самое точное без использования prototypeи newключевых слов .

Таким образом , в этом шаге functions, objectsиthis ключевое слово, все у нас есть.

Первый вопрос заключается в том, как thisключевое слово может быть полезным без использования newключевого слова. .

Итак, чтобы ответить на это, скажем, у нас есть пустой объект и две функции, такие как:

var person = {};
function Person(name){  this.name = name;  }

function getName(){
    console.log(this.name);
}

и теперь без использования newключевого слова как мы могли бы использовать эти функции. Таким образом, у JavaScript есть 3 различных способа сделать это:

а. Первый способ - просто вызвать функцию как обычную функцию:

Person("George");
getName();//would print the "George" in the console

в этом случае это будет текущий объект контекста, который обычно является глобальным windowобъектом в браузере или GLOBALвNode.js . Это означает, что у нас будет window.name в браузере или GLOBAL.name в Node.js с значением «George».

б. Мы можем прикрепить их к объекту, так как его свойства

- Самый простой способ сделать это - изменить пустой personобъект, например:

person.Person = Person;
person.getName = getName;

таким образом мы можем назвать их как:

person.Person("George");
person.getName();// -->"George"

и теперь personобъект похож на:

Object {Person: function, getName: function, name: "George"}

- Другой способ прикрепить свойство к объекту - использовать prototypeэтот объект, который можно найти в любом объекте JavaScript с именем __proto__, и я попытался объяснить это немного в итоговой части. Таким образом, мы могли бы получить аналогичный результат, выполнив:

person.__proto__.Person = Person;
person.__proto__.getName = getName;

Но таким образом, что мы на самом деле делаем, это модифицируем Object.prototype, потому что всякий раз, когда мы создаем объект JavaScript с использованием literals ( { ... }), он создается на основе Object.prototype, что означает, что он присоединяется к вновь созданному объекту как именованный атрибут __proto__, поэтому, если мы его изменим Как мы уже делали в предыдущем фрагменте кода, все объекты JavaScript будут изменены, что не является хорошей практикой. Итак, что может быть лучше практики сейчас:

person.__proto__ = {
    Person: Person,
    getName: getName
};

и теперь другие объекты в мире, но это все еще не кажется хорошей практикой. Таким образом, у нас есть еще одно решение, но чтобы использовать это решение, мы должны вернуться к той строке кода, где personбыл создан объект ( var person = {};), а затем изменить его следующим образом:

var propertiesObject = {
    Person: Person,
    getName: getName
};
var person = Object.create(propertiesObject);

он создает новый JavaScript Objectи присоединяет его propertiesObjectк __proto__атрибуту. Итак, чтобы убедиться, что вы можете сделать:

console.log(person.__proto__===propertiesObject); //true

Но сложность заключается в том, что у вас есть доступ ко всем свойствам, определенным __proto__на первом уровне personобъекта (подробнее см. Раздел «Сводка»).


как вы видите, использование любого из этих двух способов thisбудет точно указывать на personобъект.

с. У JavaScript есть другой способ предоставить функцию this, которая использует call или apply для вызова функции.

Метод apply () вызывает функцию с заданным значением this и аргументами, представленными в виде массива (или объекта, подобного массиву).

а также

Метод call () вызывает функцию с заданным значением this и аргументами, предоставляемыми индивидуально.

таким образом, который мой любимый, мы можем легко вызывать наши функции как:

Person.call(person, "George");

или

//apply is more useful when params count is not fixed
Person.apply(person, ["George"]);

getName.call(person);   
getName.apply(person);

Эти 3 метода являются важными начальными шагами для выяснения функциональности .prototype.


2- Как работает newключевое слово?

это второй шаг для понимания .prototypeфункциональности. Это то, что я использую для имитации процесса:

function Person(name){  this.name = name;  }
my_person_prototype = { getName: function(){ console.log(this.name); } };

в этой части я попытаюсь предпринять все шаги, которые предпринимает JavaScript, без использования newключевого слова и prototype, когда вы используете newключевое слово. поэтому, когда мы это делаем new Person("George"), Personфункция служит конструктором. Вот что делает JavaScript, один за другим:

а. во-первых, он создает пустой объект, в основном пустой хеш, например:

var newObject = {};

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

Здесь мы имеем my_person_prototypeаналог объекта-прототипа.

for(var key in my_person_prototype){
    newObject[key] = my_person_prototype[key];
}

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


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

var newObject = Object.create(my_person_prototype);
//here you can check out the __proto__ attribute
console.log(newObject.__proto__ === my_person_prototype); //true
//and also check if you have access to your desired properties
console.log(typeof newObject.getName);//"function"

Теперь мы можем вызвать getNameфункцию в нашем my_person_prototype:

newObject.getName();

с. затем он передает этот объект конструктору,

мы можем сделать это с нашим образцом, как:

Person.call(newObject, "George");

или

Person.apply(newObject, ["George"]);

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

теперь конечный результат перед имитацией других шагов: Object {name: "George"}


Резюме:

По сути, когда вы используете ключевое слово new для функции, вы вызываете ее, и эта функция служит конструктором, поэтому, когда вы говорите:

new FunctionName()

JavaScript внутренне делает объект, пустой хэш и затем он дает этот объект в конструктор, то конструктор может делать все , что хочет, потому что это внутри этого конструктора объект , который был только что создан , а затем он дает вам этот объект, конечно , если вы не использовали оператор return в своей функции или если вы поставили return undefined;в конце тела функции.

Поэтому, когда JavaScript отправляется на поиск свойства объекта, первое, что он делает, это ищет его на этом объекте. И затем есть секретное свойство, [[prototype]]которое у нас обычно есть, __proto__и это свойство - то, на что JavaScript смотрит дальше. И когда он просматривает __proto__, поскольку он снова является другим объектом JavaScript, он имеет свой собственный __proto__атрибут, он поднимается вверх и вверх, пока не достигнет точки, где следующий __proto__является нулевым. Точка - единственный объект в JavaScript, __proto__атрибут которого равен нулю, это Object.prototypeобъект:

console.log(Object.prototype.__proto__===null);//true

и вот как наследование работает в JavaScript.

Прототип цепи

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


6
a) Пожалуйста, не объясняйте прототипы, копируя свойства. b) Установка внутреннего [[prototype]] происходит до применения функции-конструктора к экземпляру. Пожалуйста, измените этот порядок. c) В этом вопросе jQuery совершенно оффтопичен
Берги,

1
@ Берги: спасибо за указание, я буду признателен, если вы дадите мне знать, если это нормально сейчас.
Мехран Хатами

7
Можете ли вы сделать это просто? Вы правы по всем пунктам, но студенты, которые читают это объяснение, могут быть действительно смущены в первый раз. возьмите любой более простой пример и позвольте коду объяснить себя или добавьте несколько комментариев, чтобы прояснить, что вы имеете в виду.
14:00

2
@PM: Спасибо за ваш отзыв. Я пытался сделать это как можно более простым, но я думаю, что вы правы, у него все еще есть некоторые неясные моменты. Поэтому я постараюсь изменить его, а также быть более информативным. :)
Мехран Хатами

1
@AndreaMattioli, потому что таким образом вы создаете совершенно новый объект и переопределяете старый, который также может использоваться другими объектами. Заменив, __proto__вы сначала сотрете все свойства прототипа верхнего уровня, а затем у вас будет свежая база прототипов, которая больше не будет доступна, если вы не поделитесь ею.
Мехран Хатами

77

Семь Коанов-прототипов

Когда Чиро Сан спустился с Горы Огненной Лисы после глубокой медитации, его разум был чист и спокоен.

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


0) Две разные вещи можно назвать «прототипом»:

  • свойство прототипа, как в obj.prototype

  • внутреннее свойство прототипа, обозначенное как [[Prototype]] в ES5 .

    Его можно получить через ES5 Object.getPrototypeOf().

    Firefox делает его доступным через __proto__свойство как расширение. ES6 теперь упоминает некоторые дополнительные требования для __proto__.


1) Эти понятия существуют, чтобы ответить на вопрос:

Когда я делаю obj.property, где JS ищет .property?

Интуитивно понятно, что классическое наследование должно влиять на поиск свойств.


2)

  • __proto__используется для .поиска свойства точки, как в obj.property.
  • .prototypeэто не используется для поиска непосредственно, только косвенно , как он определяет , __proto__при создании объекта с new.

Порядок поиска:

  • objсвойства добавлены с obj.p = ...илиObject.defineProperty(obj, ...)
  • свойства obj.__proto__
  • свойства obj.__proto__.__proto__ и т. д.
  • если __proto__есть null, верните undefined.

Это так называемая цепь прототипов .

Вы можете избежать .поиска obj.hasOwnProperty('key')иObject.getOwnPropertyNames(f)


3) Есть два основных способа установки obj.__proto__:

  • new:

    var F = function() {}
    var f = new F()

    затем newустановил:

    f.__proto__ === F.prototype

    Вот где .prototypeпривыкаешь.

  • Object.create:

     f = Object.create(proto)

    наборы:

    f.__proto__ === proto

4) код:

var F = function(i) { this.i = i }
var f = new F(1)

Соответствует следующей диаграмме (некоторые Numberвещи опущены):

(Function)       (  F  )                                      (f)----->(1)
 |  ^             | | ^                                        |   i    |
 |  |             | | |                                        |        |
 |  |             | | +-------------------------+              |        |
 |  |constructor  | |                           |              |        |
 |  |             | +--------------+            |              |        |
 |  |             |                |            |              |        |
 |  |             |                |            |              |        |
 |[[Prototype]]   |[[Prototype]]   |prototype   |constructor   |[[Prototype]]
 |  |             |                |            |              |        |
 |  |             |                |            |              |        |
 |  |             |                | +----------+              |        |
 |  |             |                | |                         |        |
 |  |             |                | | +-----------------------+        |
 |  |             |                | | |                                |
 v  |             v                v | v                                |
(Function.prototype)              (F.prototype)                         |
 |                                 |                                    |
 |                                 |                                    |
 |[[Prototype]]                    |[[Prototype]]          [[Prototype]]|
 |                                 |                                    |
 |                                 |                                    |
 | +-------------------------------+                                    |
 | |                                                                    |
 v v                                                                    v
(Object.prototype)                                       (Number.prototype)
 | | ^
 | | |
 | | +---------------------------+
 | |                             |
 | +--------------+              |
 |                |              |
 |                |              |
 |[[Prototype]]   |constructor   |prototype
 |                |              |
 |                |              |
 |                | -------------+
 |                | |
 v                v |
(null)           (Object)

Эта диаграмма показывает множество предопределенных языковых узлов объектов:

  • null
  • Object
  • Object.prototype
  • Function
  • Function.prototype
  • 1
  • Number.prototype(можно найти с (1).__proto__круглыми скобками, обязательными для удовлетворения синтаксиса)

Наши 2 строки кода создали только следующие новые объекты:

  • f
  • F
  • F.prototype

iтеперь является свойством, fпотому что, когда вы делаете:

var f = new F(1)

он оценивает Fс thisтого значением , которое newбудет возвращать, который затем получает назначенное f.


5) .constructor обычно приходит F.prototypeчерез .поиск:

f.constructor === F
!f.hasOwnProperty('constructor')
Object.getPrototypeOf(f) === F.prototype
F.prototype.hasOwnProperty('constructor')
F.prototype.constructor === f.constructor

Когда мы пишем f.constructor, JavaScript делает. поиск следующим образом:

  • f не имеет .constructor
  • f.__proto__ === F.prototype имеет .constructor === F , так что возьми

Результат f.constructor == Fинтуитивно правильный, так Fкак используется для построенияf , например, заданных полей, как в классических языках ООП.


6) Классический синтаксис наследования может быть достигнут путем манипулирования цепочками прототипов.

ES6 добавляет classи extendsключевые слова, которые в основном синтаксис для ранее возможного манипулирования прототипа безумия.

class C {
    constructor(i) {
        this.i = i
    }
    inc() {
        return this.i + 1
    }
}

class D extends C {
    constructor(i) {
        super(i)
    }
    inc2() {
        return this.i + 2
    }
}
// Inheritance syntax works as expected.
c = new C(1)
c.inc() === 2
(new D(1)).inc() === 2
(new D(1)).inc2() === 3
// "Classes" are just function objects.
C.constructor === Function
C.__proto__ === Function.prototype
D.constructor === Function
// D is a function "indirectly" through the chain.
D.__proto__ === C
D.__proto__.__proto__ === Function.prototype
// "extends" sets up the prototype chain so that base class
// lookups will work as expected
var d = new D(1)
d.__proto__ === D.prototype
D.prototype.__proto__ === C.prototype
// This is what `d.inc` actually does.
d.__proto__.__proto__.inc === C.prototype.inc
// Class variables
// No ES6 syntax sugar apparently:
// http://stackoverflow.com/questions/22528967/es6-class-variable-alternatives
C.c = 1
C.c === 1
// Because `D.__proto__ === C`.
D.c === 1
// Nothing makes this work.
d.c === undefined

Упрощенная схема без всех предопределенных объектов:

(c)----->(1)
 |   i
 |
 |
 |[[Prototype]]
 |
 |
 v    __proto__
(C)<--------------(D)         (d)
| |                |           |
| |                |           |
| |prototype       |prototype  |[[Prototype]] 
| |                |           |
| |                |           |
| |                | +---------+
| |                | |
| |                | |
| |                v v
|[[Prototype]]    (D.prototype)--------> (inc2 function object)
| |                |             inc2
| |                |
| |                |[[Prototype]]
| |                |
| |                |
| | +--------------+
| | |
| | |
| v v
| (C.prototype)------->(inc function object)
|                inc
v
Function.prototype

Давайте на минутку изучим, как работает следующее:

c = new C(1)
c.inc() === 2

Первые наборы строк c.iв1 , как описано в «4)».

На второй строке, когда мы делаем:

c.inc()
  • .incнайдено по [[Prototype]]цепочке: c-> C-> C.prototype->inc
  • когда мы вызываем функцию в Javascript as X.Y(), JavaScript автоматически устанавливается thisравным Xвнутри Y()вызова функции!

Та же самая логика также объясняет d.incиd.inc2 .

В этой статье https://javascript.info/class#not-just-a-syntax-sugar упоминаются дополнительные эффекты, которые classстоит знать. Некоторые из них могут быть недостижимы без classключевого слова (проверьте TODO):


1
@tomasb спасибо! «Я не знаю, где вы это взяли»: после того, как я увидел несколько из этих динамических языков, я заметил, что наиболее важно в их системе классов то, как .работает поиск (и сколько копий данных сделано) , Поэтому я решил понять этот момент. Остальное - Google + сообщения в блоге + переводчик Js под рукой. :)
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功

1
Я до сих пор не понимаю, почему g.constructor === Object, потому что вы сказали, что "4) Когда вы делаете f = new F, new также устанавливает f.constructor = F". Не могли бы вы объяснить мне больше? В любом случае, это лучший ответ, который я ищу. Спасибо огромное!
nguyenngoc101

@ nguyenngoc101 спасибо! Эта sets f.constructor = Fчасть была явно ошибочной и противоречила другим разделам: .constructorона найдена путем .поиска в цепочке прототипов. Исправлено сейчас.
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功

из всего обсуждения, что я получаю (пришло из классического наследования), если я создаю функцию-конструктор и пытаюсь создать ее экземпляр с помощью оператора new, я получу только те методы и свойства, которые были присоединены к объекту Proto, поэтому необходимо присоединить все методы и свойства к объекту прото, если мы хотим наследовать, верно?
BlackHawk

1
@CiroSantilli 刘晓波 死 六四 事件 法轮功 Я не думаю, что это ошибка в Chromium. Я думаю, что это всего лишь симптом, из которого fпрототип устанавливается Fтолько во время строительства; fне будет знать или заботиться F.prototypeв любое время после его создания.
Джон Глассмайер

76

prototypeпозволяет делать занятия. если вы не используете, prototypeто он становится статическим.

Вот короткий пример.

var obj = new Object();
obj.test = function() { alert('Hello?'); };

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

где, как в приведенном ниже коде

function obj()
{
}

obj.prototype.test = function() { alert('Hello?'); };
var obj2 = new obj();
obj2.test();

Объект стал классом, который теперь может быть создан. Может существовать несколько экземпляров объекта obj, и все они имеютtest функцию.

Выше мое понимание. Я делаю это вики-сообществом, чтобы люди могли исправить меня, если я ошибаюсь.


13
-1: prototypeэто свойство функций конструктора, а не экземпляров, т.е. ваш код неверен! Возможно, вы имели в виду нестандартное свойство __proto__объектов, но это совсем другой зверь ...
Кристоф

@Christoph - Спасибо за указание на это. Я обновил образец кода.
Рамеш

3
Это намного больше ... Плюс JavaScript не является языком, основанным на классах - он имеет дело с наследованием через прототипы, вам нужно более подробно рассмотреть различия!
Джеймс

5
Я думаю, что этот ответ немного вводит в заблуждение.
Армин Сифуэнтес

Может быть, ответ «неверный», но объясняет, для чего используется прототип, и теперь мне все ясно после всех этих «ответов» с сотнями голосов «за». Спасибо.
Алекс

66

Прочитав эту ветку, я запутался в цепочке прототипов JavaScript, а затем нашел эти графики

http://iwiki.readthedocs.org/en/latest/javascript/js_core.html#inheritance * [[protytype]] * и свойство <code> prototype </ code> функциональных объектов

это четкая диаграмма, показывающая наследование JavaScript по цепочке прототипов

а также

http://www.javascriptbank.com/javascript/article/JavaScript_Classical_Inheritance/

этот содержит пример с кодом и несколько хороших диаграмм.

В конечном итоге цепочка прототипов возвращается к Object.prototype.

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

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


Возможно ли иметь множественное наследование на Javascript?

Является ли Foo объектным литералом здесь или функциональным объектом? Если это объектный литерал, я считаю, что Foo.prototype не будет указывать на Foo через конструктор.
Мадхур Ахуджа

@ user3376708 JavaScript поддерживает только одиночное наследование ( источник )
Рафаэль Эйнг,

@ Nuno_147 Сначала это не ясно, но если вы посмотрите достаточно долго, вы можете получить что-то из этого.
Марселокра

3
Можете ли вы объяснить, что [[Prototype]]значит?
CodyBugstein

40

Каждый объект имеет внутреннее свойство [[Prototype]] , связывающее его с другим объектом:

object [[Prototype]]  anotherObject

В традиционном javascript связанный объект является prototypeсвойством функции:

object [[Prototype]]  aFunction.prototype

В некоторых средах [[Prototype]] отображается как __proto__:

anObject.__proto__ === anotherObject

Вы создаете ссылку [[Prototype]] при создании объекта.

// (1) Object.create:
var object = Object.create(anotherObject)
// object.__proto__ = anotherObject

// (2) ES6 object initializer:
var object = { __proto__: anotherObject };
// object.__proto__ = anotherObject

// (3) Traditional JavaScript:
var object = new aFunction;
// object.__proto__ = aFunction.prototype

Таким образом, эти утверждения эквивалентны:

var object = Object.create(Object.prototype);
var object = { __proto__: Object.prototype }; // ES6 only
var object = new Object;

Вы не можете увидеть ссылку target ( Object.prototype) в новом выражении; вместо цели подразумевается конструктор (Object ).

Помните:

  • Каждый объект имеет ссылку, [[Prototype]] , иногда отображаемую как __proto__ .
  • Каждая функция имеет prototypeсвойство, изначально содержащее пустой объект.
  • Объекты созданные с новым , связаны со prototypeсвойством своего конструктора.
  • Если функция никогда не используется в качестве конструктора, ее prototype свойство останется неиспользованным.
  • Если вам не нужен конструктор, используйте Object.create вместо new.

1
Редакция 5 удалила некоторую полезную информацию, включая информацию о Object.create (). Смотрите редакцию 4 .
Палек

@Palec, что я должен добавить обратно?
Сам

2
ИМО хотя бы ссылка на Object.create()документы , @sam. Ссылки на __proto__и Object.prototypeбудут хорошими улучшениями. И мне понравились ваши примеры того, как прототипы работают с конструкторами Object.create(), но они, вероятно, были самой длинной и менее важной частью, от которой вы хотели избавиться.
Палек

из всего обсуждения, что я получаю (пришло из классического наследования), если я создаю функцию-конструктор и пытаюсь создать ее экземпляр с помощью оператора new, я получу только те методы и свойства, которые были присоединены к объекту Proto, поэтому необходимо присоединить все методы и свойства к объекту прото, если мы хотим наследовать, верно?
BlackHawk

29

Javascript не имеет наследования в обычном смысле, но имеет цепочку прототипов.

прототип цепи

Если член объекта не может быть найден в объекте, он ищет его в цепочке прототипов. Цепочка состоит из других объектов. Доступ к прототипу данного экземпляра можно получить с помощью__proto__ переменной. Каждый объект имеет один, так как нет никакой разницы между классами и экземплярами в javascript.

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

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


1
FF и Chrome поддерживают Proto , но не IE и не Opera.
некоторые

Георг, пожалуйста, уточните для нуба - «нет разницы между классами и экземплярами в javascript». - не могли бы вы уточнить? Как это работает?
Хэмиш Грубиджан

из всего обсуждения, что я получаю (пришло из классического наследования), если я создаю функцию-конструктор и пытаюсь создать ее экземпляр с помощью оператора new, я получу только те методы и свойства, которые были присоединены к объекту Proto, поэтому необходимо присоединить все методы и свойства к объекту прото, если мы хотим наследовать, верно?
BlackHawk

28

Эта статья длинная Но я уверен, что он очистит большинство ваших запросов относительно «прототипной» природы наследования JavaScript. И даже больше. Пожалуйста, прочитайте статью полностью.

JavaScript в основном имеет два типа типов данных

  • Не объекты
  • Объекты

Не объекты

Ниже приведены объект Номер типов данных

  • строка
  • число (включая NaN и Infinity)
  • логические значения (true, false)
  • не определено

Эти типы данных возвращают следующее при использовании оператора typeof

typeof "строковый литерал" (или переменная, содержащая строковый литерал) === 'строка'

typeof 5 (или любой числовой литерал или переменная, содержащая числовой литерал или NaN или Infynity ) === 'число'

typeof true (или false, или переменная, содержащая true или false ) === 'логическое значение'

typeof undefined (или неопределенная переменная, или переменная, содержащая undefined ) === 'undefined'

Строка , число и булевы типы данных могут быть представлены как в виде объектов и без объектов .Когда они представлены как объекты их TypeOf всегда === «объект». Мы вернемся к этому, когда поймем типы данных объекта.

Объекты

Типы данных объекта могут быть далее разделены на два типа

  1. Объекты типа функции
  2. Объекты не функционального типа

Эти объекты типа Function являются те , которые возвращают строку «функцию» с TYPEOF оператором. Все пользовательские функции и все встроенные объекты JavaScript, которые могут создавать новые объекты с помощью оператора new, попадают в эту категорию. Например,

  • объект
  • строка
  • Число
  • логический
  • массив
  • Типизированные массивы
  • RegExp
  • функция
  • Все остальные встроенные объекты, которые могут создавать новые объекты с помощью оператора new
  • function UserDefinedFunction () {/ * пользовательский код * /}

Итак, typeof (Object) === typeof (String) === typeof (Number) === typeof (Boolean) === typeof (Array) === typeof (RegExp) === typeof (Function) == = typeof (UserDefinedFunction) === 'function'

Все объекты типа Function фактически являются экземплярами встроенной функции JavaScript объекта (включая объект Function, т. Е. Он рекурсивно определен). Это как если бы эти объекты были определены следующим образом

var Object= new Function ([native code for object Object])
var String= new Function ([native code for object String])
var Number= new Function ([native code for object Number])
var Boolean= new Function ([native code for object Boolean])
var Array= new Function ([native code for object Array])
var RegExp= new Function ([native code for object RegExp])
var Function= new Function ([native code  for object Function])
var UserDefinedFunction= new Function ("user defined code")

Как уже упоминалось, объекты типа Function могут дополнительно создавать новые объекты с помощью оператора new . Например, можно создать объект типа Object , String , Number , Boolean , Array , RegExp или UserDefinedFunction с помощью

var a=new Object() or var a=Object() or var a={} //Create object of type Object
var a=new String() //Create object of type String
var a=new Number() //Create object of type Number
var a=new Boolean() //Create object of type Boolean
var a=new Array() or var a=Array() or var a=[]  //Create object of type Array
var a=new RegExp() or var a=RegExp() //Create object of type RegExp
var a=new UserDefinedFunction() 

Все объекты, созданные таким образом, являются объектами типа Non Function и возвращают их typeof === 'object' . Во всех этих случаях объект «а» не может в дальнейшем создавать объекты, используя оператор new. Так что следующее неправильно

var b=new a() //error. a is not typeof==='function'

Встроенный объект Math - это typeof === 'object' . Следовательно, новый объект типа Math не может быть создан новым оператором.

var b=new Math() //error. Math is not typeof==='function'

Также обратите внимание, что функции Object , Array и RegExp могут создавать новый объект, даже не используя оператор new . Однако следующие не делают.

var a=String() // Create a new Non Object string. returns a typeof==='string' 
var a=Number() // Create a new Non Object Number. returns a typeof==='number'
var a=Boolean() //Create a new Non Object Boolean. returns a typeof==='boolean'

Пользовательские функции являются особым случаем.

var a=UserDefinedFunction() //may or may not create an object of type UserDefinedFunction() based on how it is defined.

Поскольку объекты типа Function могут создавать новые объекты, их также называют конструкторами .

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

Например, когда мы определяем функцию

function UserDefinedFunction()
{
}

следующее автоматически происходит

UserDefinedFunction.prototype={constructor:UserDefinedFunction}

Это свойство «prototype» присутствует только в объектах типа Function (и никогда в объектах типа Non Function ).

Это связано с тем, что при создании нового объекта (с использованием оператора new) он наследует все свойства и методы от текущего объекта-прототипа функции Constructor, т. Е. Во вновь созданном объекте создается внутренняя ссылка, которая ссылается на объект, на который ссылается текущий объект-прототип функции Constructor.

Эта «внутренняя ссылка», которая создается в объекте для ссылки на унаследованные свойства, называется прототипом объекта (который ссылается на объект, на который ссылается свойство «прототипа» конструктора, но отличается от него). Для любого объекта (Function или Non Function) это можно получить с помощью метода Object.getPrototypeOf () . С помощью этого метода можно проследить цепочку прототипов объекта.

Кроме того, каждый создаваемый объект ( тип функции или тип не функции ) имеет свойство «конструктор», которое наследуется от объекта, на который ссылается свойство prototype функции «Конструктор». По умолчанию это свойство «конструктор» ссылается на функцию «Конструктор», которая его создала (если «прототип» функции конструктора по умолчанию не изменился).

Для всех объектов типа Function функция конструктора всегда является функцией Function () {}

Для объектов не-функционального типа (например, Javascript Built in Math object) функция конструктора - это функция, которая его создала. Для объекта Math это функция Object () {} .

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

function UserDefinedFunction()
{ 

} 

/* creating the above function automatically does the following as mentioned earlier

UserDefinedFunction.prototype={constructor:UserDefinedFunction}

*/


var newObj_1=new UserDefinedFunction()

alert(Object.getPrototypeOf(newObj_1)===UserDefinedFunction.prototype)  //Displays true

alert(newObj_1.constructor) //Displays function UserDefinedFunction

//Create a new property in UserDefinedFunction.prototype object

UserDefinedFunction.prototype.TestProperty="test"

alert(newObj_1.TestProperty) //Displays "test"

alert(Object.getPrototypeOf(newObj_1).TestProperty)// Displays "test"

//Create a new Object

var objA = {
        property1 : "Property1",
        constructor:Array

}


//assign a new object to UserDefinedFunction.prototype
UserDefinedFunction.prototype=objA

alert(Object.getPrototypeOf(newObj_1)===UserDefinedFunction.prototype)  //Displays false. The object referenced by UserDefinedFunction.prototype has changed

//The internal reference does not change
alert(newObj_1.constructor) // This shall still Display function UserDefinedFunction

alert(newObj_1.TestProperty) //This shall still Display "test" 

alert(Object.getPrototypeOf(newObj_1).TestProperty) //This shall still Display "test"


//Create another object of type UserDefinedFunction
var newObj_2= new UserDefinedFunction();

alert(Object.getPrototypeOf(newObj_2)===objA) //Displays true.

alert(newObj_2.constructor) //Displays function Array()

alert(newObj_2.property1) //Displays "Property1"

alert(Object.getPrototypeOf(newObj_2).property1) //Displays "Property1"

//Create a new property in objA
objA.property2="property2"

alert(objA.property2) //Displays "Property2"

alert(UserDefinedFunction.prototype.property2) //Displays "Property2"

alert(newObj_2.property2) // Displays Property2

alert(Object.getPrototypeOf(newObj_2).property2) //Displays  "Property2"

Цепочка прототипов каждого объекта в конечном итоге ведет к объекту Object.prototype (который сам по себе не имеет какого-либо объекта-прототипа). Следующий код может быть использован для отслеживания цепочки прототипов объекта

var o=Starting object;

do {
    alert(o + "\n" + Object.getOwnPropertyNames(o))

}while(o=Object.getPrototypeOf(o))

Прототип цепи для различных объектов работает следующим образом.

  • Каждый объект Function (включая встроенный объект Function) -> Function.prototype -> Object.prototype -> null
  • Простые объекты (создаются с помощью нового объекта () или {}, включая встроенный объект Math) -> Object.prototype -> null
  • Объект создан с помощью new или Object.create -> Одна или несколько цепочек прототипов -> Object.prototype -> null

Для создания объекта без прототипа используйте следующее:

var o=Object.create(null)
alert(Object.getPrototypeOf(o)) //Displays null

Можно подумать, что установив свойство prototype конструктора NULL, создаст объект с нулевым прототипом. Однако в таких случаях прототип вновь созданного объекта устанавливается в Object.prototype, а его конструктор устанавливается в функцию Object. Это демонстрируется следующим кодом

function UserDefinedFunction(){}
UserDefinedFunction.prototype=null// Can be set to any non object value (number,string,undefined etc.)

var o=new UserDefinedFunction()
alert(Object.getPrototypeOf(o)==Object.prototype)   //Displays true
alert(o.constructor)    //Displays Function Object

Следуя в кратком изложении этой статьи

  • Есть два типа объектов типов функций и Non типов функций
  • Только объекты типа Function могут создавать новый объект, используя оператор new . Созданные таким образом объекты являются объектами не функционального типа . Эти объекты типа Function Номера не могут дополнительно создать объект с помощью оператора нового .

  • Все объекты типа Function по умолчанию имеют свойство «prototype» . Это свойство «prototype» ссылается на объект, имеющий свойство «constructor», которое по умолчанию ссылается на сам объект типа Function .

  • Все объекты ( типа Function и Non типа Function ) имеют свойство «конструктор» , который по умолчанию ссылки Тип функции объекта / Конструктор , который создал его.

  • Каждый создаваемый объект внутренне ссылается на объект, на который ссылается свойство «prototype» конструктора, который его создал. Этот объект известен как прототип созданного объекта (который отличается от свойства «prototype» объектов функции, на которое он ссылается). Таким образом, созданный объект может напрямую обращаться к методам и свойствам, определенным в объекте, на который ссылается свойство «прототипа» конструктора (во время создания объекта).

  • An прототип объекта (и , следовательно , его наследственные имена свойств) могут быть получены с помощью Object.getPrototypeOf () метод. Фактически этот метод может использоваться для навигации по всей цепочке прототипов объекта.

  • Цепочка прототипов каждого объекта в конечном итоге ведет к объекту Object.prototype (если объект не создан с использованием Object.create (null), в этом случае у объекта нет прототипа).

  • typeof (new Array ()) === «объект» создан по типу языка и не является ошибкой, на что указывает Дуглас Крокфорд

  • Установка для свойства prototype конструктора значения null (или неопределенного, number, true, false, string) не должна создавать объект с нулевым прототипом. В таких случаях для вновь созданного прототипа объекта устанавливается Object.prototype, а для его конструктора устанавливается функция Object.

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


24

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

введите описание изображения здесь

Если мы используем newоператор в Tree function, мы вызываем его как constructorфункцию.

введите описание изображения здесь

Каждая JavaScriptфункция имеет prototype. Когда Tree.prototypeвы входите, вы получаете ...

введите описание изображения здесь

Если вы посмотрите на приведенный выше console.log()вывод, вы можете увидеть свойство конструктора Tree.prototypeи __proto__свойство тоже. Представление __proto__представляет собой prototypeто, что это functionосновано, и, поскольку это просто равнина, еще JavaScript functionне inheritanceнастроенная, оно ссылается на то, Object prototypeчто просто встроено в JavaScript ...

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype

Это имеет такие вещи, как и .toString, .toValue, .hasOwnPropertyт.д ...

__proto__который был принесен мой Mozilla устарела и заменена Object.getPrototypeOfметодом, чтобы получить object's prototype.

введите описание изображения здесь

Object.getPrototypeOf(Tree.prototype); // Object {} 

Давайте добавим метод к нашему Tree prototype.

введите описание изображения здесь

Мы изменили Rootи добавили functionветку к нему.

введите описание изображения здесь

Это означает, что когда вы создаете instanceof Tree, вы можете вызвать его branchметод.

введите описание изображения здесь

Мы также можем добавить primitivesили objectsк нашему Prototype.

введите описание изображения здесь

Давайте добавим child-treeк нашему Tree.

введите описание изображения здесь

Здесь он Childнаследует его prototypeот Tree. То, что мы здесь делаем, использует Object.create()метод для создания нового объекта на основе того, что вы передаете, вот оно Tree.prototype. В этом случае мы устанавливаем прототип Child для нового объекта, который выглядит идентично Treeпрототипу. Далее мы устанавливаем Child's constructor to Child, если мы этого не сделаем, это будет указывать Tree().

введите описание изображения здесь

Childтеперь имеет свои prototype, свои __proto__точки Treeи Tree's prototypeуказывает на базу Object.

Child  
|
 \
  \
   Tree.prototype
   - branch
   |
   |
    \
     \
      Object.prototype
      -toString
      -valueOf
      -etc., etc.

Теперь вы создаете instanceиз Childи вызова , branchкоторый первоначально доступен в Tree. Мы на самом деле не определили наши branchна Child prototype. НО, в Root prototypeкотором ребенок наследует.

введите описание изображения здесь

В JS все не является объектом, все может действовать как объект.

Javascriptимеет такие примитивы, как strings, number, booleans, undefined, null.они object(i.e reference types), но, безусловно, может действовать как object. Давайте посмотрим на пример здесь.

введите описание изображения здесь

В первой строке этого списка primitiveстроковому значению присваивается имя. Вторая строка обрабатывает имя как objectи вызывает charAt(0)с использованием точечной нотации.

Вот что происходит за кулисами: // что JavaScriptделает двигатель

введите описание изображения здесь

String objectСуществует только одно заявление , прежде чем он будет уничтожен (этот процесс называется autoboxing). Давайте снова вернемся к нашему prototypal inheritance.

  • Javascriptподдерживает наследование через delegationна основе prototypes.
  • У каждого Functionесть prototypeсвойство, которое относится к другому объекту.
  • properties/functionsсмотрят из objectсебя или через prototypeцепочку, если она не существует

А prototypeв JS - это объект, который yieldsвы родителю другого object. [то есть .. делегирование] Delegation означает, что если вы не можете что-то сделать, вы скажете кому-то другому сделать это за вас.

введите описание изображения здесь

https://jsfiddle.net/say0tzpL/1/

Если вы посмотрите вышеупомянутую скрипку, у собаки есть доступ к toStringметоду, но он не доступен в нем, но доступен через цепочку прототипов, которая делегируетObject.prototype

введите описание изображения здесь

Если вы посмотрите на приведенный ниже, мы пытаемся получить доступ к callметоду, доступному в каждом function.

введите описание изображения здесь

https://jsfiddle.net/rknffckc/

Если вы посмотрите вышеупомянутую скрипку, у Profileфункции есть доступ к callметоду, но он не доступен в нем, но доступен через цепочку прототипов, которая делегируетFunction.prototype

введите описание изображения здесь

Примечание: prototype это свойство конструктора функции, тогда __proto__как это свойство объектов, созданных из конструктора функции. Каждая функция поставляется со prototypeсвойством, значение которого является пустым object. Когда мы создаем экземпляр функции, мы получаем внутреннее свойство [[Prototype]]или __proto__ссылка на который является прототипом функции constructor.

введите описание изображения здесь

Вышеприведенная диаграмма выглядит немного сложнее, но выявляет всю картину того, как prototype chainingработает. Давайте пройдемся по этому медленно:

Есть два экземпляра b1и b2, чей конструктор Barи родитель - Foo и имеет два метода из цепочки прототипов identifyи speakчерез BarиFoo

введите описание изображения здесь

https://jsfiddle.net/kbp7jr7n/

Если вы посмотрите код выше, у нас есть Fooконструктор, у которого есть метод, identify()и Barконструктор, у которого есть speakметод. Мы создаем два Barэкземпляра b1и b2родительский тип которых Foo. Теперь при вызове speakметода Barмы можем определить, кто вызывает разговор по prototypeцепочке.

введите описание изображения здесь

BarТеперь есть все методы, Fooкоторые определены в его prototype. Давайте копать дальше в понимании Object.prototypeи Function.prototypeи как они связаны между собой . Если вы посмотрите вверх конструктор Foo, Barи Objectесть Function constructor.

введите описание изображения здесь

prototypeИз Barэто Foo, prototypeиз FooIS Objectи если вы посмотрите тесно prototypeот Fooсвязано Object.prototype.

введите описание изображения здесь

Прежде чем мы закроем это, давайте просто завернем небольшой кусочек кода здесь, чтобы подвести итог всему выше . Здесь мы используем instanceofоператор, чтобы проверить, objectимеет ли an в своей prototypeцепочке prototypeсвойство a, constructorкоторое ниже суммирует всю большую диаграмму.

введите описание изображения здесь

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


22

Каково точное назначение этого свойства ".prototype"?

Интерфейс к стандартным классам становится расширяемым. Например, вы используете Arrayкласс, и вам также необходимо добавить настраиваемый сериализатор для всех ваших объектов массива. Вы бы потратили время на кодирование подкласса, или использовали состав или ... Свойство prototype решает эту проблему, позволяя пользователям контролировать точный набор членов / методов, доступных для класса.

Думайте о прототипах как о дополнительном vtable-указателе. Когда некоторые члены отсутствуют в исходном классе, прототип ищется во время выполнения.


21

Это может помочь классифицировать цепочки прототипов на две категории.

Рассмотрим конструктор:

 function Person() {}

Значение Object.getPrototypeOf(Person)является функцией. На самом деле это так Function.prototype. Поскольку он Personбыл создан как функция, он разделяет тот же объект функции-прототип, что и все функции. Это так же, как Person.__proto__, но это свойство не должно использоваться. Во всяком случае, с Object.getPrototypeOf(Person)вами эффективно подниматься по лестнице того, что называется цепочкой прототипов.

Цепочка в направлении вверх выглядит так:

    PersonFunction.prototypeObject.prototype(конечная точка)

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

Возьмем для примера этот объект:

var p = new Person();

р не имеет прямого отношения прототипа с человеком . Их отношения разные. Объект р имеет свою собственную цепочку прототипов. Используя Object.getPrototypeOf, вы найдете цепочку следующим образом:

    pPerson.prototypeObject.prototype(конечная точка)

В этой цепочке нет объекта функции (хотя это может быть).

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

  1. .prototype: перейти от цепочки конструктора к цепочке созданного объекта. Таким образом, это свойство определено только для функциональных объектов (какnew может использоваться только для функций).

  2. .constructor: перейти от цепочки созданного объекта к цепочке конструктора.

Вот визуальное представление двух задействованных цепочек прототипов, представленных в виде столбцов:

введите описание изображения здесь

Чтобы обобщить:

prototypeСвойство не дает никакой информации из субъекта цепочки прототипов, но объекты , созданных субъектом.

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

Вы можете прыгать туда-сюда между двумя цепями прототипов:

Person.prototype.constructor === Person

Эту симметрию можно нарушить, явно назначив другой объект prototype свойству (подробнее об этом позже).

Создать одну функцию, получить два объекта

Person.prototypeявляется объектом, который был создан одновременно с Personсозданием функции . Он имеет Personконструктор, хотя этот конструктор еще не выполнялся. Итак, два объекта создаются одновременно:

  1. Функция Person сама по себе
  2. Объект, который будет действовать как прототип, когда функция вызывается как конструктор

Оба являются объектами, но они играют разные роли: функции объекта конструкций , в то время как другой объект представляет собой прототип любого объекта, функция будет строить. Прототип объекта станет родителем построенного объекта в его цепочке прототипов.

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

Вот некоторые равенства, которые могут помочь разобраться в проблеме - все это напечатано true:

function Person() {};

// This is prototype chain info for the constructor (the function object):
console.log(Object.getPrototypeOf(Person) === Function.prototype);
// Step further up in the same hierarchy:
console.log(Object.getPrototypeOf(Function.prototype) === Object.prototype);
console.log(Object.getPrototypeOf(Object.prototype) === null);
console.log(Person.__proto__ === Function.prototype);
// Here we swap lanes, and look at the constructor of the constructor
console.log(Person.constructor === Function);
console.log(Person instanceof Function);

// Person.prototype was created by Person (at the time of its creation)
// Here we swap lanes back and forth:
console.log(Person.prototype.constructor === Person);
// Although it is not an instance of it:
console.log(!(Person.prototype instanceof Person));
// Instances are objects created by the constructor:
var p = new Person();
// Similarly to what was shown for the constructor, here we have
// the same for the object created by the constructor:
console.log(Object.getPrototypeOf(p) === Person.prototype);
console.log(p.__proto__ === Person.prototype);
// Here we swap lanes, and look at the constructor
console.log(p.constructor === Person);
console.log(p instanceof Person);

Добавление уровней в цепочку прототипов

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

Например:

function Thief() { }
var p = new Person();
Thief.prototype = p; // this determines the prototype for any new Thief objects:
var t = new Thief();

Теперь цепочка прототипов t на один шаг длиннее, чем цепочка p :

    tpPerson.prototypeObject.prototype(конечная точка)

Другая цепочка прототипов не длиннее: Thiefи у Personбратьев и сестер общий родительский элемент в их цепочке прототипов:

    Person}
    Thief  } → Function.prototypeObject.prototype(конечная точка)

Ранее представленный рисунок может быть расширен до этого (оригинал Thief.prototypeопущен):

введите описание изображения здесь

Синие линии представляют цепочки прототипов, другие цветные линии представляют другие отношения:

  • между объектом и его конструктором
  • между конструктором и прототипом объекта, который будет использоваться для создания объектов

18

Полное руководство по объектно-ориентированному JavaScript - очень краткое и четкое объяснение задаваемого вопроса о видео в течение 30 минут (тема наследования прототипа начинается с 5:45 , хотя я бы предпочел прослушать все видео). Автор этого видео также сделал сайт визуализатора объектов JavaScript http://www.objectplayground.com/ .введите описание изображения здесь введите описание изображения здесь


1
отличный видео справочник
Lukeaus

16

Я нашел полезным объяснить «цепочку прототипов» как рекурсивное соглашение, когда obj_n.prop_Xна него ссылаются:

если obj_n.prop_Xне существует, проверьтеobj_n+1.prop_X гдеobj_n+1 = obj_n.[[prototype]]

Если prop_Xнаконец найден в k-ом объекте-прототипе, то

obj_1.prop_X = obj_1.[[prototype]].[[prototype]]..(k-times)..[[prototype]].prop_X

Вы можете найти график отношений объектов Javascript по их свойствам здесь:

граф объектов JS

http://jsobjects.org


14

Когда конструктор создает объект, этот объект неявно ссылается на свойство «прототипа» конструктора с целью разрешения ссылок на свойства. На свойство «prototype» конструктора может ссылаться выражение программы constructor.prototype, а свойства, добавленные к прототипу объекта, совместно используются посредством наследования всеми объектами, совместно использующими прототип.


11

Здесь есть две разные, но связанные сущности, которые нужно объяснить:

  • .prototypeСвойство функций.
  • Свойство [[Prototype]][1] всех объектов [2] .

Это две разные вещи.

[[Prototype]]Свойство:

Это свойство, которое существует на всех [2] объектах.

Здесь хранится еще один объект, который, как сам объект, имеет [[Prototype]]свой собственный объект, который указывает на другой объект. Этот другой объект имеет [[Prototype]]свой собственный. Эта история продолжается до тех пор, пока вы не достигнете прототипа объекта, который предоставляет методы, доступные для всех объектов (например, .toString).

[[Prototype]]Свойство является частью того , что образует [[Prototype]]цепь. Эта цепочка [[Prototype]]объектов является то , что проверяется , когда, например, [[Get]]или [[Set]]операции выполняются на объекте:

var obj = {}
obj.a         // [[Get]] consults prototype chain
obj.b = 20    // [[Set]] consults prototype chain

.prototypeСвойство:

Это свойство, которое можно найти только в функциях. Используя очень простую функцию:

function Bar(){};

.prototypeСвойство содержит объект , который будет назначен , b.[[Prototype]]когда вы делаете var b = new Bar. Вы можете легко проверить это:

// Both assign Bar.prototype to b1/b2[[Prototype]]
var b = new Bar;
// Object.getPrototypeOf grabs the objects [[Prototype]]
console.log(Object.getPrototypeOf(b) === Bar.prototype) // true

Одним из наиболее важных .prototypeс является то , что в Objectфункции . Этот прототип содержит прототип объекта, который [[Prototype]]содержится во всех цепях. На нем определены все доступные методы для новых объектов:

// Get properties that are defined on this object
console.log(Object.getOwnPropertyDescriptors(Object.prototype))

Теперь, поскольку .prototypeэто объект, у него есть [[Prototype]]свойство. Когда вы не делаете никаких назначений Function.prototype, .prototype's [[Prototype]]указывает на прототип объекта (Object.prototype ). Это автоматически выполняется каждый раз, когда вы создаете новую функцию.

Таким образом, каждый раз, когда вы делаете new Bar;цепочку прототипов, вы получаете все, что определено, Bar.prototypeи все, что определено Object.prototype:

var b = new Bar;
// Get all Bar.prototype properties
console.log(b.__proto__ === Bar.prototype)
// Get all Object.prototype properties
console.log(b.__proto__.__proto__ === Object.prototype)

Когда вы делаете назначения Function.prototypeвсем, что делаете, расширяете цепочку прототипов, чтобы включить в нее другой объект. Это как вставка в односвязный список.

Это в основном изменяет [[Prototype]]цепочку, позволяя свойствам, которые определены для объекта, назначенного Function.prototypeбыть видимым любому объекту, созданному функцией.


[1: Это никого не смущает; доступны через в __proto__собственности во многих реализациях.
[2]: все, кроме null.


10

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

Прототип похож на модель, на основе которой вы создаете продукт. Важно понять, что при создании объекта с использованием другого объекта в качестве прототипа связь между прототипом и продуктом является постоянной. Например:

var model = {x:2};
var product = Object.create(model);
model.y = 5;
product.y
=>5

Каждый объект содержит внутреннее свойство, называемое [[prototype]], к которому может обращаться Object.getPrototypeOf()функция. Object.create(model)создает новый объект и устанавливает его свойство [[prototype]] для объектной модели . Следовательно, когда вы это сделаете Object.getPrototypeOf(product), вы получите объектную модель .

Свойства в продукте обрабатываются следующим образом:

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

Такое связывание объектов с использованием свойства prototype называется наследованием прототипов. Там это так просто, согласен?


Не всегда написано на товаре по заданию. Вы не очень ясно даете понять, что конкретные элементы экземпляра должны быть инициализированы, а общие участники могут использовать прототип. Особенно, когда у вас есть изменяемые члены, специфичные для экземпляра: stackoverflow.com/questions/16063394/…
HMR

HMR: В вашем примере в вашем ответе ben.food.push («Гамбургер»); line изменяет свойство объекта-прототипа из-за следующего: 1.) Сначала ищется ben.food, и любое действие поиска просто ищет цепочку области видимости. 2.) Функция push этого объекта ben.food выполняется. Под написанием режима в моем ответе я подразумеваю, когда вы явно устанавливаете для него значение, например: ben.food = ['Idly']; Это всегда создаст новое свойство (если оно еще не существует) для объекта продукта, а затем присвоит ему значение.
Аравинд

HMR: Спасибо за ваш комментарий, он заставил меня задуматься и проверить мое понимание.
Аравинд

При повторном назначении ben.food он будет скрывать элемент food, если только еда не создана с использованием Object.defineProperty, Object.defineProperties или Object.create со вторым аргументом (так не всегда). Вы даже можете изменить прототип с помощью (как это выглядит) повторного назначения при создании метода установки. Когда дело доходит до шаблонов наследования, я понимаю, что функция конструктора трудна для понимания и имеет некоторые серьезные проблемы, но хорошо, если вы ее понимаете. Наследование в JavaScript не начинается и не заканчивается установкой прототипа, также нужно (пере) использовать инициализаторы (конструкторы).
HMR

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


10

Рассмотрим следующий keyValueStoreобъект:

var keyValueStore = (function() {
    var count = 0;
    var kvs = function() {
        count++;
        this.data = {};
        this.get = function(key) { return this.data[key]; };
        this.set = function(key, value) { this.data[key] = value; };
        this.delete = function(key) { delete this.data[key]; };
        this.getLength = function() {
            var l = 0;
            for (p in this.data) l++;
            return l;
        }
    };

    return  { // Singleton public properties
        'create' : function() { return new kvs(); },
        'count' : function() { return count; }
    };
})();

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

kvs = keyValueStore.create();

Каждый экземпляр этого объекта будет иметь следующие открытые свойства:

  • data
  • get
  • set
  • delete
  • getLength

Теперь предположим, что мы создали 100 экземпляров этого keyValueStoreобъекта. Несмотря на то get, set, delete,getLength будет делать ту же самую вещь для каждого из этих 100 экземпляров, каждый экземпляр имеет свою собственную копию этой функции.

Теперь представьте, что если бы вы могли иметь только один get, set, deleteиgetLength копию, и каждый экземпляр будет ссылаться на ту же самую функцию. Это было бы лучше для производительности и потребовало бы меньше памяти.

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

Теперь рассмотрим keyValueStoreобъект еще раз. Я мог бы переписать это так:

var keyValueStore = (function() {
    var count = 0;
    var kvs = function() {
        count++;
        this.data = {};
    };

    kvs.prototype = {
        'get' : function(key) { return this.data[key]; },
        'set' : function(key, value) { this.data[key] = value; },
        'delete' : function(key) { delete this.data[key]; },
        'getLength' : function() {
            var l = 0;
            for (p in this.data) l++;
            return l;
        }
    };

    return  {
        'create' : function() { return new kvs(); },
        'count' : function() { return count; }
    };
})();

Это точно так же, как и в предыдущей версии keyValueStoreобъекта, за исключением того, что все его методы теперь помещены в прототип. Это означает, что все 100 экземпляров теперь совместно используют эти четыре метода вместо того, чтобы каждый имел свою собственную копию.


9

Резюме:

  • Функции являются объектами в JavaScript и, следовательно, могут иметь свойства
  • (Конструктор) функции всегда имеют свойство прототипа
  • Когда функция используется в качестве конструктора с newключевым словом, объект получает прототип. Ссылку на этот прототип можно найти на__proto__ свойстве вновь созданного объекта.
  • Это __proto__свойство ссылается на prototypeсвойство функции конструктора.

Пример:

function Person (name) {
  this.name = name;
}

let me = new Person('willem');

console.log(Person.prototype) // Person has a prototype property

console.log(Person.prototype === me.__proto__) // the __proto__ property of the instance refers to prototype property of the function.

Почему это полезно:

Javascript имеет механизм поиска свойств в объектах, который называется «наследование прототипов» , вот что в основном делает:

  • Сначала проверяется, находится ли свойство на самом объекте. Если это так, это свойство возвращается.
  • Если свойство не находится на самом объекте, оно «поднимется по проточине». Он в основном смотрит на объект, на который ссылается свойство proto . Там он проверяет, доступно ли свойство для объекта, на который ссылается прото
  • Если свойство не находится на объекте прото, оно будет подниматься вверх по цепи прото до объекта Object.
  • Если он не может найти свойство нигде в объекте и его цепочке прототипов, он вернет неопределенное.

Например:

function Person(name) {
  this.name = name;
}

let mySelf = new Person('Willem');

console.log(mySelf.__proto__ === Person.prototype);

console.log(mySelf.__proto__.__proto__ === Object.prototype);

Обновить:

Это __proto__свойство устарело, хотя оно реализовано в большинстве современных браузеров, и лучший способ получить ссылку на объект-прототип:

Object.getPrototypeOf()


7

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

Представь себе это ....

Ты в старшей школе, ты в классе и у тебя есть тест, который должен пройти сегодня, но у тебя нет ручки, чтобы заполнить свои ответы. Doh!

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

Здесь важно то, что Дерп не дает вам ручку, поскольку у вас нет с ним прямых отношений .

Это упрощенный пример того, как работают прототипы, где дерево данных ищет то, что вы ищете.


3

другая схема, показывающая __proto__ , отношения между прототипом и конструктором : введите описание изображения здесь


1

Просто у вас уже есть объект, Object.newно у вас все еще нет объекта при использовании синтаксиса конструктора.


1

Важно понимать, что существует различие между прототипом объекта (который доступен через Object.getPrototypeOf(obj)или через устаревшее __proto__свойство) и prototypeсвойством в функциях конструктора. Первый является свойством каждого экземпляра, а второй - свойством конструктора. То есть Object.getPrototypeOf(new Foobar())относится к тому же объекту, что и Foobar.prototype.

Ссылка: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes


0

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.