Когда я впервые перешел с Ruby на JavaScript, мне было сложно понять концепцию прототипа объекта и цепочку прототипов. Ключевое слово class
, введенное в ES2015, является просто синтаксическим сахаром и не облегчает понимание.
Тем не менее, независимо от того, на основе прототипа или класса, причина иметь порядок разрешения цепочки или метода (в Python) состоит в том, чтобы установить связи между классами и лучше использовать отношения наследования.
Что касается наследования, в JavaScript есть только одна конструкция: объекты. У каждого объекта есть частное свойство, называемое prototype
object, которое действует как объект шаблона, который наследует методы и свойства от другого объекта (например, __mro__
в Python). У этого объекта-прототипа есть собственный прототип, и так до тех пор, пока не будет достигнут объект с null
в качестве прототипа. По определению, null
не имеет прототипа и действует как последнее звено в этой цепочке прототипов. Перед достижением null
предпоследняя остановка должна быть Object
, поскольку почти все объекты в JavaScript являются экземплярами Object
.
Как это работает? Что ж, давайте посмотрим на пример.
let f = function () { this.a = 1; this.b = 2; } let o = new f(); // {a: 1, b: 2}
f.prototype.b = 3; f.prototype.c = 4;
Сейчас:
- o.[[Prototype]]
имеет свойства b и c, и
- o.[[Prototype]].[[Prototype]]
is Object.prototype, и
- o.[[Prototype]].[[Prototype]].[[Prototype]]
имеет значение null.
Итак, вся цепочка собственности выглядит так:
{a: 1, b: 2} ---> {b: 3, c: 4} ---> Object.prototype ---> null
Обратите внимание, что {a: 1, b: 2}
являются собственными свойствами объекта f, которые не наследуются ни от кого другого.
Когда мы пытаемся получить доступ к o.b
, мы получаем значение 2
. Почему не 3
? Потому что .b
существует как f
собственное свойство, и если оно определено, то значение считывается, и обход цепочки прототипов здесь останавливается. Даже у прототипа есть свойство b
, но оно не посещается. Это называется Затенение свойств.
Давайте рассмотрим более конкретный пример прохождения по цепочке прототипов.
function doSomething(){} doSomething.prototype.foo = "bar"; var doSomeInstancing = new doSomething(); doSomeInstancing.prop = "some value";
console.log(doSomeInstancing.prop); //some value console.log(doSomeInstancing.foo); // bar console.log(doSomething.prop); // undefined console.log(doSomething.foo); // undefined console.log(doSomething.prototype.prop);// undefined console.log(doSomething.prototype.foo); // bar
Как видно выше, __proto__
из doSomeInstancing
равно doSomething.prototype
.
Когда вы получаете доступ к свойству doSomeInstancing
, браузер сначала проверяет, есть ли у doSomeInstancing
это свойство.
Если doSomeInstancing
не имеет свойства, браузер ищет свойство в __proto__
из doSomeInstancing
(он же doSomething.prototype
).
Если __proto__
doSomeInstancing не имеет свойства, тогда браузер ищет __proto__
из __proto__
doSomeInstancing, в данном случае window.Object.prototype
.
Если свойство не найдено, просматривается __proto__
из __proto__
из __proto__
doSomeInstancing i, и в этом случае null
.
Но __proto__
не существует на нуле. Таким образом, возвращается результат undefined.
Короче говоря, prototype
- это свойство объекта Function. Это прототип объектов, созданных этой функцией.
__proto__
- внутреннее свойство объекта, указывающее на его прототип.
function example(x, y) {
this.x = x;
this.y = y;
}
Когда JavaScript выполняет этот код, он добавляет свойство prototype
к example
, свойство prototype
- это объект с двумя свойствами:
constructor
__proto__
Итак, когда мы делаем example.prototype
, он возвращается
constructor: example(x,y)
__proto__: Object
Теперь, как вы можете видеть, constructor
- это не что иное, как сама функция example
, а __proto__
указывает на корневой уровень Object
JavaScript.
var myExample = new example();
В строке выше мы создаем экземпляр example
. Как это работает?
- Сначала он создает новый пустой объект
{}
- Он создает
__proto__
наmyExample
и указывает наexample.prototype
, поэтомуmyExample.__proto__ === example.prototype
- Он выполняет
example.prototype.constructor
(что является определением функцииexample
) с вновь созданным пустым объектом в качестве контекста (this
), поэтому свойствоx,y
добавляется к вновь созданному объекту. - Возвращает вновь созданный объект
// the following are all true
myExample.__proto__ == example.prototype
myExample.__proto__.__proto__ == Object.prototype
myExample.__proto__.__proto__.__proto__ == null
myExample instanceof example;
myExample instanceof Object;
Существуют разные способы создания объектов и результирующей цепочки прототипов.
- Объекты, созданные с помощью синтаксических конструкций
var o = {a: 1};
- С конструктором
«Конструктор» в JavaScript - это «просто» функция, которая вызывается с помощью оператора new.
function Rectangle() { this.length = 2; this.width = 3; }
Rectangle.prototype = { area: function() { this.length * this.width; } };
var re = new Rectangle(); // re is an object with own properties 'length' and 'width'. // re.[[Prototype]] is the value of Rectangle.prototype when new Rectangle() is executed.
- С
Object.create
В ECMAScript 5 появился новый метод: Object.create()
. Вызов этого метода создает новый объект. прототип этого объекта является первым аргументом функции:
var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (inherited)
- С ключевым словом
class
ECMAScript 2015 представил новый набор ключевых слов, реализующих классы. Новые ключевые слова включают class
, constructor
, static
, extends
и super
.
Будьте осторожны, открывая или изменяя prototype
.
Обратите внимание, что __proto__
считается амортизированным. Если возможно, вам следует использовать следующие методы.
Object.create(proto, [descriptors])
- создает пустой объект с заданнымproto
as[[Prototype]]
и дополнительными дескрипторами свойств.Object.getPrototypeOf(obj)
- возвращает[[Prototype]]
изobj
.Object.setPrototypeOf(obj, proto)
- устанавливает[[Prototype]]
дляobj
наproto.
Но не изменяйте prototype
объекта на лету, если это возможно, так как он должен создаваться только при создании объекта. Механизмы JavaScript оптимизированы для этого. Изменение prototype
на более позднем этапе нарушит внутреннюю оптимизацию операций доступа к свойствам объекта.
Вот и все сегодня!
Приятного чтения!