原型和原型链

原型

原型是 function 对象的一个属性,它定义了构造函数制造出来的对象的公有祖先,通过该构造函数产生的对象,可以继承该原型的属性和方法,原型其实就是一个对象。person 查找变量,首先找自己身上的,没有就通过 __proto__ 去查找祖先的属性。需要注意的是在 new 操作时候,其实就是在构造函数内部定义了一个 this,然后 this 下面有很多属性,其中有一个 __proto__ 隐式原型属性,它的值就是指向 Person 的原型,也就是 Person.prototype。这也是 person 怎么查找祖先的根本。

function Person(){}

let person = new Person()

Person.prototype.name = 'name'

console.log(person.name) // 'name'

// 理解 __proto__
let obj = {
  name: 'proto_name'
}

person.__proto__ = obj

console.log(person.name) // 'proto_name'

因此,如下操作可以视为等价

let person = new Person()

// 等价于
function Person(){
  let this = {
    __proto__: Person.prototype
  }
}

原型链

每个显式原型下也有一个隐式原型,指向规则和上面一样。在每一层首先寻找自身的属性或方法,如果没有通过隐式原型去找上一层的显示原型的方法,依次形成一个链

原型链的最高层 Object 的显式原型拥有 toStringhasOwnProperty 方法和自身隐式原型指向 null

// 最高层
Object.prototype.__proto__ === null   // true

// person 原型链
let person = new Person()
person.__proto__ === Person.prototype   // true
person.__proto__.__proto__ === Object.prototype // true
person.__proto__.__proto__.__proto__ === null // true

Person.__proto__ === Function.prototype // true
Person.__proto__.__proto__ === Object.prototype // true

// 内置的构造函数也都算是Function的实例
Array.__proto__ === Function.prototype // true
Date.__proto__ === Function.prototype // true
Function.__proto__ === Function.prototype // true
Object.__proto__ === Function.prototype // true

// 构造函数
Person.prototype.constructor === Person // true
person.constructor === Person // true

new 过程

  • 创建一个空的简单 JavaScript 对象 (即 {});
  • 为步骤 1 新创建的对象添加属性 proto,将该属性链接至构造函数的原型对象;
  • 将步骤 1 新创建的对象作为 this 的上下文;
  • 如果该函数没有返回对象,则返回 this。

代码实现如下

function New(constructor, ...args){
  // let obj = {} // obj = new Object(), obj = Object.create(null)
  // Object.create https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create
  let obj = Object.create(constructor.prototype)
  
  // obj.__proto__ = constructor.prototype

  // set obj as the context of `this` when the constructor runs
  // apply for array: New(Employee, ['joe','New'])
  // call for single: New(Employee, 'joe','New')

  let results = constructor.apply(obj, args)

  return results instanceof Object ? results : obj
}

// ================ test case =================== 
function Employee(args) {
  this.name = args[0];
  this.title = args[1];
  console.log('constructor, this = ', this);
}

let p = New(Employee, ['joe','New'])

p.constructor === Employee          // true
p.__proto__ === Employee.prototype  // true
// output
// {
//     "name": "joe",
//     "title": "New"
// }

Object.create

function create(proto){

  if (typeof proto !== 'object' && typeof proto !== 'function') {
            throw new TypeError('Object prototype may only be an Object: ' + proto);
        } else if (proto === null) {
            throw new Error("This browser's implementation of Object.create is a shim and doesn't support 'null' as the first argument.");
        }

  function F(){}
  F.prototype = proto

  return obj
}

继承

原型链继承

继承方式虽然简单,但是会继承很多多余的属性

function Parent(){}
Parent.prototype.name = 'parent'

function Child(){}

Child.prototype = new Parent()

let child = new Child()
child.name // 'parent'

构造函数继承

调用父级构造函数逇方式其实和上面那个一样,虽然写法好看了,但是无法借用构造函数的原型

function Parent(name = 'parent'){
  this.name = name
}
Parent.prototype.name = 'parent'

function Child(name){
  Parent.call(this, name)
}

let child = new Child()
child.name // 'parent'

公有原型继承

无法新增自己的原型属性,会互相影响

Father.prototype.lastName = "李雷"
function Father(){
}
function Son(){}
// 两个构造函数继承同一个原型
Son.prototype = Father.prototype
var son = new Son()
var father = new Father()
Son.prototype.name = "lihua" //这样会导致Father.prototype = "lihua"

圣杯模式继承

使用了一个中间函数做过渡,弥补前两种方式的缺点,解决了公有原型无法增加私有原型属性的缺点

function extend(target, origin){
  function F(){}

  F.prototype = origin.prototype

  target.prototype = new F()

  target.prototype.constructor = target
}

寄生组合继承

只调用一次父类的构造函数,避免了在子类原型上创建不必要的,多余的属性 原型链保持不变

function inheritPrototype(Child,Parent) {
  // Object.create 可以将subPrototype的原型(__proto__) 设置成 Parent 的原型对象(prototype);
  let subPrototype = Object.create(Parent.prototype)

  // 因为 Object.prototype.constructor 会返回对象本身,所以这里设置为 Child 本身;
  subPrototype.constructor = Child

  // 这里将 Child 的原型对象(prototype)设置成subPrototype,实现 Child 对 Parent 基于原型链的继承;
  Child.prototype = subPrototype
}
上次更新:
贡献者: Joe