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关键字来实现类继承。

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

相关推荐
Senar6 分钟前
Web端选择本地文件的几种方式
前端·javascript·html
烛阴24 分钟前
UV Coordinates & Uniforms -- OpenGL UV坐标和Uniform变量
前端·webgl
姑苏洛言28 分钟前
扫码小程序实现仓库进销存管理中遇到的问题 setStorageSync 存储大小限制错误解决方案
前端·后端
烛阴39 分钟前
JavaScript 的 8 大“阴间陷阱”,你绝对踩过!99% 程序员崩溃瞬间
前端·javascript·面试
lh_12541 小时前
ECharts 地图开发入门
前端·javascript·echarts
jjw_zyfx1 小时前
成熟的前端vue vite websocket,Django后端实现方案包含主动断开websocket连接的实现
前端·vue.js·websocket
Mikey_n2 小时前
前台调用接口的方式及速率对比
前端
周之鸥2 小时前
使用 Electron 打包可执行文件和资源:完整实战教程
前端·javascript·electron
我爱吃朱肉2 小时前
HTMLCSS模板实现水滴动画效果
前端·css·css3
机器视觉知识推荐、就业指导2 小时前
开源QML控件:进度条滑动控件(含源码下载链接)
前端·qt·开源·qml