通过寄生组合式继承创建js的异常类
最近项目中在做js的统一的异常处理,需要自定义异常类。理想的设计方案为:自定义一个异常错误类BaseError,继承自Error,然后再自定义若干个系统异常,例如用户取消异常、表单异常、网络异常,这些异常类都继承自BaseError。系统中,根据各个自定义异常做统一的异常处理,例如如果是用户发出取消操作指令,当前调用链则抛出一个用户取消异常,然后由统一异常处理捕获,先判断他是不是继承自BaseError,如果是则再根据事先定义好的处理方案处理。
为啥说这只是理想的设计方案呢?因为es5根本就没有提供js的继承语法,更没有提供能够继承自Error的方案。
但是,虽然es5的js没有真正意义上的继承方法,但是还是提供了很多折中的方案,例如使用修改原型对象,实现仿继承的方案,参考代码如下
function MyObject(){}
function MySonObject(){}
MySonObject.prototype = new MyObject()
例子代码中,MyObject是父类,他的一个对象赋给了子类MySonObject的原型,这样MySonObject就获取到MyObject的所有属性。通过这种形式,我们可以模拟出继承。
然后就是想办法继承自Error。这里先阐明一下为什么要继承自Error,作为异常类最重要一点就是一定要返回错误调用的堆栈信息,否则出错了都不知道到底是哪个地方抛出的,根本无法调试。js语法中,任何对象都可以被throw抛出,但是只有Error抛出才有堆栈信息,如下列测试。
try{throw "string"}catch(e){console.log(e)}
try{throw new Object()}catch(e){console.log(e)}
try{throw new Error()}catch(e){console.log(e)}

为了能够携带错误的堆栈信息,就必须要有Error对象,如果我们直接“继承”自Error对象,会有什么样的效果呢?在chrome里做如下测试:
function BaseError(){}
BaseError.prototype = new Error()
try{throw new BaseError()}catch(e){console.log(e)}
结果堆栈信息丢了。

不能直接继承Error,我们可以在BaseError里面的构造函数里定义Error,这样虽然没有直接继承Error,但是仍然有Error对象作为属性。
function BaseError(){this.error = new Error()}
//或者BaseError.prototype.error = new Error()
BaseError.prototype.printError = function(){console.log(this.error.stack)};
function MyError(){}
MyError.prototype = new BaseError()
try{throw new MyError()}catch(e){e.printError()}

这样写虽然携带了堆栈信息,但是堆栈信息的位置却是“function BaseError(){this.error = new Error()}”这句,而不是“new MyError()”,这样根本就没有什么意义。同样把Error创建过程放入BaseError的原型链上赋值(BaseError.prototype.error = new Error())一样无法获得正确的堆栈信息。只有把new Error()这句放入子类都构造器里,才能正确显示堆栈信息,不过这样就增加了子类的维护成本,继承的意义也丢失了。
有没有更好的解决方案呢?问题就这样我们的“继承”方式,因为js中,类的原型也是一个对象,BaseError子类中的原型是一个BaseError实例,所以属性error相当于一个静态属性,各个子类共享了这个error变量,同时仅在声明继承的时候调用了父类的构造函数,不能在子类创建对象时候调用构造函数,这使得Error对象不能在子类创建的时候被创建。所以只有改变目前的这种继承方式,实现Error随子类创建而创建,这样才能返回正确的堆栈信息。
因此继续深入地学习js的继承方式,发现被认为是最理想的“寄生组合继承”可以解决这个问题,首先简单介绍一下什么是寄生组合继承。
组合继承:因为类似上述我遇到的问题,原型链继承时候父类的属性是静态共享型属性,所以必须要在子类型的构造函数内,通过apply函数调用父类型的构造函数的一种继承方法。代码如下
function BaseError(){this.error = new Error()}
BaseError.prototype.printError = function(){console.log(this.error.stack)};
function MyError(){BaseError.apply(this);}
try{throw new MyError()}catch(e){console.log(e)}
这样创建MyError的时候,也调用了BaseError的构造函数,并打印出了正确的堆栈。不过也出现了一个问题,那就是BaseError的printError没有继承到。使用上述代码仅仅继承了属性,却不能继承父类的方法,这样显然也是不能满足需求的。
所谓组合继承方式,就是在apply调用超类构造函数继承基础上,再调用原型链继承。
function BaseError(){
this.error = new Error();
console.log(this.error); //为了显示父类的构造方法调用了两次
}
BaseError.prototype.printError = function(){console.log(this.error.stack)};
function MyError(){BaseError.apply(this);}
MyError.prototype = new BaseError()
try{throw new MyError()}catch(e){e.printError()}
打印出结果为

从结果看得出基本已经满足了我们的需求,但是需要注意点是父类的构造方法执行了两次,所以执行了两次console.log(this.error)。为了解决这个问题,需要引入另一个js继承方式,即寄生继承。
寄生组合继承:寄生继承的核心就是,现将超链的原型赋给另一个寄生类,然后创建这个寄生类的实例,再子类继承这个寄生类实例,这样就去除了原型继承中,需要创建基类的过程,代替为创建一个寄生类。将组合继承和寄生继承同时使用,就是组合寄生继承了。
function BaseError(){
this.error = new Error();
console.log(this.error);
}
BaseError.prototype.printError = function(){console.log(this.error.stack)};
//构造寄生类
function parasiticObject(){}
parasiticObject.prototype = BaseError.prototype;
function MyError(){BaseError.apply(this);}
MyError.prototype = new parasiticObject()
try{throw new MyError()}catch(e){e.printError()}

