Как работает JavaScript .prototype? - PullRequest
1948 голосов
/ 21 февраля 2009

Я не очень разбираюсь в языках динамического программирования, но я написал свою долю кода 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

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

Ответы [ 25 ]

20 голосов
/ 21 февраля 2009

какова точная цель этого свойства .prototype?

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

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

19 голосов
/ 19 марта 2017

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

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

 function Person() {}

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

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

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

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

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

var p = new Person();

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

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

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

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

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

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

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

enter image description here

Подведем итог:

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

Неудивительно, что название объекта 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();

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

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

Другая цепочка прототипов не длиннее: Thief и Person - это братья и сестры, имеющие одного и того же родителя в цепочке прототипов:

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

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

enter image description here

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

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

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

14 голосов
/ 21 октября 2013

Мне показалось полезным объяснить «цепочку прототипов» как рекурсивное соглашение, когда на 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 objects graph

http://jsobjects.org

13 голосов
/ 05 февраля 2010

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

10 голосов
/ 09 ноября 2014

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

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

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

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

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

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

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

10 голосов
/ 13 декабря 2017

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

  • Свойство .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 s является функции Object . Этот прототип содержит прототип объекта, который содержится во всех цепях [[Prototype]]. На нем определены все доступные методы для новых объектов:

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

Теперь, поскольку .prototype является объектом, у него есть свойство [[Prototype]]. Если вы не присваиваете Function.prototype, .prototype * [[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.

9 голосов
/ 11 марта 2016

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

8 голосов
/ 25 октября 2015

Еще одна попытка объяснить наследование на основе прототипов JavaScript с лучшими картинками

Simple objects inheritanse

7 голосов
/ 26 мая 2017

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

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...