资讯专栏INFORMATION COLUMN

JavaScript中的继承

guyan0319 / 693人阅读

摘要:前言作为中最重要的内容之一,继承问题一直是我们关注的重点。如果一个类别继承自另一个类别,就把这个称为的子类,而把称为的父类别也可以称是的超类。

前言

作为 JavaScript 中最重要的内容之一,继承问题一直是我们关注的重点。那么你是否清晰地知道它的原理以及各种实现方式呢

阅读这篇文章,你将知道:

什么是继承

实现继承有哪几种方式

它们各有什么特点

这里默认你已经清楚的知道构造函数、实例和原型对象之间的关系,如果并不是那么清晰,那么推荐你先阅读这篇文章 -- JavaScript 中的原型与原型链

如果文章中有出现纰漏、错误之处,还请看到的小伙伴多多指教,先行谢过

以下↓

概念
继承(inheritance)是面向对象软件技术当中的一个概念。如果一个类别 B 继承自 另一个类别 A ,就把这个 B 称为 A的子类 ,而把 A 称为 B的父类别 也可以称 A是B的超类 。继承可以使得子类具有父类别的各种属性和方法,而不需要再次编写相同的代码 ...更多)

通过这些概念和图示我们不难知道继承可以在我们的开发中带来的便捷,那么在 JavaScript 中如何去实现继承呢?

继承实现方式 原型链继承
利用原型让一个引用类型继承另一个引用类型的属性和方法
function SuperType() {
    this.name = "tt";
}
SuperType.prototype.sayName = function() {
    return this.name
}

function SubType() {
    this.name = "oo";
}
SubType.prototype = new SuperType()

var instance = new SubType()

instance.sayName() // oo
instance instanceof SubType // true
instance instanceof SuperType // ture

以上的试验中,我们创建了两个构造函数 SuperTypeSubType ,并且让 SubType 的原型指向 SuperTypeSubType 也就继承了 SuperType 原型对象中的方法。所以在创建 instance 实例的时候,实例本身也就具有了 SuperType 中的方法,并且都处在它们的原型链中

SubType.prototype.constructor == SubType // false
SubType.prototype.constructor == SuperType // true

需要注意的是:这个时候 SubType.prototype.constructor 是指向 SuperType 的,相当于重写了 SubType 的原型对象。

用一张图表示:

SubType.prototype 相当于 SuperType 的实例存在的,所以 SubType.prototype.constructor 就指向 SuperType

原型继承的特点

优点:

简单、易于实现

父类新增原型方法/原型属性,子类都能访问到

非常纯粹的继承关系,实例是子类的实例,也是父类的实例

缺点:

无法实现多继承

想要为子类 SubType 添加原型方法,就必须在 new SuperType 之后添加(会覆盖)

来自原型对象的所有属性被所有实例共享(引用类型的值修改会反映在所有实例上面)

创建子类实例时,无法向父类构造函数传参

借用构造函数
在子类构造函数的内部调用超类型构造函数,通过 applycall 实现
function SuperType(name) {
    this.name = name;
    this.colors = ["red", "orange", "black"];
}

function SubType() {
    SuperType.call(this, "tt");
}

var instance = new SubType()
var instance1 = new SubType()

instance.colors // ["red", "orange", "black"]
instance.name // tt

instance.colors.push("green");
instance.colors // ["red", "orange", "black", "green"]
instance1.colors // ["red", "orange", "black"]
借用构造函数的特点

优点:

解决了原型链继承不能传参的问题

子类实例共享父类引用属性的问题

可以实现多继承(call可以指定不同的超类)

缺点:

实例并不是父类的实例,只是子类的实例

只能继承父类的实例属性和方法,不能继承原型属性/方法

无法实现函数复用

组合继承
伪经典继承(最常用的继承模式):将原型链和借用构造函数的技术组合到一起。使用原型链实现对原型属性和方法的继承,通过构造函数来实现对实例属性的继承
function SuperType(name) {
    this.name = name;
    this.colors = ["red", "orange", "black"];
}
SuperType.prototype.sayName = function() {
    return this.name
}

function SubType() {
    SuperType.call(this, "tt");
    this.name = "oo";
}
// 这里的 SubType.prototype.constructor 还是指向 SuperType
SubType.prototype = new SuperType();

var instance = new SubType();
var instance1 = new SubType();

instance.name // oo
instance.sayName() // oo

instance.colors.push("green");
instance.colors // ["red", "orange", "black", "green"]
instance1.colors // ["red", "orange", "black"]
组合继承的特点

优点:

可以继承实例属性/方法,也可以继承原型属性/方法

不存在引用属性共享问题

可传参

函数可复用

缺点:

调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

原型式继承
借助原型链可以基于已有的对象创建新对象,同时还不必因此创建自定义类型
function obj(o) {
    function F(){}
    F.prototype = o;
    return new F();
}

var person = {
    name: "tt",
    age: 18,
    colors: ["red", "green"]
}

var instance = obj(person);
var instance1 = obj(person);
instance.colors.push("black");
instance.name // tt
instance.colors // ["red", "green", "black"]
instance1.colors // ["red", "green", "black"]

创建一个临时的构造函数,然后将传入的对象当做这个构造函数的原型对象,最后返回这个临时构造函数的新实例。实际上,就是对传入的对象进行了一次浅复制

ES5 通过新增 Object.create() 规范化了原型式继承

更多 Object.create()语法请点击 这里

原型式继承特点

优点:

