阿里巴巴技术文章分享 Javascript继承机制的实现


Posted in Javascript onJanuary 14, 2016

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继承机制的实现

原型链的这个特性,和继承很相似,所以自然而然,我们可以利用它来实现继承机制。而原型链对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)。这样我们得到三级类结构:

阿里巴巴技术文章分享 Javascript继承机制的实现

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

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

这篇文章不错吧,那就给个赞吧!

Javascript 相关文章推荐
nw.js实现类似微信的聊天软件
Mar 16 Javascript
jQuery实现淡入淡出二级下拉导航菜单的方法
Aug 28 Javascript
javascript返回顶部的按钮实现方法
Jan 09 Javascript
MvcPager分页控件 适用于Bootstrap
Jun 03 Javascript
浅谈通过JS拦截 pushState和replaceState事件
Jul 21 Javascript
利用Javascript实现一套自定义事件机制
Dec 14 Javascript
React 组件转 Vue 组件的命令写法
Feb 28 Javascript
layui实现table加载的示例代码
Aug 14 Javascript
Vue中使用方法、计算属性或观察者的方法实例详解
Oct 31 Javascript
Layui之table中的radio在切换分页时无法记住选中状态的解决方法
Sep 02 Javascript
JS使用正则表达式实现常用的表单验证功能分析
Apr 30 Javascript
解决iview table组件里的 固定列 表格不自适应的问题
Nov 13 Javascript
AngularJS初始化静态模板详解
Jan 14 #Javascript
基于jQuery实现文本框只能输入数字(小数、整数)
Jan 14 #Javascript
jquery拖拽效果完整实例(附demo源码下载)
Jan 14 #Javascript
基于javascript实现随机颜色变化效果
Jan 14 #Javascript
JavaScript事件类型中UI事件详解
Jan 14 #Javascript
JavaScript事件 "事件对象"的注意要点
Jan 14 #Javascript
详解JavaScript中localStorage使用要点
Jan 13 #Javascript
You might like
无数据库的详细域名查询程序PHP版(1)
2006/10/09 PHP
Drupal 添加模块出现莫名其妙的错误的解决方法(往往出现在模块较多时)
2011/04/18 PHP
PHP利用str_replace防注入的方法
2013/11/10 PHP
php使用strtotime和date函数判断日期是否有效代码分享
2013/12/25 PHP
一个图片地址分解程序(用于PHP小偷程序)
2014/08/23 PHP
php的4种常见运行方式
2015/03/20 PHP
深入理解PHP中的count函数
2016/05/31 PHP
JavaScript的Cookies
2008/01/16 Javascript
Jquery Ajax请求代码(2)
2011/01/07 Javascript
Prototype源码浅析 String部分(一)之有关indexOf优化
2012/01/15 Javascript
基于jQuery.Validate验证库知识点的详解
2013/04/26 Javascript
JS取文本框中最小值的简单实例
2013/11/29 Javascript
使用js判断数组中是否包含某一元素(类似于php中的in_array())
2013/12/12 Javascript
用js来刷新当前页面保留参数的具体实现
2013/12/23 Javascript
js特殊字符过滤的示例代码
2014/03/05 Javascript
基于jQuery实现最基本的淡入淡出效果实例
2015/02/02 Javascript
jQuery实现标题有打字效果的焦点图代码
2015/11/16 Javascript
JS组件Bootstrap Select2使用方法解析
2016/05/30 Javascript
只需五句话搞定JavaScript作用域(经典)
2016/07/26 Javascript
AngularJS基于factory创建自定义服务的方法详解
2017/05/25 Javascript
js中let和var定义变量的区别
2018/02/08 Javascript
webpack实践之DLLPlugin 和 DLLReferencePlugin的使用教程
2019/06/10 Javascript
LayUi使用switch开关,动态的去控制它是否被启用的方法
2019/09/21 Javascript
JS Ajax请求会话过期处理问题解决方法分析
2019/11/16 Javascript
如何使用RoughViz可视化Vue.js中的草绘图表
2021/01/30 Vue.js
使用python检测手机QQ在线状态的脚本代码
2013/02/10 Python
python实现翻转棋游戏(othello)
2019/07/29 Python
给大家整理了19个pythonic的编程习惯(小结)
2019/09/25 Python
python字典和json.dumps()的遇到的坑分析
2020/03/11 Python
keras 权重保存和权重载入方式
2020/05/21 Python
python爬虫今日热榜数据到txt文件的源码
2021/02/23 Python
Html5 canvas画图白板踩坑
2020/06/01 HTML / CSS
沙特阿拉伯网上购物:Sayidaty Mall
2018/05/06 全球购物
美国领先的低折扣旅行网站:Hotwire
2019/01/19 全球购物
美国椅子和沙发制造商:La-Z-Boy
2020/10/25 全球购物
Nginx配置根据url参数重定向
2022/04/11 Servers