Свойство prototype
— это объект, созданный JavaScript для каждого экземпляра Function()
. В частности, он связывает экземпляры объектов, созданные с new
ключевым словом, с функцией конструктора, которая их создала. Это делается для того, чтобы экземпляры могли совместно использовать или наследовать общие методы и свойства. Важно отметить, что обмен происходит во время поиска свойств. Помните из первой статьи, что каждый раз, когда вы просматриваете или обращаетесь к свойству объекта, свойство будет искать как в объекте, так и в цепочке прототипов.
Объект-прототип создается для каждой функции, независимо от того, собираетесь ли вы использовать эту функцию в качестве конструктора.
В следующем коде я создаю массив из конструктора Array()
, а затем вызываю метод join()
.
Образец: sample118.html
1
2
3
4
5
6
7
|
<!DOCTYPE html><html lang=»en»><body><script>
var myArray = new Array(‘foo’, ‘bar’);
console.log(myArray.join());
</script></body></html>
|
Метод join()
не определен как свойство экземпляра объекта myArray
, но у нас есть доступ к join()
как если бы он был. Этот метод определен где-то, но где? Ну, это определяется как свойство свойства prototype конструктора Array()
. Поскольку join()
не найден в экземпляре объекта массива, JavaScript ищет цепочку прототипов для метода, называемого join()
.
Хорошо, так почему все так? Действительно, речь идет об эффективности и повторном использовании. Почему каждый экземпляр массива, созданный из функции конструктора массива, должен иметь уникально определенный метод join()
когда join()
всегда функционирует одинаково? Для всех массивов имеет больше смысла использовать одну и ту же функцию join()
не создавая новый экземпляр функции для каждого экземпляра массива.
Эта эффективность, о которой мы говорим, возможна благодаря свойству прототипа, связи прототипа и цепочке поиска прототипа. В этой статье мы разберем эти часто запутанные атрибуты наследования прототипов. Но, по правде говоря, вам было бы лучше просто запомнить механику того, как на самом деле работает цепная иерархия. Вернитесь к первой статье, если вам нужно обновить информацию о том, как разрешаются значения свойств.
Зачем заботиться о prototype
недвижимости?
Вы должны заботиться о свойстве prototype
по четырем причинам.
Причина 1
Первая причина заключается в том, что свойство prototype используется встроенными функциями конструктора ( Object()
, Array()
, Function()
и т. Д.), Чтобы экземпляры конструктора могли наследовать свойства и методы. Это механизм, который сам JavaScript использует, чтобы экземпляры объектов могли наследовать свойства и методы от свойства prototype
функции конструктора. Если вы хотите лучше понять JavaScript, вам нужно понять, как сам JavaScript использует prototype
объекта.
Причина 2
При создании пользовательских функций конструктора вы можете управлять наследованием так же, как это делают нативные объекты JavaScript. Но сначала вы должны узнать, как это работает.
Причина 3
Возможно, вам действительно не нравится наследование прототипов или вы предпочитаете другой шаблон для наследования объектов, но реальность такова, что когда-нибудь вам, возможно, придется редактировать или управлять чужим кодом, который считает наследование прототипа коленями пчелы. Когда это происходит, вы должны знать, как работает наследование прототипов, а также как его можно тиражировать разработчикам, использующим пользовательские функции конструктора.
Причина 4
Используя прототип наследования, вы можете создавать эффективные экземпляры объектов, которые используют одни и те же методы. Как уже упоминалось, не все объекты массива, которые являются экземплярами конструктора Array()
, нуждаются в собственных методах join()
. Все экземпляры могут использовать один и тот же метод join()
поскольку этот метод хранится в цепочке прототипов.
Прототип является стандартным для всех экземпляров Function()
Все функции создаются из конструктора Function()
, даже если вы напрямую не вызываете конструктор Function()
( var add = new Function('x', 'y', 'return x + z');
) и вместо этого используете буквенное обозначение ( var add = function(x,y){return x + z};
).
Когда создается экземпляр функции, ему всегда присваивается свойство prototype
, которое является пустым объектом. В следующем примере мы определяем функцию myFunction и затем получаем доступ к свойству prototype
которое является просто пустым объектом.
Образец: sample119.html
1
2
3
4
5
6
7
|
<!DOCTYPE html><html lang=»en»><body><script>
var myFunction = function () { };
console.log(myFunction.prototype);
console.log(typeof myFunction.prototype);
</script></body></html>
|
Убедитесь, что вы полностью понимаете, что свойство prototype исходит из конструктора Function()
. Только когда мы намереваемся использовать нашу функцию в качестве определяемой пользователем функции конструктора, свойство прототипа используется, но это не меняет того факта, что конструктор Function()
дает каждому экземпляру свойство прототипа.
Свойство prototype
умолчанию является Object()
Все эти разговоры о prototype
могут стать немного тяжелыми. По сути, prototype
— это просто пустое свойство объекта с именем «prototype», созданное JavaScript за кулисами и сделанное доступным с помощью конструктора Function()
. Если бы вы делали это вручную, это выглядело бы примерно так:
Образец: sample120.html
1
2
3
4
5
6
7
8
9
|
<!DOCTYPE html><html lang=»en»><body><script>
var myFunction = function () { };
myFunction.prototype = {};
console.log(myFunction.prototype);
</script></body></html>
|
На самом деле, этот пример кода работает просто отлично, по сути, просто дублируя то, что JavaScript уже делает.
Значением свойства прототипа может быть любое из сложных значений (объектов), доступных в JavaScript. JavaScript будет игнорировать любое свойство прототипа, установленное на примитивное значение.
Экземпляры, созданные из функции конструктора, prototype
свойством prototype
Хотя его prototype
является единственным объектом, он является особенным, поскольку цепочка прототипов связывает каждый экземпляр со свойством прототипа функции конструктора. Это означает, что каждый раз, когда объект создается из функции конструктора с использованием ключевого слова new
(или когда обертка объекта создается для примитивного значения), он добавляет скрытую ссылку между созданным экземпляром объекта и свойством prototype используемой функции конструктора. создать это. Эта ссылка известна внутри экземпляра как __proto__
(хотя она предоставляется / поддерживается только через код в Firefox 2+, Safari, Chrome и Android). JavaScript связывает это вместе в фоновом режиме, когда вызывается функция конструктора, и это ссылка this, которая позволяет цепочке прототипов быть, ну, в общем, цепочкой. В следующем примере мы добавляем свойство к собственному prototype
конструкторов Array()
, к которому мы затем можем получить доступ из экземпляра Array()
используя свойство __proto__
установленное для этого экземпляра.
Образец: sample121.html
1
2
3
4
5
6
7
8
9
|
<!DOCTYPE html><html lang=»en»><body><script>
// This code only works in browsers that support __proto__ access.
Array.prototype.foo = ‘foo’;
var myArray = new Array();
console.log(myArray.__proto__.foo);
</script></body></html>
|
Поскольку доступ к __proto__
не является частью официального стандарта ECMA, существует более универсальный способ отследить ссылку от объекта к объекту-прототипу, который он наследует, и это с помощью свойства constructor
. Это продемонстрировано в следующем примере.
Образец: sample122.html
01
02
03
04
05
06
07
08
09
10
11
12
13
|
<!DOCTYPE html><html lang=»en»><body><script>
Array.prototype.foo = ‘foo’;
var myArray = new Array();
// Trace foo in a verbose way leveraging *.constructor.prototype
console.log(myArray.constructor.prototype.foo);
// Or, of course, leverage the chain.
console.log(myArray.foo) // Logs foo.
// Uses prototype chain to find property at Array.prototype.foo
</script></body></html>
|
В этом примере свойство foo
находится внутри объекта-прототипа. Вы должны понять, что это возможно только из-за связи между экземпляром Array()
и объектом-прототипом конструктора Array()
( Array.prototype
). Проще говоря, myArray.__proto__
(или myArray.constructor.prototype
) ссылается на Array.prototype
.
Последняя остановка в prototype
Chain Is Object.prototype
Поскольку свойство prototype является объектом, последняя остановка в цепочке или поиске прототипа находится в Object.prototype
. В следующем коде я создаю myArray
, который является пустым массивом. Затем я пытаюсь получить доступ к свойству myArray
, которое еще не определено, задействуя цепочку поиска прототипа. Объект myArray
проверяется на наличие свойства foo. При отсутствии свойства свойство Array.prototype
в Array.prototype
, но его там тоже нет. Итак, последнее место, где JavaScript выглядит, это Object.prototype
. Поскольку оно не определено ни в одном из этих трех объектов, свойство не undefined
.
Образец: sample123.html
1
2
3
4
5
6
7
8
9
|
<!DOCTYPE html><html lang=»en»><body><script>
var myArray = [];
console.log(myArray.foo) // Logs undefined.
/* foo was not found at myArray.foo or Array.prototype.foo or Object.prototype.foo, so it is undefined.
</script></body></html>
|
Обратите внимание, что цепочка остановилась с Object.prototype
. Последнее место, где мы искали foo, было Object.prototype
.
Осторожный! Все, что добавлено в Object.prototype, будет отображаться в цикле for.
prototype
цепочки возвращает первое совпадение свойств, которое он находит в цепочке
Как и в цепочке областей действия, цепочка prototype
будет использовать первое значение, найденное во время поиска цепочки.
При изменении предыдущего примера кода, если мы добавили одно и то же значение в объекты Object.prototype
и Array.prototype
, а затем попытались получить доступ к значению в экземпляре массива, возвращаемое значение будет Array.prototype
объекта Array.prototype
.
Образец: sample124.html
01
02
03
04
05
06
07
08
09
10
11
12
13
|
<!DOCTYPE html><html lang=»en»><body><script>
Object.prototype.foo = ‘object-foo’;
Array.prototype.foo = ‘array-foo’;
var myArray = [];
console.log(myArray.foo);
myArray.foo = ‘bar’;
console.log(myArray.foo) // Logs ‘bar’, was found at Array.foo
</script></body></html>
|
В этом примере значение foo в Array.prototype.foo
представляет собой затенение или маскирование значения foo
найденного в Object.prototype.foo
. Просто помните, что поиск заканчивается, когда свойство найдено в цепочке, даже если то же имя свойства также используется дальше по цепочке.
Замена свойства prototype
новым объектом Удаляет свойство конструктора по умолчанию
Возможно заменить значение по умолчанию свойства prototype
новым значением. Однако это исключит свойство конструктора по умолчанию, найденное в «готовом» объекте- prototype
если вы не укажете его вручную.
В следующем коде мы создаем функцию конструктора Foo
, заменяем свойство prototype
новым пустым объектом и проверяем, что свойство конструктора нарушено (теперь оно ссылается на менее полезный прототип Object
).
Образец: sample125.html
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
<!DOCTYPE html><html lang=»en»><body><script>
var Foo = function Foo() { };
Foo.prototype = {};
var FooInstance = new Foo();
console.log(FooInstance.constructor === Foo);
console.log(FooInstance.constructor);
// Compare to code in which we do not replace the prototype value.
var Bar = function Bar() { };
var BarInstance = new Bar();
console.log(BarInstance.constructor === Bar);
console.log(BarInstance.constructor);
</script></body></html>
|
Если вы намереваетесь заменить свойство prototype
по умолчанию (общее с некоторыми шаблонами JS OOP), установленное в JavaScript, вы должны соединить вместе свойство конструктора, которое ссылается на функцию конструктора. В следующем примере мы изменим наш предыдущий код, чтобы свойство constructor
снова предоставило ссылку на соответствующую функцию конструктора.
Образец: sample126.html
01
02
03
04
05
06
07
08
09
10
11
12
|
<!DOCTYPE html><html lang=»en»><body><script>
var Foo = function Foo() { };
Foo.prototype = { constructor: Foo };
var FooInstance = new Foo();
console.log(FooInstance.constructor === Foo);
console.log(FooInstance.constructor);
</script></body></html>
|
Экземпляры, которые наследуют свойства от prototype
всегда получат последние значения
Свойство prototype является динамическим в том смысле, что экземпляры всегда будут получать последнее значение из прототипа независимо от того, когда он был создан, изменен или добавлен. В следующем коде мы создаем конструктор Foo
, добавляем свойство x
к prototype
, а затем создаем экземпляр Foo()
именем FooInstance
. Далее мы записываем значение x
. Затем мы обновляем значение прототипа x и регистрируем его снова, чтобы обнаружить, что наш экземпляр имеет доступ к последнему значению, найденному в объекте prototype
.
Образец: sample127.html
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
<!DOCTYPE html><html lang=»en»><body><script>
var Foo = function Foo() { };
Foo.prototype.x = 1;
var FooInstance = new Foo();
console.log(FooInstance.x);
Foo.prototype.x = 2;
console.log(FooInstance.x);
</script></body></html>
|
Учитывая, как работает цепочка поиска, такое поведение не должно вызывать удивления. Если вам интересно, это работает одинаково, независимо от того, используете ли вы объект- prototype
по умолчанию или переопределяете его своим. В следующем примере я заменяю объект- prototype
по умолчанию, чтобы продемонстрировать этот факт.
Образец: sample128.html
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
<!DOCTYPE html><html lang=»en»><body><script>
var Foo = function Foo() { };
Foo.prototype = { x: 1 };
var FooInstance = new Foo();
console.log(FooInstance.x);
Foo.prototype.x = 2;
console.log(FooInstance.x);
</script></body></html>
|
Замена свойства prototype
новым объектом не обновляет прежние экземпляры
Вы можете подумать, что вы можете полностью заменить свойство prototype
в любое время и что все экземпляры будут обновлены, но это не правильно. Когда вы создаете экземпляр, этот экземпляр будет привязан к prototype
который был создан во время создания экземпляра. Предоставление нового объекта в качестве свойства prototype не обновляет связь между уже созданными экземплярами и новым prototype
.
Но помните, как я уже говорил ранее, вы можете обновить или добавить первоначально созданный объект- prototype
и эти значения остаются подключенными к первому экземпляру (-ам).
Образец: sample129.html
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<!DOCTYPE html><html lang=»en»><body><script>
var Foo = function Foo() { };
Foo.prototype.x = 1;
var FooInstance = new Foo();
console.log(FooInstance.x);
// Now let’s replace/override the prototype object with a new Object() object.
Foo.prototype = { x: 2 };
console.log(FooInstance.x);
/* FooInstance still references the same state of the prototype object that was there when it was instantiated.
// Create a new instance of Foo()
var NewFooInstance = new Foo();
// The new instance is now tied to the new prototype object value ({x:2};).
console.log(NewFooInstance.x);
</script></body></html>
|
Ключевая идея, которую стоит здесь отбросить, состоит в том, что прототип объекта не следует заменять новым объектом, как только вы начнете создавать экземпляры. Это приведет к тому, что экземпляры будут иметь ссылку на разные прототипы.
Пользовательские конструкторы могут использовать то же наследование prototype
конструкторы
Надеемся, что в этот момент в статье описывается, как JavaScript сам использует свойство prototype
для наследования ( Array.prototype
). Этот же шаблон можно использовать при создании не собственных пользовательских функций конструктора. В следующем примере мы берем классический объект Person
и имитируем шаблон, который JavaScript использует для наследования.
Образец: sample130.html
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
<!DOCTYPE html><html lang=»en»><body><script>
var Person = function () { };
// All Person instances inherit the legs, arms, and countLimbs properties.
Person.prototype.legs = 2;
Person.prototype.arms = 2;
Person.prototype.countLimbs = function () { return this.legs + this.arms;
var chuck = new Person();
console.log(chuck.countLimbs());
</script></body></html>
|
В этом коде создается функция конструктора Person()
. Затем мы добавляем свойства в свойство prototype
Person()
, которые могут наследоваться всеми экземплярами. Очевидно, что вы можете использовать цепочку прототипов в своем коде так же, как JavaScript использует ее для наследования нативных объектов.
В качестве хорошего примера того, как вы можете использовать это, вы можете создать функцию конструктора, экземпляры которой наследуют свойства legs
и arms
если они не предоставлены в качестве параметров. В следующем примере, если конструктору Person()
передаются параметры, параметры используются в качестве свойств экземпляра, но если один или несколько параметров не предоставлены, возникает запасной вариант. Эти свойства экземпляра затемняют или маскируют унаследованные свойства, давая вам лучшее из обоих миров.
Образец: sample131.html
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
<!DOCTYPE html><html lang=»en»><body><script>
var Person = function (legs, arms) {
// Shadow prototype value.
if (legs !== undefined) { this.legs = legs;
if (arms !== undefined) { this.arms = arms;
};
Person.prototype.legs = 2;
Person.prototype.arms = 2;
Person.prototype.countLimbs = function () { return this.legs + this.arms;
var chuck = new Person(0, 0);
console.log(chuck.countLimbs());
</script></body></html>
|
Создание цепочек наследования (оригинальное намерение)
Прототипное наследование было задумано так, чтобы позволить цепочки наследования, которые имитируют шаблоны наследования, встречающиеся в традиционных объектно-ориентированных языках программирования. Чтобы один объект наследовал от другого объекта в JavaScript, все, что вам нужно сделать, — это создать экземпляр объекта, от которого вы хотите наследовать, и назначить его свойству prototype
объекта, который выполняет наследование.
В следующем примере кода объекты Chef
( cody
) наследуются от Person()
. Это означает, что если свойство не найдено в объекте Chef
, оно будет проверяться в прототипе функции, создавшей объекты Person()
. Чтобы подключить наследование, все, что вам нужно сделать, это создать экземпляр Person()
в качестве значения для Chef.prototype
( Chef.prototype = new Person();
).
Образец: sample132.html
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
<!DOCTYPE html><html lang=»en»><body><script>
var Person = function () { this.bar = ‘bar’ };
Person.prototype.foo = ‘foo’;
var Chef = function () { this.goo = ‘goo’ };
Chef.prototype = new Person();
var cody = new Chef();
console.log(cody.foo);
console.log(cody.goo);
console.log(cody.bar);
</script></body></html>
|
Вывод
Все, что мы сделали в этом примере, это использовали систему, которая уже была на месте с нативными объектами. Учтите, что Person()
мало чем отличается от значения Object()
по умолчанию для свойств прототипа. Другими словами, это именно то, что происходит, когда свойство prototype, содержащее пустое значение Object()
умолчанию, ищет прототип созданной функции конструктора ( Object.prototype
) для унаследованных свойств.