当前位置: 首页 > news >正文

Javascript继承机制的实现

Javascript作为一门脚本语言,在设计之初并没有考虑到面向对象的特性。即便到了当今这个遍布现代浏览器的年代,各种Javascript 框架/库如雨后春笋般地疯狂生长,Javascript中连个 class 关键字都没有。如果你要编写一个类,你还得借助于function,至于继承、重载什么的,就别奢望了。

可是,没有继承,日子怎么过啊?难道把所有的共有逻辑都拷贝一遍,实现最低级的代码复用?

答案当然是——NO,所以,我们要自己实现继承!

目标

最关键的目标当然是继承——子类自动拥有父类的所有公共属性和方法。

支持instanceof,例如c是子类的实例,而P是父类,c instanceof P应该返回true。

其次应该能够重写(Override)父类的方法,并且在子类的方法中,能够方便地调用到父类的同名方法。

至于说重载(Overload),由于Javascript的语言特性(不可以有同名方法,即便它们参数列表不一样),无法实现。

设计与实现

Javascript的对象有一个很重要的属性——__proto__,也就是原型。原型实质上也是一个对象,所有它也可以有自己的原型,这样就形成一个原型链。当你调用某个对象的某个方法,或者读取该对象的某个属性,Javascript执行器是这样做的:

  1. 首先到该对象中找对应的方法或属性,如果找不到,
  2. 到该对象的原型中找,如果还找不到,
  3. 到原型的原型里面找
  4. ...
  5. 直到最后找到Object的原型为止,如果还没有则返回undefined

如下图所示:
Javascript_Prototypal_Inheritance_Diagram_grand_picture_with_some_Dog_objects_1_

原型链的这个特性,和继承很相似,所以自然而然,我们可以利用它来实现继承机制。而原型链对instanceof的支持,使得它成为很好的选择。

我们定义extend函数,这个函数接受两个参数,第一个是父类,第二个是子类,如下所示:

function extend(ParentClass, ChildClass) {
    ...
    return ChildClass;
}

这个函数对子类进行处理,并返回子类。处理的逻辑如下:

建立原型链

通过将子类的原型链与父类的原型链连接起来,子类可以自动拥有父类的方法和属性:

var pp = ParentClass.prototype,
    cp = ChildClass.prototype;
function T() {};
T.prototype = pp;
ChildClass.prototype = new T();

为了连接原型链,需要创建一个父类的实例,并将其赋给子类的原型属性。但我们不希望在extend方法里面就实例化父类,所以引入了一个中间类T,以解决这个问题。

实现重写

原型链建立之后,原来子类原型上的方法和属性我们也需要保留下来:

方法

如果父类有同名方法,我们使用一个闭包,来保留对父类方法和子类方法的引用。然后,修改新的原型中该方法的引用,将其指向一个新的 function。在这个function里面,我们创建一个临时属性super,将其指向父类方法,并调用子类方法,这样在子类方法中,通过 this.super可以调用该父类方法:

ChildClass.prototype[name] = (function(pm, cm) {
    return function() {
        var _super = this.super;
        this.super = pm;
        var result = cm.apply(this, arguments);
        this.super = _super;
        return result;
    };
})(pp[name], cp[name]);

属性

对于属性,不存在重写的问题,所以直接将子类原来的原型中的属性加到新的原型中即可:

ChildClass.prototype[name] = cp[name];

构造器

为了让子类能够访问到父类的构造器,我们将父类赋给子类的super属性:

ChildClass.super = ParentClass;

如何使用

假设我们要设计一个管理系统,里面涉及到客户、工人和经理等。将客户和员工的共性抽象出来,我们得到人(People);然后将工人和经理的共性抽象得到员工(Employee)。这样我们得到三级类结构:
oo

实现这个设计的代码如下:

function People(firstname, lastname) {
    this.firstname = firstname;
    this.lastname = lastname;
}

function Employee(firstname, lastname, company) {
    Employee.super.apply(this, arguments);
    this.company = company;
}

function Manager(firstname, lastname, company, title) {
    Manager.super.apply(this, arguments);
    this.title = title;
}

我们希望对每个人都有一个描述,People是姓+名;员工在姓+名之后,还包括公司名称;而经理在员工的描述之后,还包括职位。代码如下:

