由浅入深讲解Javascript继承机制与simple-inheritance源码分析


Posted in Javascript onDecember 13, 2015

老生常谈的问题,大部分人也不一定可以系统的理解。Javascript语言对继承实现的并不好,需要工程师自己去实现一套完整的继承机制。下面我们由浅入深的系统掌握使用javascript继承的技巧。

1. 直接使用原型链

这是最简粗暴的一种方式,基本没法用于具体的项目中。一个简单的demo如下:

function SuperType(){
  this.property = true;
}
SuperType.prototype.getSuperValue = function(){
  return this.property;
}
function SubType(){
  this.subproperty = false;
}
//继承
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){
  return this.subproperty;
}
var instance = new SubType();

这种方式的问题是原型中的属性会被所用实例共享,通过一个实例改变一个继承过来的属性时,会影响到其他实例。,这显然不是一种常规意义上的继承。

2.使用构造函数

构造函数本质上也只是一个函数而已,可以在任何作用域中调用,在子构造函数中调用父构造函数,就可以实现简单的继承。

function SuperType(){
  this.colors = {"red","blue","green"}
}
function SubType(){
  SuperType.call(this);  
}
var instance = new SubType();

这种实现避免了多个实例共享属性的问题,但是又出现了新的问题,比如没法共享函数,而且 instance instanceof SuperType 为false。

3. 组合使用原型和构造函数

function SuperType(name){
  this.name = name;
  this.colors = {"red","blue","green"}
}
SuperType.prototype.sayName = function(){
  //code
}
function SubType(name,age){
  SuperType.call(this,name); 
  this.age = age;
}
SubType.prototype = new SuperType();
var instance = new SubType();

组合使用原型和构造函数是javascript中最常用的继承模式。使用这种方式,每个实例都有自己的属性,同时可以共享原型中的方法。但是这种方式的缺点是:无论什么情况,都会调用两次超类构造函数。一次是在创建子类原型时,另一次是在子类构造函数内部。这种问题该怎么解决呢?

4. 寄生组合式继承

SubType的原型并不一定非要是SuperType的实例,只需是一个构造函数的原型是SuperType的原型的普通对象就可以了。Douglas Crockford的方法如下:

function obejct(o){
  function F(){};
  F.prototype = o;
  return new F();
}

其实这也就是ES5中Object.create的实现。那么我们可以修改本文中的第3种方案:

function inheritPrototype(subType,superType){
  var prototype = object(superType.prototype);
  prototype.constructor = subType;
  subType.prototype = prototype;
}
function SuperType(name){
  this.name = name;
  this.colors = {"red","blue","green"}
}
SuperType.prototype.sayName = function(){
  //code
}
function SubType(name,age){
  SuperType.call(this,name); 
  this.age = age;
}
inheritPrototype(SubType,SuperType);
var instance = new SubTYpe();

其实寄生组合式继承已经是一种非常好的继承实现机制了,足以应付日常使用。如果我们提出更高的要求:比如如何在子类中调用父类的方法呢?

5.simple-inheritance库的实现

看这么难懂的代码,起初我是拒绝的,但是深入之后才发现大牛就是大牛,精妙思想无处不在。我对每一行代码都有详细的注释。如果你想了解细节,请务必详细研究,读懂每一行。我觉得这个实现最精妙的地方就是按需重写父类方法,在实例对象中可以通过_super调用父类的同名方法,类似于java的实现。

(function(){
  //initializing用于控制类的初始化,非常巧妙,请留意下文中使用技巧
  //fnTest返回一个正则比表达式,用于检测函数中是否含有_super,这样就可以按需重写,提高效率。当然浏览器如果不支持的话就返回一个通用正则表达式
  var initializing = false,fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
  //所有类的基类Class,这里的this一般是window对象
  this.Class = function(){};
  //对基类添加extend方法,用于从基类继承
  Class.extend = function(prop){
    //保存当前类的原型
    var _super = this.prototype;
    //创建当前类的对象,用于赋值给子类的prototype,这里非常巧妙的使用父类实例作为子类的原型,而且避免了父类的初始化(通过闭包作用域的initializing控制)
    initializing = true;
    var prototype = new this();   
    initializing = false;
    //将参数prop中赋值到prototype中,这里的prop中一般是包括init函数和其他函数的对象
    for(var name in prop){
      //对应重名函数,需要特殊处理,处理后可以在子函数中使用this._super()调用父类同名构造函数, 这里的fnTest很巧妙:只有子类中含有_super字样时才处理从写以提高效率
      prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" && fnTest.test(prop[name])?
       (function(name,fn){
        return function(){
          //_super在这里是我们的关键字,需要暂时存储一下
          var tmp = this._super; 
          //这里就可以通过this._super调用父类的构造函数了       
          this._super = _super[name];
          //调用子类函数 
          fn.apply(this,arguments);
          //复原_super,如果tmp为空就不需要复原了
          tmp && (this._super = tmp);
        }
       })(name,prop[name]) : prop[name];
    }
    //当new一个对象时,实际上是调用该类原型上的init方法,注意通过new调用时传递的参数必须和init函数的参数一一对应
    function Class(){
      if(!initializing && this.init){
        this.init.apply(this,arguments);  
      }
    }    
    //给子类设置原型
    Class.prototype = prototype;
    //给子类设置构造函数
    Class.prototype.constructor = Class;
    //设置子类的extend方法,使得子类也可以通过extend方法被继承
    Class.extend = arguments.callee;
    return Class;
  }
})();

