IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> ES5的继承方式总结 -> 正文阅读

[JavaScript知识库]ES5的继承方式总结

借鉴于《JavaScript高级程序设计(第4版)》

  1. 原型链继承

  2. 盗用构造函数

  3. 组合继承

  4. 原型式继承

  5. 寄生式继承

  6. 寄生组合式继承

原型链继承

代码如下:

function SuperType(){
 ? ?this.colors = ['red','orange']
}
function SubType(){}
SubType.prototype = new SuperType();

此种继承方式会出现以下两个问题:

  1. 子类会一并继承父类的属性,并且所有的子类实例对象想要访问父类的属性xxx的时候,该属性都指向相同的引用(SubType.prototype.xxx)

    举例:见下面代码

    let instance1 = new SubType();
    let instance2 = new SubType();
    // 创建两个子类实例对象instance1和instance2
    instance1.colors.push('black');
    console.log(instance1.colors); ?// 'red,orange,black'
    console.log(instance2.colors); ?// 'red,orange,black'

    这不是我们想要的效果,此处我们希望的效果应该是console.log(instance2.colors); // 'red,orange',即每个子类实例对象的colors可以继承自SuperType,同时这些colors指向不同的引用,改变instance1的colors的时候,instance2的colors不受干扰。

    但是由于实际上所有的子类SubType的实例对象指向colors的时候,指向的都是SubType.prototype.colors,所以相当于所有的子类实例对象共享该属性

    console.log(instance1.colors === SubType.prototype.colors); //true
    console.log(instance2.colors === SubType.prototype.colors); //true

    原型链继承存在着这样的问题与弊端。

  2. 子类在实例化的时候,无法按照子类的需求向父类的构造函数进行动态传参

    因为父类已经在 SubType.prototype = new SuperType(); 这里进行了实例化了,父类只会实例化一次,不会多次构造。同时由于所有的子类实例化对象都通过SubType.prototype来访问父类属性与方法,传参的时候同样会出现上述1中的问题,其他的子类对象在访问父类属性时会受到影响。

  3. 因此该方式可能只适合继承父类上定义的方法,没有定义可变属性的情况。

盗用构造函数

基本思路:在子类构造函数中调用父类构造函数。代码如下:

function SuperType(){
    this.colors = ['red','blue','green'];
}
?
function SubType(){
 ? ?SuperType.call(this);
 ? ?// 执行效果相当于
 ? ?// this.colors = ['red','blue','green'];
}
?
let instance1 = new SubType();
let instance2 = new SubType();
instance1.colors.push('black');
console.log(instance1.colors); ?// ['red','blue','green','black']
console.log(instance2.colors); ?// ['red','blue','green']

这里的instance1.colors和instance2.colors指向不同的引用,所以不会出现上述原型链继承所出现的问题

对比于原型链继承,盗用构造函数实现继承的好处是:

  • 可以实现子类向父类的构造函数传递参数

  • 见下面代码

    function SuperType(name){
     ? ?this.name = name;
    }
    function SubType(name){
     ? ?SuperType.call(this,name);
    }
    ?
    let instance = new SubType('Alice');
    console.log(instance.name); ?//'Alice'

同时该方法存在着问题:

  1. 由于子类型是调用父类型的构造函数来实现继承,所以父类中所有的属性和方法都必须在自己的构造函数中实现,所以这些定义在构造函数里面的函数没有办法做到重用。

  2. 由于子类调用父类构造函数,实际上只是执行了父类的构造函数里面的代码,将父类的this变成了子类的this来实现继承,只是继承了父类中定义的属性和方法。因此并没有继承父类原型上的方法。

组合继承

结合了原型链和盗用构造函数,基本思路是使用原型链继承原型上的属性和方法,通过盗用构造函数继承实例属性,结合了上述两种方法的优点同时避免了上述方法的缺点。

代码如下:

function SuperType(name){
 ? ?this.name = name;
 ? ?this.colors = ['red','blue'];
}
// 定义父类构造函数
?
SuperType.prototype.sayName = function(){
 ? ?console.log(this.name);
}
// 父类显式原型对象上的sayName函数
?
function SubType(name,age){
 ? ?SuperType.call(this,name); ?// 调用父类的构造函数
 ? ?// 获得 this.name = name; this.colors = ['red','blue']
 ? ?this.age = age;
}
// 定义子类构造函数
?
SubType.prototype = new SuperType(); ?// 实际效果是:
// SubType.prototype.name = undefined;
// SubType.prototype.colors = ['red','blue'];
SubType.prototype.sayAge = function(){
    console.log(this.age);
}
?
let instance1 = new SubType("Alice",19);
let instance2 = new SubType("Greg",20);
instance1.colors.push('yellow');
console.log(instance1.colors); //'red,blue,yellow'
console.log(instance1.colors); // 'red,blue'
// 因为SubType在new的时候调用了SuperType的构造函数,创建了不同的colors
?
instance1.sayName(); ?//'Alice'
// 实际上是instance1.__proto__.__proto__.sayName()
instance1.sayAge(); ?//'19'
// 实际上是instance1.__proto__.sayAge();

