前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >类的继承

类的继承

作者头像
我是leon
发布2019-08-28 12:04:39
8920
发布2019-08-28 12:04:39
举报
文章被收录于专栏:leon的专栏leon的专栏leon的专栏

本文讲述JavaScript中类继承的实现方式,并比较实现方式的差异。

一、何为继承

继承,是子类继承父类的特征和行为,使得子类对象具有父类的实例域和方法。 继承是面向对象编程中,不可或缺的一部分。

1.1 优点

  • 减少代码冗余 父类可以为子类提供通用的属性,而不必因为增加功能,而逐个修改子类的属性
  • 代码复用 同上
  • 代码易于管理和扩展 子类在父类基础上,可以实现自己的独特功能

1.2 缺点

  • 耦合度高 如果修改父类代码,将影响所有继承于它的子类
  • 影响性能 子类继承于父类的数据成员,有些是没有使用价值的。但是,在实例化的时候,已经分配了内存。所以,在一定程度上影响程序性能。

二、例子

例子以图书馆中的书入库归类为例。 以下是简化后的父类Book(也可称为基类)。 目的是通过继承该父类,产出Computer(计算机)子类。 并且,子类拥有新方法say,输出自己的书名。

function Book(){
    this.name = ''; // 书名
    this.page = 0; // 页数
    this.classify = ''; // 类型
}
Book.prototype = {
    constructor: Book,
    init: function(option){
        this.name = option.name || '';
        this.page = option.page || 0;
        this.classify = option.classify || '';
    },
    getName: function(){
        console.log(this.name);
    },
    getPage: function(){
        console.log(this.page);
    },
    getClassify: function(){
        console.log(this.classify);
    }
};

接下来会讲解子类Computer几种继承方式的实现和优化方法。开始飙车~

三、实例式继承

function Computer(){
    Book.apply(this, arguments);
}
Computer.prototype = new Book();
Computer.prototype.constructor = Computer;
Computer.prototype.init = function(option){
    option.classify = 'computer';
    Book.prototype.init.call(this, option);
};
Computer.prototype.say = function(){
    console.log('I\'m '+ this.name);
}

3.1 调用父类构造器进行初始化

function Computer(){
    Book.apply(this, arguments);
}

Computer的构造函数里,调用父类的构造函数进行初始化操作。使子类拥有父类一样的初始化属性。

3.2 将父类的原型传递给子类

Computer.prototype = new Book();使用new操作符对父类Book进行实例化,并将实例对象赋值给子类的prototype。 这样,子类Computer就可以通过原型链访问到父类的属性。

3.3 缺点

  • 父类Book的构造函数被执行了2次
    • 一次是在Computer的构造函数里Book.apply(this, arguments);
    • 一次是在Computer.prototype = new Book(); 这种模式,存在一定的性能浪费。
  • 父类实例化无法传参 Computer.prototype = new Book();,这种实例化方式,无法让Book父类接收不固定的参数集合。

四、原型式继承

function Computer(){
    Book.apply(this, arguments);
}
Computer.prototype = Object.create(Book.prototype);
Computer.prototype.constructor = Computer;
Computer.prototype.init = function(option){
    option.classify = 'computer';
    Book.prototype.init(option);
};
Computer.prototype.say = function(){
    console.log('I\'m '+ this.name);
}

这里的改进:是使用Object.create(Book.prototype)。它的作用是返回一个继承自原型对象Book.prototype的新对象。且该对象下的属性已经初始化。 用Object.create生成新对象,并不会调用到Book的构造函数。 这种方式,也可以通过原型链实现继承。

五、Object.create的简单版兼容

由于低版本的浏览器是不支持Object.create的。所以这里简单介绍下兼容版本:

Object.create = function(prototype){
    function F(){}
    F.prototype = prototype;
    return new F();
}

原理是定义一个空的构造函数,然后修改其原型,使之成为一个跳板,可以将原型链传递到真正的prototype。

六、函数化继承

上述两种实现方式,都存在一个问题:不存在私有属性私有方法。也就是说,存在被篡改的风险。 接下来就用函数化来化解这个问题。

function book(spec, my){
    var that = {};

    // 私有变量
    spec.name = spec.name || ''; // 书名
    spec.page = spec.page || 0; // 页数
    spec.classify = spec.classify || ''; // 类型

    var getName = function(){
        console.log(spec.name);
    };
    var getPage = function(){
        console.log(spec.page);
    };
    var getClassify = function(){
        console.log(spec.classify);
    };

    that.getName = getName;
    that.getPage = getPage;
    that.getClassify = getClassify;

    return that;
}

function computer(spec, my){
    spec = spec || {};
    spec.classify = 'computer';
    var that = book(spec, my);

    var say = function(){
        console.log('I\'m '+ spec.name);
    };
    that.say = say;

    return that;
}

var Ninja = computer({name: 'JavaScript忍者秘籍', page: 350});

函数化的优势,就是可以更好地进行封装和信息隐藏。 也许有人疑惑为什么用以下这种方式声明和暴露方法:

var say = function(){
    console.log('I\'m '+ spec.name);
};
that.say = say;

其实是为了保护对象自身的完整性。即使that.say被外部篡改或破坏掉,function computer内部的say方法仍然能够正常工作。 另外,解释下thatspecmy的作用:

  • that是一个公开数据存储容器,暴露出去的数据接口,都放到这个容器
  • spec是用来存储创建新实例所需的信息,属于实例之间共同编辑的数据
  • my是用来存储父类、子类之间共享的私密数据容器,外部是访问不到的。

七、ES6继承

最后,看下现代版ES6的类继承。不禁感慨以前的刀耕火种,是多么折磨人?

class Book {
    constructor(){
        this.name = ''; // 书名
        this.page = 0; // 页数
        this.classify = ''; // 类型
    }
    init(option) {
        this.name = option.name || '';
        this.page = option.page || 0;
        this.classify = option.classify || '';
    }
    getName() {
        console.log(this.name);
    }
    getPage (){
        console.log(this.page);
    }
    getClassify (){
        console.log(this.classify);
    }
}
class Computer extends Book{
    constructor(...args){
        super(...args);
    }
    init(option) {
        super.init(option);
        this.classify = 'computer';
    }
    say() {
        console.log('I\'m '+ this.name);
    }
}

结语

虽然ES5终究会被淘汰,但是了解下其工作原理,还是很有必要。因为很多源码还是有用到里面的模式。 附带的价值就是,ES5的继承玩到飞起,ES6的继承就是小菜一碟。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-07-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、何为继承
    • 1.1 优点
      • 1.2 缺点
      • 二、例子
      • 三、实例式继承
        • 3.1 调用父类构造器进行初始化
          • 3.2 将父类的原型传递给子类
            • 3.3 缺点
            • 四、原型式继承
            • 五、Object.create的简单版兼容
            • 六、函数化继承
            • 七、ES6继承
            • 结语
            相关产品与服务
            容器服务
            腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档