通过使用simple-inheritance库,我们就可以通过很简单的方式实现继承了,是不是发现特别像强类型语言的继承。

var Human = Class.extend({
 init: function(age,name){
  this.age = age;
  this.name = name;
 },
 say: function(){
  console.log("I am a human");
 }
});
var Man = Human.extend({
  init: function(age,name,height){
    this._super(age,name);
    this.height = height;
  }, 
  say: function(){
    this._super();
    console.log("I am a man"); 
  }
});
var man = new Man(21,'bob','191');
man.say();

由浅入深讲解Javascript继承机制与simple-inheritance源码分析,希望本文分享能够帮助到大家。

Javascript 相关文章推荐
获取任意Html元素与body之间的偏移距离 offsetTop、offsetLeft (For:IE5+ FF1 )[
Dec 22 Javascript
jQueryUI如何自定义组件实现代码
Nov 14 Javascript
Javascript中Event属性搜集整理
Sep 17 Javascript
原生JS操作网页给p元素添加onclick事件及表格隔行变色
Dec 01 Javascript
js实现仿百度风云榜可重复多次调用的TAB切换选项卡效果
Aug 31 Javascript
Angularjs处理页面闪烁的解决方法
Mar 09 Javascript
vue组件间通信子与父详解(二)
Nov 07 Javascript
简单易扩展可控性强的Jquery转盘抽奖程序
Mar 16 jQuery
Element-ui树形控件el-tree自定义增删改和局部刷新及懒加载操作
Aug 31 Javascript
在vue中实现某一些路由页面隐藏导航栏的功能操作
Sep 21 Javascript
swiper实现导航滚动效果
Dec 13 Javascript
JavaScript实现滚动加载更多
Dec 27 Javascript
分享Javascript实用方法二
Dec 13 #Javascript
JavaScript判断按钮被点击的方法
Dec 13 #Javascript
jquery插件uploadify实现带进度条的文件批量上传
Dec 13 #Javascript
JavaScript代码判断点击第几个按钮
Dec 13 #Javascript
JavaScript模块化开发之SeaJS
Dec 13 #Javascript
node.js require() 源码解读
Dec 13 #Javascript
JavaScript 模块的循环加载实现方法
Dec 13 #Javascript
You might like
php gd2 上传图片/文字水印/图片水印/等比例缩略图/实现代码
2010/05/15 PHP
深入解析Session是否必须依赖Cookie
2013/08/02 PHP
php清空(删除)指定目录下的文件,不删除目录文件夹的实现代码
2014/09/04 PHP
微信支付开发发货通知实例
2016/07/12 PHP
如何在Laravel5.8中正确地应用Repository设计模式
2019/11/26 PHP
jquery构造器的实现代码小结
2011/05/16 Javascript
如何解决Jquery库及其他库之间的$命名冲突
2013/09/15 Javascript
微信浏览器内置JavaScript对象WeixinJSBridge使用实例
2015/05/25 Javascript
jquery获取css的color值返回RGB的方法
2015/12/18 Javascript
JS弹出层遮罩,隐藏背景页面滚动条细节优化分析
2016/04/29 Javascript
D3.js封装文本实现自动换行和旋转平移等功能
2016/10/14 Javascript
Node.js和Express简单入门介绍
2017/03/24 Javascript
浅谈Vue下使用百度地图的简易方法
2018/03/23 Javascript
通过vue-router懒加载解决首次加载时资源过多导致的速度缓慢问题
2018/04/08 Javascript
vue-cli 脚手架基于Nightwatch的端到端测试环境的过程
2018/09/30 Javascript
vue element-ui之怎么封装一个自己的组件的详解
2019/05/20 Javascript
JavaScript遍历查找数组中最大值与最小值的方法示例
2019/05/24 Javascript
vue中组件通信的八种方式(值得收藏!)
2019/08/09 Javascript
node删除、复制文件或文件夹示例代码
2019/08/13 Javascript
简单了解微信小程序 e.target与e.currentTarget的不同
2019/09/27 Javascript
vue使用element-ui实现表单验证
2020/12/13 Vue.js
在Windows中设置Python环境变量的实例讲解
2018/04/28 Python
Python基于最小二乘法实现曲线拟合示例
2018/06/14 Python
python保存二维数组到txt文件中的方法
2018/11/15 Python
对Python的交互模式和直接运行.py文件的区别详解
2019/06/29 Python
给你一面国旗 教你用python画中国国旗
2019/09/24 Python
美国亚马逊旗下男装网站:East Dane(支持中文)
2019/09/25 全球购物
高二英语教学反思
2014/01/19 职场文书
文明风采获奖感言
2014/02/18 职场文书
企业办公室岗位职责
2014/03/12 职场文书
正风肃纪剖析材料范文
2014/10/10 职场文书
乡镇三严三实学习心得体会
2014/10/13 职场文书
开展批评与自我批评发言材料
2014/10/17 职场文书
中职班主任培训心得体会
2016/01/07 职场文书
100句拼搏进取的名言警句,值得一读!
2019/10/07 职场文书
手把手教你导入Go语言第三方库
2021/08/04 Golang