JS原型与继承

前言

通过这篇文章我们了解一下JS的原型和实现继承的几种方式,以及他们各自的优缺点。

原型链

基本思想就是通过原型继承多个引用类型的属性和方法,我们来重温一个构造函数、原型和实例的关系。 每个构造函数都有原型对象(prototype),原型有一个属性(constructor)指回构造函数,而实例有一个内部指针(proto)指向原型,如果这个原型是另一个类型的实例呢?那么就意味着这个原型内部也有一个指针指向另一个原型,另一个原型也有一个属性指向另一个构造函数,这样就形成了一个原型链。

例如:c1.prototype = instance2 如果试图引用c1构造的实例instance1中的某个属性p1

  1. 首先会在instance1 内部找一遍
  2. 然后会在instance1.proto(c1.prorotype)中找,而c1.prototype 实际上是instance2,也就是在instance2中找
  3. 如果instance2中还没找到,会继续往上找instance2.proto,直到Object的原型对象。

instance1->instance2->instance2._proto->....->Object.prototype

原型链的问题

  1. 当原型链中包含引用值类型值是,该引用类型值会被所有实例共享。
  2. 子类型在实例化时不能给父类型的构造函数传参。

盗用构造函数(经典继承)

为了解决原型中包含引用值导致的继承问题 基本思路:在子类构造函数中调用父类构造函数。

typescript 复制代码
function father(){
  this.arr = [1,2,3]
}

function son(){
  father.call(this) // 继承father  可以传递参数 用 apply 也可以
}

const instance1 = new son()
instance1.arr.push(4)
console.log(instance1.arr) // 1,2,3,4
const instance2  = new son()
console.log(instance2.arr) // 1,2,3

当然这种继承方法也有问题,方法都在构造函数中定义,因此son函数也不能复用了,而且father 原型中定义的方法也不能访问。

组合继承(伪经典继承)

基本思路:使用原型链实现对原型属性和方法的继承,同时通过借用构造函数来实现对实例属性的继承。

typescript 复制代码
function father(name){
  this.name = name
  this.arr = [1,2,3]
}

father.prototype.sayName = function(){
  console.log(this.name)
}

function son(name,age){
  father.call(this,name) // 继承实例属性   第一次调用father
  this.age = age
}

son.prototype = new father() // 继承父类方法  第二次调用father
son.prototype.sayAge = function(){
  console.log(this.age)
}

let instance1 = new son('tao',5)
instance1.arr.push(4)
console.log(instance1.arr) // 1,2,3,4
instance1.sayName() // tao
instance1.sayAge() // 5

let instance2 = new son('lin',10)
console.log(instance2.arr) // 1,2,3
instance2.sayName() // lin
instance2.sayAge() // 10

这样就解决了原型链和盗用构造函数的缺陷,但是父类构造函数会调用两次。

原型式继承

基本思路:在object() 函数内部,创建一个临时的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时构造函数的实例。

typescript 复制代码
function object(o){
  function F(){}
  F.prototype = o
  return new F()
}

本质上讲,object() 函数是对传入的对象进行了一次浅复制

typescript 复制代码
let p = {
  name:['tao','lin']
}

let a1 = object(p)
a1.name.push('zhange')
let a2 = object(p)
a2.name.push('xu')
console.log(p.name)

把p 传入object() 函数中,然后返回一个新对象,这个新对象的原型指向p,因此新对象的原型中还包含引用类型的属性。 其实现在Object.create()方法已经规范了上面的原型式继承。

寄生式继承

基本思路:创建一个用于封装继承过程的函数,在函数内部以某种方式来增强对象,然后再返回这个对象

typescript 复制代码
function create(o){
  let clone = object(o) // 创建一个新对象,其他方法也可以
  clone.sayName = function(){ // 以某种方式增强这个对象
    console.log('li')
  }
  return clone
}

这个方法中基于 o 返回了一个新对象,新对象不仅具有 o 的属性和方法,还被增强了。 使用寄生式继承来为对象添加函数,也会导致函数难以复用,这一点与盗用构造函数相似

寄生组合式继承

基本思路:不通过调用父类构造函数给子类原型赋值,而是获取父类原型的一个副本。

typescript 复制代码
function extend(son,father){
  let prototype = Object.create(father.prototype) // 创建父类原型的副本
  prototype.constructor = son // 设置新对象的constructor,解决重写原型导致constructor丢失问题
  son.prototype = prototype // 将新创建的对象赋值给子类原型
}


function father(name){
  this.name = name
  this.arr = [1,2,3]
}

father.prototype.sayName = function(){
  console.log(this.name)
}

function son(name,age){
  father.call(this,name)  // 一次调用
  this.age = age
}

extend(son,father) // 

son.prototype.sayAge = function(){
  console.log(this.age)
}

let instance1 = new son('tao',5)
instance1.arr.push(4)
console.log(instance1.arr) // 1,2,3,4
instance1.sayName() // tao
instance1.sayAge() // 5

let instance2 = new son('lin',10)
console.log(instance2.arr) // 1,2,3
instance2.sayName() // lin
instance2.sayAge() // 10

这种继承模式可以是引用类型继承的最佳方式。

class继承

class继承本质上还是基于原型机制的语法糖,具体请看阮一峰老师的es6 class 这一章节。

总结

ES5 有5种实现继承的方式:

  1. 原型继承
  2. 盗用构造函数继承
  3. 组合继承
  4. 原型式继承
  5. 寄生式继承
  6. 寄生组合式继承

ES6 则可以通过class关键字来实现类继承。

如果本文对你有一点帮助,请点一个大大的赞,你的支持就是我创作的动力。

相关推荐
Data_Journal6 小时前
Node.js网络爬取指南——简单易上手!
大数据·linux·服务器·前端·javascript
a1117766 小时前
可视化角色权限配置页面(html 开源)
前端·开源·html
Lee川6 小时前
个人中心与 AI 头像生成:从页面到 DALL-E 的完整实现
前端·架构
tedcloud12311 小时前
UI-TARS-desktop部署教程:构建AI桌面自动化系统
服务器·前端·人工智能·ui·自动化·github
UXbot14 小时前
AI原型设计工具如何支持团队协作与快速迭代
前端·交互·个人开发·ai编程·原型模式
ZC跨境爬虫15 小时前
跟着MDN学HTML_day_48:(Node接口)
前端·javascript·ui·html·音视频
PieroPc17 小时前
CAMWATCH — 局域网摄像头监控系统 Fastapi + html
前端·python·html·fastapi·监控
巴巴博一18 小时前
2026 最新:Trae / Cursor 一键接入 taste-skill 完整教程(让 AI 前端告别“AI 味”)
前端·ai·ai编程
kyriewen18 小时前
半夜三点线上崩了,AI替我背了锅——用AI排错,五分钟定位三年老bug
前端·javascript·ai编程
kyriewen18 小时前
我让 AI 当了 24 小时全年无休的“毒舌考官”
前端·ci/cd·ai编程