对于instance1,当访问name的时候,子类实例对象内部具有内部属性name='Alice',子类实例的隐式原型对象上同样具有name=undefined。

在访问类属性的时候,首先查找类内部是否有该属性,如果有的话直接使用该类内部的属性,找不到的情况下才会沿着原型链去寻找该属性。

因此在访问的时候,子类实例对象内部的name属性会覆盖掉原型上的属性,实现各个子类的实例属性在读or写的时候互不干扰的效果,因为他们访问的是自己类内部的name属性,不会读取到原型上的name属性。

原型式继承

Object.create()函数将原型式继承的概念规范化

接收两个参数,1.该对象指向的原型p 2.给新对象定义额外属性的对象(可选)

意思是创建一个对象,将该对象的原型对象指向参数1。

let person = {
 ? ?name:'Alice',
 ? ?friends:['A','B','C']
}
?
let anotherPerson1 = Object.create(person);
anotherPerson1.name = 'Greg';
anotherPerson1.friends.push('D');
// anotherPerson1.__proto__name === person.name  'true'
?
let anotherPerson2 = Object.create(person);
// anotherPerson2.__proto__.name === person.name  'true'
anotherPerson2.friends.push('E')
?
console.log(person.name); ? ? // Alice
console.log(person.friends); ?// ['A','B','C','D','E'] 

该方法本质上与原型链继承是一样的,但是表达更为简便:当具有某个对象A的时候,不需要单独创建产生该对象的构造函数来实现继承,可以直接将这个对象A传进Object.create()函数里面,然后返回一个新的对象B,该对象B的隐式原型对象是A,实现继承。

寄生式继承

与原型式继承类似,寄生式继承的思路类似于寄生构造函数和工厂模式,基本的寄生继承模式如下:

function createAnother(original){
 ? ?let clone = object(original);
 ? ?// 相当于 function F(){}; F.prototype = original; return new F();
 ? ?// clone 继承了original的方法,同时扩充了自身的方法
 ? ?clone.sayHi = function(){
        console.log('hi');
 ?  }
 ? ?// 缺陷:在这里添加函数会导致函数难以重用,与盗用构造函数类似
 ? ?return clone;
}

寄生式组合继承

组合继承的效率问题:父类构造函数会被调用两次(一次是创建子类原型,一次是在子类构造函数里面),这就导致:

通过上面组合继承,可以得知一个SubType的实例对象上有name属性,同时其原型对象上也有name属性,在具体访问属性的时候,实例对象中的属性将原型对象上的掩盖掉了。

寄生式组合继承解决这个问题。它通过盗用构造函数来继承属性,通过混合式原型链继承方法(就是构造函数继承属性,原型链继承方法)(通过盗用构造函数来继承父类内部的内容,通过原型链继承来继承父类原型上的内容)。

不通过调用父类的构造函数来给子类原型赋值,而是取得父类原型的一个副本。

寄生式组合继承的基本模式如下

function inheritPrototype(subType,superType){
 ? ?let prototype = object(superType.prototype); ?// 创建父类原型的一个副本
 ? ?prototype.constructor = subType; ? ? ? ? ? ? ?// 设置constructor属性
 ? ?subType.prototype = prototype; ? ? ? ?// 设置子类原型:父类对象实例
 ? ?// 此处继承到的是父类原型里面的方法,父类中的属性没有继承
 ? ?// 子类如何继承父类中的属性:在子类的构造函数中调用父类的构造函数来获取
}

使用:

function SuperType(name){
    this.name = name;
    this.colors = ['red','blue','green'];
}
?
SuperType.prototype.sayName = function(){
    console.log(this.name);
}
?
function SubType(name,age){
 ? ?SuperType.call(this,name); ? // 在这里调用了一次构造函数,继承父类的属性
 ? ?this.age = age;
}
?
inheritPrototype(subType,superType); ?//实现子类原型的设置,继承父类的方法

该方案在调用子类SubType的构造函数的时候,调用了一次父类SuperType构造函数,避免了在子类原型上会出现name 和 colors属性的情况(即便他们出现,也不会被访问到,因此在原型上这两个属性没有用处,不需要继承下来),效率相比于组合继承更高。

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-11-12 19:30:07  更:2021-11-12 19:31:30 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/10 12:39:11-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码