Прототипы и цепочки прототипов
Давайте сначала посмотрим на отношения между конструкторами, экземплярами и объектами-прототипами.
«Существует прямая связь между экземплярами и объектами-прототипами, но не между экземплярами и конструкторами».
две концепции
js делится на «функциональные объекты» и «обычные объекты», каждый объект имеет__proto__
свойства, но только для функциональных объектов и "нестрелочных функций"prototype
Атрибуты.
- Атрибуты
__proto__
является объектом [экземпляр__proto__
Неявный прототип указывает на свой объект-прототип], который имеет два свойства:constructor
а также__proto__
; - Объекты-прототипы имеют значение по умолчанию
constructor
свойство для записи того, каким конструктором был создан экземпляр;
прототип
понять архетип
Создание функции (не стрелочной функции) создает свойство прототипа (указывающее на объект-прототип) для функции в соответствии с определенными правилами. По умолчанию все объекты-прототипы автоматически получают свойство с именем конструктор, которое указывает на связанную с ними функцию-конструктор. При настройке конструктора объект-прототип по умолчанию получает только свойство конструктора, а все остальные методы наследуются от Object. Каждый раз, когда вызывается конструктор для создания нового экземпляра, внутреннему указателю [[Prototype]] экземпляра присваивается объект-прототип конструктора. Не существует стандартного способа доступа к этой функции [[Prototype]] в сценариях, но Firefox, Safari и Chrome предоставляют ее для каждого объекта.__proto__
свойство, через которое можно получить доступ к прототипу объекта.
function Person() {}
// 说明:name,age,job这些本不应该放在原型上,只是为了说明属性查找机制
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
console.log(this.name);
};
let person1 = new Person()
let person2 = new Person()
// 声明之后,构造函数就有了一个与之关联的原型对象
console.log(Object.prototype.toString.call(Person.prototype)) // [object Object]
console.log(Person.prototype) // {constructor: ƒ}
// 构造函数有一个 prototype 属性引用其原型对象,而这个原型对象也有一个constructor 属性,引用这个构造函数
// 换句话说,两者循环引用
console.log(Person.prototype.constructor === Person); // true
// 构造函数、原型对象和实例是 3 个完全不同的对象
console.log(person1 !== Person); // true
console.log(person1 !== Person.prototype); // true
console.log(Person.prototype !== Person); // true
// 实例通过__proto__链接到原型对象,它实际上指向隐藏特性[[Prototype]]
// 构造函数通过 prototype 属性链接到原型对象,实例与构造函数没有直接联系,与原型对象有直接联系,后面将会画图再次说明这个问题
console.log(person1.__proto__ === Person.prototype); // true
conosle.log(person1.__proto__.constructor === Person); // true
// 同一个构造函数创建的两个实例,共享同一个原型对象
console.log(person1.__proto__ === person2.__proto__); // true
// Object.getPrototypeOf(),返回参数的内部特性[[Prototype]]的值 ,用于获取原型对象,兼容性更好
console.log(Object.getPrototypeOf(person1) == Person.prototype); // true
Как показано ниже:
Person.prototype указывает на объект-прототип, а Person.prototype.contructor указывает обратно на конструктор Person. Объект-прототип содержит свойство конструктора и другие свойства, добавленные позже. Оба экземпляра Person, person1 и person2, имеют только одно внутреннее свойство, указывающее на Person.prototype, и ни один из них не имеет прямой связи с конструктором.
уровень прототипа
При доступе к свойству через объект поиск начинается с имени свойства. Поиск начинается с самого экземпляра объекта. Если заданное имя найдено в этом экземпляре, возвращается значение, соответствующее этому имени. Если свойство не найдено, поиск следует по указателю на объект-прототип, находит свойство в объекте-прототипе и возвращает соответствующее значение. Следовательно, когда вызывается person1.sayName(), происходит двухэтапный поиск. Во-первых, движок JavaScript спрашивает: «Есть ли у экземпляра person1 свойство sayName?» Ответ — нет. Затем продолжите поиск и спросите: «Есть ли у прототипа person1 свойство sayName?» Ответ — да. Таким образом, функция, сохраненная в прототипе, возвращается. При вызове person2.sayName() происходит тот же процесс поиска и возвращаются те же результаты. Именно так прототипы используются для совместного использования свойств и методов несколькими экземплярами объекта.
Сеть прототипов
Пересмотрите отношения между конструкторами, прототипами и экземплярами: у каждого конструктора есть прототип, указывающий на объект-прототип, у объекта-прототипа есть свойство конструктора, указывающее обратно на конструктор, а у экземпляра есть внутренний указатель на прототип. Что, если прототип является экземпляром другого типа? Это означает, что сам прототип имеет внутренний указатель на другой прототип, который, в свою очередь, имеет указатель на другой конструктор. Это создает цепочку прототипов между экземпляром и прототипом. Это основная идея цепочки прототипов.
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function () {
return this.property;
};
function SubType() {
this.subproperty = false;
}
// 继承 SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () {
return this.subproperty;
};
let instance = new SubType();
console.log(instance.getSuperValue()); // true
Типы SuperType и SubType определяют свойство и метод соответственно. Основное различие между этими двумя типами заключается в том, что SubType реализует наследование SuperType, создавая экземпляр SuperType и присваивая его собственному прототипу SubTtype.prototype. Это назначение переопределяет исходный прототип SubType, заменяя его экземпляром SuperType. Это означает, что все свойства и методы, доступные для экземпляра SuperType, также будут существовать в SubType.prototype. После реализации наследования таким образом код добавляет новый метод в SubType.prototype, который является экземпляром этого SuperType. Наконец, создается экземпляр SubType и вызывается унаследованный от него метод getSuperValue().
имитировать новый
использоватьnew
, что случилось?
- Создайте пустой объект в качестве возвращаемого экземпляра объекта.
- Укажите прототип этого пустого объекта на конструктор
prototype
Атрибуты - Назначьте этот пустой объект функции внутри функции
this
ключевые слова - Начать выполнение кода внутри конструктора
- Если конструктор возвращает объект, то вернуть объект напрямую, в противном случае вернуть созданный объект
То есть внутри конструктораthis
Относится к вновь сгенерированному пустому объекту, все объектыthis
Все операции будут происходить над этим пустым объектом. Причина, по которой конструктор называется «конструктор», означает, что целью этой функции является работа с пустым объектом (т.this
объект), «конструируя» его по мере необходимости.
function simulateNew() {
let newObject = null,result = null,
constructor = Array.prototype.shift.call(arguments)
// 参数判断
if (typeof constructor !== 'function') {
console.error('type error')
return
}
// 新建一个空对象,对象的原型为构造函数的 prototype 对象
newObject = Object.create(constructor.prototype)
// 将 this 指向新建对象,并执行函数
result = constructor.apply(newObject, arguments)
// 判断返回对象
const flag =
result && (typeof result === 'object' || typeof result === 'function')
// 判断返回结果
return flag ? result : newObject
}
/** 测试如下 */
function Person(name) {
this.name = name
}
const p1 = new Person("p1")
const p2 = simulateNew(Person, 'p2')
console.log("p1",p1, p1 instanceof Person);
console.log('p2', p2, p2 instanceof Person)
имитировать instanceof
instanceof
Основным принципом реализации является«Пока переменная справа» prototype
«На цепочке прототипов переменной слева». следовательно,instanceof
В процессе поиска будет проходиться цепочка прототипов левой переменной до тех пор, пока не будет найдена правая переменная.prototype
, который вернется, если поиск завершится ошибкойfalse
, что говорит нам о том, что переменная слева не является экземпляром переменной справа.
function instanceOf(leftVaule, rightVaule) {
let rightProto = rightVaule.prototype; // 取右表达式的 prototype 值
leftVaule = leftVaule.__proto__; // 取左表达式的__proto__值
while (true) {
if (leftVaule === null) {
return false;
}
if (leftVaule === rightProto) {
return true;
}
leftVaule = leftVaule.__proto__
}
}