【JavaScript】原型对象、对象原型、原型继承以及原型链

160 阅读6分钟

什么是原型?

一个生活化的例子:iPhone16 pro max这款手机在生产时使用的模具应是完全相同的,正是因为他们都共有同样的长宽

JS中,这种提取共有资源的办法,就是原型这一概念的核心思想。

function iPhone16ProMax (color){
  this.color = color
}

iPhone16ProMax.prototype = {  // 原型对象
  constructor: iPhone16ProMax,
  price: 9999,
  size: '6.7英寸',
  system: 'iOS 17',
  call: function () {
    console.log('打电话')
  },
  takePhoto: function () {
    console.log('拍照')
  }
}

上述的prototype就是构造函数iPhone16ProMax的原型对象,存放着所有同款手机共有的资源和方法

原型对象 (显式原型)--prototype

  • 函数天生拥有一个属性 prototype, 它的指向是一个对象

  • 挂载在原型上的属性和方法,在实例对象中可以直接访问

  • 实例对象无法修改和删除原型上的属性和方法

意义:

  1. 将一些固定的属性和方法挂载到原型上,在创建实例的时候,就不需要重复执行这些资源

  2. 构造函数和原型对象中的 this 都指向实例化的对象

代码示例:

let that1, that2  //方便后续判断this指向

function Star (uname) {
  that1 = this  
  this.uname = uname
}

Star.prototype.sing = function () {
  that2 = this
  console.log('唱歌')
}

const ldh = new Star('刘德华')
const zxy = new Star('张学友')

// 检验两个完全不同的实例对象是否都共享同一个原型上的sing方法
console.log(ldh === zxy) // false
console.log(ldh.sing === zxy.sing) //true

// 检验构造函数和原型对象中的 this 是否都指向实例化的对象
const FWB = new Star('FWB')
FWB.sing()
console.log(FWB === that1)  // true   构造函数中的 this 就是 实例对象 FWB 
console.log(FWB === that2)  // true   原型对象中的函数 this 还是 实例对象 FWB

对象原型 (隐式原型)--__proto__

  1. 每一个实例对象都拥有一个属性 __proto__, 它完全等于 构造函数的prototype
function Star () {

}

Star.prototype.sing = function () {
  console.log('唱歌')
}

const ldh = new Star()
ldh.sing()
// 对象原型__proto__ 指向 该构造函数的原型对象   --是JS非标准属性,存在实例对象里
console.log(ldh.__proto__)  //  { sing: [Function (anonymous)] }
console.log(ldh.__proto__ === Star.prototype)  // true
// 对象原型里面有constructor 指向 构造函数 Star  
console.log(ldh.__proto__.constructor === Star)  // true
  1. v8在访问对象中的属性时,会先访问对象上的显示拥有的属性,如果找不到,就会去对象的隐式原型中查找,也就是 __proto__ 中查找

追问为什么obj.__proto__ === Object.prototype

其实是new 的原理导致的

new 的原理

  1. 创建一个空对象
  2. 让构造函数的 this 指向这个空对象
  3. 执行构造函数中的代码
  4. 将这个空对象的隐式原型 __proto__ 赋值成 构造函数的原型对象prototype
  5. 返回这个对象
  • 这么设计的意义是什么?

让实例对象继承构造函数原型上的属性和方法,方便我们为某一个数据类型添加属性和方法

constructor属性 --区分原型对象到底属于哪个构造函数

function Star () {
}

// prototype第一种写法:
 Star.prototype.sing = function () {
   console.log('唱歌')
 }
 Star.prototype.dance = function () {
   console.log('跳舞')
 }

// 使用constructor 属性写法:  --constructor属性指向原型对象的构造函数
Star.prototype = {
  // 重新指回创造这个原型对象的 构造函数   --可以用于区分原型对象到底属于哪个构造函数
  constructor: Star,
  sing: function () {
    console.log('唱歌')
  },
  dance: function () {
    console.log('跳舞')
  },
}

const ldh = new Star()
console.log(Star.prototype)  //原型对象

console.log(Star.prototype.constructor)  //指向原型对象的构造函数