People.prototype.summary = function() {
    return this.firstname + " " + this.lastname;
};

Employee.prototype.summary = function() {
    return this.super.call(this) + ", " + this.company;
};

Manager.prototype.summary = function() {
    return this.super.call(this) + ", " + this.title;
};

在所有的成员方法都已经定义好之后,声明类的继承(必须先定义方法,再声明类的继承,否则无法在方法中使用this.super调用父类方法!):

extend(People, Employee);
extend(Employee, Manager);

使用这些类就比较简单,直接new就好了:

var people = new People("Alice", "Dickens");
var employee = new Employee("Bob", "Ray", "Alibaba");
var manager = new Manager("Calvin", "Klein", "Alibaba", "Senior Manager");
console.log( people.summary() ); //Alice Dickens
console.log( employee.summary() ); //Bob Ray, Alibaba
console.log( manager.summary() ); //Calvin Klein, Alibaba, Senior Manager

相关文章:

  • AndroidTestCase简单使用
  • Linux开源文本编辑器培训教材(二)
  • 微信公众平台开发(111) 现金红包、裂变红包、企业付款
  • ListView和SimPleteAdapter 把新闻数据绑定到ListView
  • I.MX6 Linux udev porting
  • Nginx搭建反向代理服务器过程详解(转)
  • 【PHP】PHP7的异常处理详解
  • IOS开发知识(六)
  • linux基础网络设置
  • 深入浅出Node.js (6) - 理解Buffer
  • Javascript 正确用法 二
  • 广州Uber优步司机奖励政策(1月25日~1月31日)
  • 6.Java基础知识--IO
  • fiddler+jmeter联合使用
  • 解决包含已存在的php文件,但提示就是找不到的问题
  • 【从零开始安装kubernetes-1.7.3】2.flannel、docker以及Harbor的配置以及作用
  • emacs初体验
  • golang 发送GET和POST示例
  • maya建模与骨骼动画快速实现人工鱼
  • mongo索引构建
  • Octave 入门
  • Python实现BT种子转化为磁力链接【实战】
  • Vue2.0 实现互斥
  • 机器学习中为什么要做归一化normalization
  • 来,膜拜下android roadmap,强大的执行力
  • 猫头鹰的深夜翻译:JDK9 NotNullOrElse方法
  • 学习Vue.js的五个小例子
  • 在weex里面使用chart图表
  • Nginx惊现漏洞 百万网站面临“拖库”风险
  • 如何在招聘中考核.NET架构师
  • ​马来语翻译中文去哪比较好?
  • ​软考-高级-信息系统项目管理师教程 第四版【第14章-项目沟通管理-思维导图】​
  • #{}和${}的区别?
  • #vue3 实现前端下载excel文件模板功能
  • (a /b)*c的值
  • (ZT) 理解系统底层的概念是多么重要(by趋势科技邹飞)
  • (附源码)springboot 房产中介系统 毕业设计 312341
  • (离散数学)逻辑连接词
  • (四)TensorRT | 基于 GPU 端的 Python 推理
  • (转)es进行聚合操作时提示Fielddata is disabled on text fields by default
  • .equal()和==的区别 怎样判断字符串为空问题: Illegal invoke-super to void nio.file.AccessDeniedException
  • .NET Core/Framework 创建委托以大幅度提高反射调用的性能
  • .NET6 开发一个检查某些状态持续多长时间的类
  • [ vulhub漏洞复现篇 ] GhostScript 沙箱绕过(任意命令执行)漏洞CVE-2019-6116
  • [ 数据结构 - C++]红黑树RBTree
  • []Telit UC864E 拨号上网
  • [20170728]oracle保留字.txt
  • [2544]最短路 (两种算法)(HDU)
  • [ACL2022] Text Smoothing: 一种在文本分类任务上的数据增强方法
  • [AIGC] 开源流程引擎哪个好,如何选型?
  • [AX]AX2012开发新特性-禁止表或者表字段
  • [C++参考]拷贝构造函数的参数必须是引用类型
  • [EWS]查找 文件夹
  • [flask] flask的基本介绍、flask快速搭建项目并运行
  • [hive] 窗口函数 ROW_NUMBER()