Прототипы и цепочки прототипов

JavaScript
Прототипы и цепочки прототипов

Прототипы и цепочки прототипов

Давайте сначала посмотрим на отношения между конструкторами, экземплярами и объектами-прототипами.

«Существует прямая связь между экземплярами и объектами-прототипами, но не между экземплярами и конструкторами».

две концепции

js делится на «функциональные объекты» и «обычные объекты», каждый объект имеет__proto__свойства, но только для функциональных объектов и "нестрелочных функций"prototypeАтрибуты.

  1. Атрибуты__proto__является объектом [экземпляр__proto__Неявный прототип указывает на свой объект-прототип], который имеет два свойства:constructorа также__proto__;
  2. Объекты-прототипы имеют значение по умолчанию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, что случилось?

  1. Создайте пустой объект в качестве возвращаемого экземпляра объекта.
  2. Укажите прототип этого пустого объекта на конструкторprototypeАтрибуты
  3. Назначьте этот пустой объект функции внутри функцииthisключевые слова
  4. Начать выполнение кода внутри конструктора
  5. Если конструктор возвращает объект, то вернуть объект напрямую, в противном случае вернуть созданный объект

То есть внутри конструктора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__
  }
}