Помните, что новые объекты можно создавать с помощью функции-конструктора, например new F()
.
Если F.prototype
является объектом, то оператор new
использует его для установки [[Prototype]]
для нового объекта.
Пожалуйста, обрати внимание:
В JavaScript с самого начала было прототипное наследование. Это была одна из основных особенностей языка.
Но в прежние времена прямого доступа к нему не было. Единственное, что работало надежно, — это свойство "prototype"
функции-конструктора, описанное в этой главе. Итак, существует множество скриптов, которые до сих пор его используют.
Обратите внимание, что здесь F.prototype
означает обычное свойство F
с именем "prototype"
. Звучит это чем-то похоже на термин «прототип», но на самом деле здесь имеется в виду обычное свойство с таким названием.
Вот пример:
пусть животное = { ест: правда }; функция Кролик(имя) { это.имя = имя; } Rabbit.prototype = животное; let Rabbit = new Rabbit("Белый Кролик"); // кролик.__proto__ == животное оповещение(кролик.ест); // истинный
Установка Rabbit.prototype = animal
буквально гласит следующее: «Когда создается new Rabbit
, назначьте его [[Prototype]]
animal
».
Вот получившаяся картинка:
На картинке "prototype"
— горизонтальная стрелка, означающая регулярное свойство, а [[Prototype]]
— вертикальная, означающая наследование rabbit
от animal
.
F.prototype
используется только в new F
Свойство F.prototype
используется только при вызове new F
Оно присваивает [[Prototype]]
нового объекта.
Если после создания свойство F.prototype
изменится ( F.prototype = <another object>
), то новые объекты, созданные new F
будут иметь другой объект как [[Prototype]]
, но уже существующие объекты сохранят старый.
Каждая функция имеет свойство "prototype"
даже если мы его не предоставляем.
"prototype"
по умолчанию — это объект с единственным constructor
свойств, указывающим на саму функцию.
Так:
функция Кролик() {} /* прототип по умолчанию Rabbit.prototype = {конструктор: Rabbit}; */
Мы можем это проверить:
функция Кролик() {} // по умолчанию: // Rabbit.prototype = { конструктор: Rabbit } alert( Rabbit.prototype.constructor == Rabbit); // истинный
Естественно, если мы ничего не делаем, свойство constructor
доступно всем кроликам через [[Prototype]]
:
функция Кролик() {} // по умолчанию: // Rabbit.prototype = { конструктор: Rabbit } пусть кролик = новый кролик (); // наследуется от {конструктора: Rabbit} alert(rabbit.constructor == Кролик); // правда (из прототипа)
Мы можем использовать свойство constructor
для создания нового объекта, используя тот же конструктор, что и существующий.
Как здесь:
функция Кролик(имя) { это.имя = имя; оповещение (имя); } let Rabbit = new Rabbit("Белый Кролик"); let Rabbit2 = новый Rabbit.constructor("Черный Кролик");
Это удобно, когда у нас есть объект, и мы не знаем, какой конструктор для него использовался (например, он взят из сторонней библиотеки), и нам нужно создать еще один такой же тип.
Но, наверное, самое главное в "constructor"
то, что…
…JavaScript сам по себе не гарантирует правильное значение "constructor"
.
Да, он существует в "prototype"
функций по умолчанию, но это все. Что с ним будет дальше – полностью зависит от нас.
В частности, если мы заменим дефолтный прототип целиком, то "constructor"
в нем не будет.
Например:
функция Кролик() {} Кролик.прототип = { прыжки: правда }; пусть кролик = новый кролик (); alert(rabbit.constructor === Кролик); // ЛОЖЬ
Итак, чтобы сохранить правильный "constructor"
мы можем добавить/удалить свойства к "prototype"
по умолчанию вместо того, чтобы перезаписывать его целиком:
функция Кролик() {} // Не перезаписывать Rabbit.prototype полностью // просто добавим к нему Rabbit.prototype.jumps = правда // сохраняется конструктор Rabbit.prototype.constructor по умолчанию
Или, альтернативно, воссоздайте свойство constructor
вручную:
Кролик.прототип = { прыжки: правда, конструктор: Кролик }; // теперь конструктор тоже правильный, потому что мы его добавили
В этой главе мы кратко описали способ установки [[Prototype]]
для объектов, созданных с помощью функции-конструктора. Позже мы увидим более сложные шаблоны программирования, основанные на этом.
Все довольно просто, всего несколько замечаний, чтобы все было понятно:
Свойство F.prototype
(не путайте его с [[Prototype]]
) устанавливает [[Prototype]]
новых объектов при вызове new F()
.
Значение F.prototype
должно быть либо объектом, либо null
: другие значения не будут работать.
Свойство "prototype"
имеет такой особый эффект только тогда, когда оно установлено в функции-конструкторе и вызывается с помощью new
.
На обычных объектах prototype
не представляет собой ничего особенного:
пусть пользователь = { имя: «Джон», прототип: "Бла-бла" // никакой магии };
По умолчанию все функции имеют F.prototype = { constructor: F }
, поэтому мы можем получить конструктор объекта, обратившись к его свойству "constructor"
.
важность: 5
В приведенном ниже коде мы создаем new Rabbit
, а затем пытаемся изменить его прототип.
В начале у нас есть такой код:
функция Кролик() {} Кролик.прототип = { ест: правда }; пусть кролик = новый кролик (); оповещение(кролик.ест); // истинный
Мы добавили еще одну строку (выделено). Что теперь покажет alert
?
функция Кролик() {} Кролик.прототип = { ест: правда }; пусть кролик = новый кролик (); Кролик.прототип = {}; оповещение(кролик.ест); // ?
…А если код такой (заменена одна строка)?
функция Кролик() {} Кролик.прототип = { ест: правда }; пусть кролик = новый кролик (); Rabbit.prototype.eats = ложь; оповещение(кролик.ест); // ?
И вот так (заменил одну строчку)?
функция Кролик() {} Кролик.прототип = { ест: правда }; пусть кролик = новый кролик (); удалить Rabbit.EATS; оповещение(кролик.ест); // ?
Последний вариант:
функция Кролик() {} Кролик.прототип = { ест: правда }; пусть кролик = новый кролик (); удалить Rabbit.prototype.eats; оповещение(кролик.ест); // ?
Ответы:
true
.
Присвоение Rabbit.prototype
устанавливает [[Prototype]]
для новых объектов, но не влияет на существующие.
false
.
Объекты назначаются по ссылке. Объект из Rabbit.prototype
не дублируется, это по-прежнему один объект, на который ссылается как Rabbit.prototype
, так и [[Prototype]]
rabbit
.
Поэтому, когда мы меняем его содержимое по одной ссылке, оно видно по другой.
true
.
Все операции delete
применяются непосредственно к объекту. Здесь delete rabbit.eats
пытается удалить свойство eats
из rabbit
, но у него его нет. Так что операция не даст никакого эффекта.
undefined
.
Свойство eats
удалено из прототипа и больше не существует.
важность: 5
Представьте, что у нас есть произвольный объект obj
, созданный функцией-конструктором — мы не знаем, какой именно, но нам бы хотелось создать с его помощью новый объект.
Можем ли мы сделать это так?
пусть obj2 = новый obj.constructor();
Приведите пример функции-конструктора для obj
, которая позволяет такому коду работать правильно. И пример, который заставляет это работать неправильно.
Мы можем использовать такой подход, если уверены, что свойство "constructor"
имеет правильное значение.
Например, если мы не трогаем стандартный "prototype"
, то этот код точно работает:
функция Пользователь(имя) { это.имя = имя; } пусть пользователь = новый пользователь('Джон'); let user2 = новый user.constructor('Пит'); оповещение(имя_пользователя2); // Пит (сработал!)
Это сработало, потому что User.prototype.constructor == User
.
…Но если кто-то, так сказать, перезапишет User.prototype
и забудет воссоздать constructor
для ссылки на User
, то это потерпит неудачу.
Например:
функция Пользователь(имя) { это.имя = имя; } Пользователь.прототип = {}; // (*) пусть пользователь = новый пользователь('Джон'); let user2 = новый user.constructor('Пит'); оповещение(имя_пользователя2); // неопределенный
Почему user2.name
undefined
?
Вот как работает new user.constructor('Pete')
:
Сначала он ищет constructor
в user
. Ничего.
Затем следует цепочка прототипов. Прототипом user
является User.prototype
, и у него также нет constructor
(потому что мы «забыли» его правильно настроить!).
Если идти дальше по цепочке, User.prototype
— это простой объект, его прототипом является встроенный Object.prototype
.
Наконец, для встроенного Object.prototype
есть встроенный Object.prototype.constructor == Object
. Поэтому его используют.
Наконец, в конце мы let user2 = new Object('Pete')
.
Наверное, это не то, чего мы хотим. Мы хотели бы создать new User
, а не new Object
. Это результат отсутствия constructor
.
(На случай, если вам интересно, вызов new Object(...)
преобразует свой аргумент в объект. Это теоретическая вещь, на практике никто не вызывает new Object
со значением, и обычно мы не используем new Object
вообще создавать предметы).