console.log(Star.prototype.constructor === Star)  // true

console.log(ldh.__proto__.constructor === Star)  // true

图解原型对象、对象原型、构造函数指向:

原型对象、对象原型、构造函数的关系指向转存失败,建议直接上传图片文件

原型继承

这个时候我来考考你,现在有三个构造函数PersonWomenMen。现在WomenMen构造函数中都未定义具体方法和属性。

需求希望WomenMen的实例有专属的方法,同时共用Person的属性请你用原型帮我写出来

分析

  1. 子类构造函数中不存在方法和属性
  2. 男人、女人的实例对象需要有独立的方法
  3. 男人、女人的实例对象共同拥有人类的可复用属性

实现思路

  1. 实例对象可以共用/继承构造函数的原型上的属性和方法 --可实现男人、女人有独立方法
  2. 逆向使用原型对象,将子类构造函数的原型对象变为父类的实例 --可实现继承父类的方法和属性
  3. 同时将子类的constructor重新指向子类的构造函数 --以防混淆原型对象对数的构造函数

代码示范:

// 人类  构造函数
function Person () {  
  this.eyes = 2
  this.head = 1
}

//女人  构造函数
function Woman () {

}

// 男人 构造函数  
function Man () {

}

// 通过原型来继承 Person
// 父构造函数(父类)   子构造函数(子类)
// 子类的原型 =  new 父类
Woman.prototype = new Person()   // {eyes: 2, head: 1}
Man.prototype = new Person()     // 由于new出来的对象完全不同,所以后续对各自的原型进行操作不共享

// 指回原来的构造函数
Woman.prototype.constructor = Woman  // 防止混淆原型对象归属的构造函数
Man.prototype.constructor = Man

// 给女人添加一个方法  生孩子
Woman.prototype.baby = function () {
  console.log('宝贝')
}
const red = new Woman()
const FWB = new Man()
const person = new Person()

// 检验是否成功继承父类方法,添加生孩子方法后除Women外的对象与函数是否被影响
console.log(Woman.prototype) // 成功指向Person
console.log(red)  // 成功继承父类属性并添加专属baby方法
console.log(FWB)  // 成功继承父类属性,没被误加方法
console.log(person) //没被误加方法

运行结果

结果截图

此时Women实例对象拥有的专属方法,MenPerson没有被影响,同时也继承到了父类的属性

做出来这个其实你也已经掌握原型链的规则了🎉

原型链

v8 在访问对象中的属性时,会先访问对象上的显示拥有的属性,如果找不到,就会去对象的对象原型.__proto__也就是构造函数的原型对象prototype中查找,如果还找不到,就会去 对象原型的对象原型(原型对象的原型).prototype.__proto__ 中找,层层往上,直到找到 null (Object) 为止

v8 的这种查找的链状关系就叫原型链

// function Object() {}
console.log(Object.prototype)  // Object{}
console.log(Object.prototype.__proto__)  // null

function Person () {

}

const ldh = new Person()
console.log(ldh.__proto__ === Person.prototype) // true
console.log(Person.prototype.__proto__ === Object.prototype) // true

原型链示意图

原型链示意图

instanceof

  • instanceof 可以用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上
  • instanceof 的原理
    1. 获取实例对象的原型链
    2. 获取构造函数的prototype属性
    3. 判断构造函数的prototype属性是否在实例对象的原型链上
console.log(ldh instanceof Person)  // true
console.log(ldh instanceof Object)  // true
console.log(ldh instanceof Array)  //  false   --ldh不在数组的原型链上
console.log([1, 2, 3] instanceof Array) // true
console.log(Array instanceof Object) // true

没有原型的对象

  • Object.create(obj) 创建一个新对象,让这个新对象的隐式原型等于传入的obj
  • Object.create(null) 得到一个没有原型的对象
// 创建一个没有原型的对象
const obj = Object.create(null)
console.log(obj.__proto__) // undefined

// 创建一个指定原型的对象
const parent = { name: 'parent' }
const child = Object.create(parent)
console.log(child.name) // 'parent' - 来自指定的原型