这样就解决了所有问题。
因为寄生继承导致整个继承的代码过于啰嗦,所有我们一般把继承过程写到一个函数里执行。这样就简化了整个继承代码了,同时也隐藏了无需暴漏的寄生类parasiticObject。
function BaseError(){
this.error = new Error();
console.log(this.error);
}
BaseError.prototype.printError = function(){console.log(this.error.stack)};
//继承方法
function inheritPrototype(superType,subType){
var prototype = Object(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
function MyError(){BaseError.apply(this);}
inheritPrototype(BaseError,MyError)
try{throw new MyError()}catch(e){e.printError()}
这样就完成了整个js异常类的设计。通过寄生组合继承方式,可以很好地实现js的类的继承,并通过创建Error获得错误的堆栈信息,方便调试,也为统一的异常处理机制奠定了基础,下一次将分享我们项目中的统一异常处理的设计。
通过寄生组合式继承创建js的异常类的更多相关文章
- 详解js中的寄生组合式继承
寄生组合式继承是js中最理想的继承方式, 最大限度的节省了内存空间. js中的寄生组合式继承要求是: 1.子对象有父对象属性的副本, 且这些不应该保存在子对象的prototype上. 2. ...
- js组合继承和寄生组合式继承比较
本文是原创文章,如需转载,请注明文章出处 1.js中实现组合继承(B继承A): function A(name){ this.name = name; this.ary = ["AA&quo ...
- js寄生组合式继承
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- [js高手之路]寄生组合式继承的优势
在之前javascript面向对象系列的文章里面,我们已经探讨了组合继承和寄生继承,回顾下组合继承: function Person( uName ){ this.skills = [ 'php', ...
- 寄生组合式继承 js
寄生组合式继承是集寄生式继承和组合继承的优点于一身,是基于类型继承最有效的方式 function object(o){ function F(){}; F.prototype = o; return ...
- JavaScript继承基础讲解,原型链、借用构造函数、混合模式、原型式继承、寄生式继承、寄生组合式继承
说好的讲解JavaScript继承,可是迟迟到现在讲解.废话不多说,直接进入正题. 既然你想了解继承,证明你对JavaScript面向对象已经有一定的了解,如还有什么不理解的可以参考<面向对象J ...
- javaScript设计模式之面向对象编程(object-oriented programming,OOP)--寄生组合式继承
组合式继承:将类式继承同构造函数继承组合使用,但是存在一个问题,子类不是父类的实例,而子类的原型式父类的实例,所以才有了寄生组合式继承. 意思就是说,寄生就是寄生式继承,寄生式继承就是依托于原型继承, ...
- JavaScript对寄生组合式继承的理解
有关JavaScript的几种继承方式请移步JavaScript的几种继承方式 原型链的缺陷 SubType.prototype = new SuperType(); 这样做的话,SuperType构 ...
- JavaScript寄生组合式继承分析
JavaScript寄生组合式继承特点: 避免了在子类prototype上创建不必要多余的属性,相比直接继承基类的实例效率要高. 是JavaScript 实现继承的最有效方式. <script& ...
随机推荐
- java类加载相关
可参考一篇文章:http://www.tuicool.com/articles/QZnENv 下面题输出结果 package com.h3c.itac; public class Dervied ex ...
- 用itextsharp打印pdf示例
学习了用itextsharp输出PDF文档,支持用XML定义文档,可定义多个数据源,简单的表达式,用于项目中效果还不错,其中PdfPageEvent类由包大人提供. 示例程序定义了一个简单的xml文件 ...
- linux时间同步ntp服务的安装与配置
1.首先安装NTP [root@localhost /]# yum install ntp -y 2.修改NTP配置文件,添加NTP服务器的网络位置 /etc/ntp.conf # For mo ...
- 利用js来实现文字的滚动(也就是我们常常见到的新闻版块中的公示公告)
首先先看一下大致效果图(因为是动态的,在页面无法显示出来) 具体的实现代码如下: 1.首先是css代码: <style type="text/css"> body,ul ...
- 启动App的Intent
类似桌面图标打开App的Intent 程序中需要一种通知,点击后的效果需要像点击桌面图标那样: 程序在前台就什么也不干. 程序在后台,就切换到前台. 程序未启动,就启动程序. 点击通知后,通知本身跳转 ...
- 公司内部培训SQL Server传统索引结构PPT分享
公司内部培训SQL Server传统索引结构PPT分享 下载地址 http://files.cnblogs.com/files/lyhabc/SQLServer%E4%BC%A0%E7%BB%9F%E ...
- iOS开发系列--C语言之预处理
概述 大家都知道一个C程序的运行包括编译和链接两个阶段,其实在编译之前预处理器首先要进行预处理操作,将处理完产生的一个新的源文件进行编译.由于预处理指令是在编译之前就进行了,因此很多时候它要比在程序运 ...
- java中文乱码解决之道(三)-----编码详情:伟大的创想---Unicode编码
随着计算机的发展.普及,世界各国为了适应本国的语言和字符都会自己设计一套自己的编码风格,正是由于这种乱,导致存在很多种编码方式,以至于同一个二进制数字可能会被解释成不同的符号.为了解决这种不兼容的问题 ...
- WPF +MVVM(Caliburn.Micro)项目框架
最近做了一个软件,这个软件不是网站,但是与HTML,AJAX等技术密切相关,也不是只有单纯的数据库增删改查,还涉及到线程协调,比较复杂的文本处理…… 这样的软件,用OA,ERP的框架显然是不合适的,因 ...
- 我所记录的git命令(非常实用)
一.前言 记录一下工作中常用到的git命令,只是简单的笔记,欢迎大家交流... [ 顺便问下园友们,怎么感觉博客园发布的博客搜索有时都搜不到,后台编辑能填的都填写了,还是觉得搜索排名不高? 相同的标题 ...