支持多继承(传入的对象不同)

不需要兴师动众的创建很多构造函数

缺点: 和原型链继承基本一致,效率较低,内存占用高(因为要拷贝父类的属性)

寄生式继承
创建一个仅用于封装继承过程的函数,在函数内部对这个对象进行改变,最后返回这个对象
function createAnother(obj) {
    var clone = Object(obj);
    clone.sayHi = function() {
        alert("Hi");
    }
    return clone
}

var person = {
    name: "tt",
    age: 18,
    friends: ["oo", "aa", "cc"],
    sayName() {
        return this.name
    }
}

var instance = createAnother(person)
var instance1 = createAnother(person)

instance.friends.push("yy")

instance.name // "tt"
instance.sayHi() // Hi
instance.friends // ["oo", "aa", "cc", "yy"]
instance1.friends // ["oo", "aa", "cc", "yy"]
寄生式继承的特点

优点:

支持多继承

缺点:

实例并不是父类的实例,只是子类的实例

不能实现复用(与构造函数相似)

实例之间会互相影响

寄生组合继承
借用构造函数来继承属性,通过原型链的混成形式来继承方法。通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点
function inherit(subType, superType) {
    var obj = Object(superType.prototype); // 创建对象
    obj.constructor = subType;  // 指定constructor
    subType.prototype = obj;    // 指定对象
}

function SuperType(name) {
    this.name = name;
    this.colors = ["red", "orange", "black"];
}
SuperType.prototype.sayName = function() {
    return this.name
}

function SubType() {
    SuperType.call(this, "tt");
    this.name = "oo";
}

inherit(SubType, SuperType)

var instance = new SubType()

instance.name // oo
instance.sayName // oo
instance instanceof SubType // true
instance instanceof SuperType // true
SubType.prototype.constructor == SubType // true
寄生组合继承的特点

堪称完美,只是实现稍微复杂一点

后记

作为 JavaScript 最重要的概念之一,对于继承实现的方式方法以及它们之间的差异我们还是很有必要了解的。

在实现继承的时候,拷贝 也是一种很有效的方式,由于 JavaScript 简单数据类型与引用类型的存在,衍生出了 浅拷贝深拷贝 的概念,那么它们又是什么,怎么去实现呢

且听下回分解,哈哈

周末愉快

最后,推荐一波前端学习历程,不定期分享一些前端问题和有意思的东西欢迎 star 关注 传送门

参考文档

JavaScript 高级程序设计

JavaScript实现继承的几种方式

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/103697.html

相关文章

  • 彻底搞懂JavaScript中的继承

    摘要:这正是我们想要的太棒了毫不意外的,这种继承的方式被称为构造函数继承,在中是一种关键的实现的继承方法,相信你已经很好的掌握了。 你应该知道,JavaScript是一门基于原型链的语言,而我们今天的主题 -- 继承就和原型链这一概念息息相关。甚至可以说,所谓的原型链就是一条继承链。有些困惑了吗?接着看下去吧。 一、构造函数,原型属性与实例对象 要搞清楚如何在JavaScript中实现继承,...

    _ivan 评论0 收藏0
  • Javascript 设计模式读书笔记(三)——继承

    摘要:的继承方式属于原型式继承,非常灵活。当使用关键字执行类的构造函数时,系统首先创建一个新对象,这个对象会继承自构造函数的原型对象新对象的原型就是构造函数的属性。也就是说,构造函数用来对生成的新对象进行一些处理,使这个新对象具有某些特定的属性。 继承这个东西在Javascript中尤其复杂,我掌握得也不好,找工作面试的时候在这个问题上栽过跟头。Javascript的继承方式属于原型式继承,...

    cangck_X 评论0 收藏0
  • javascript继承你了解多少?

    摘要:和构造函数前面提到,是个内置隐藏属性,虽然在可以通过访问,但是其设计本意是不可被读取和修改的,那么我们如何利用原型链来建立继承关系提供了关键字。到这儿,思路就清晰了,怎么让对象和对象的相连实现继承只需把的构造函数的连接到就行了。 什么是继承? 大多数人使用继承不外乎是为了获得这两点好处,代码的抽象和代码的复用。代码的抽象就不用说了,交通工具和汽车这类的例子数不胜数,在传统的OO语言中(...

    baishancloud 评论0 收藏0
  • 白话解释 Javascript 原型继承(prototype inheritance)

    摘要:我们有了构造函数之后,第二步开始使用它构造一个函数。来个例子这种方式很简单也很直接,你在构造函数的原型上定义方法,那么用该构造函数实例化出来的对象都可以通过原型继承链访问到定义在构造函数原型上的方法。 来源: 个人博客 白话解释 Javascript 原型继承(prototype inheritance) 什么是继承? 学过面向对象的同学们是否还记得,老师整天挂在嘴边的面向对象三大特...

    kid143 评论0 收藏0
  • [译] 为什么原型继承很重要

    摘要:使用构造函数的原型继承相比使用原型的原型继承更加复杂,我们先看看使用原型的原型继承上面的代码很容易理解。相反的,使用构造函数的原型继承像下面这样当然,构造函数的方式更简单。 五天之前我写了一个关于ES6标准中Class的文章。在里面我介绍了如何用现有的Javascript来模拟类并且介绍了ES6中类的用法,其实它只是一个语法糖。感谢Om Shakar以及Javascript Room中...

    xiao7cn 评论0 收藏0

发表评论

0条评论

guyan0319

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<