分享下《JavaScript忍者秘籍》中的一种编写类风格代码的方法

JavaScript可以让我们通过原型实现继承,许多开发人员,尤其是那些有传统面向对象背景的开发人员,都希望将JavaScript的继承系统简化并抽象成一个他们更熟悉的系统。
所以,这不可避免地引导我们走向类的领域。类是面向对象开发人员所期望的内容,尽管JavaScript本身不支持传统的类继承。
通常,这些开发人员希望它有如下特性:

  • 一套可以构建新构造器函数和原型的轻量级系统
  • 一种简单的方式来执行原型继承
  • 一种可以访问被函数原型所覆盖的方法的途径

以下代码展示了一个可以实现上述目标的示例。

//通过subClass()方法,创建一个Person类作为Object的一个子类,该方法之后实现
var Person = Object.subClass({init: function (isDancing) {this.dancing = isDancing;},dance: function () {return this.dancing;}
});//通过继承Person类,创建一个Ninja子类
var Ninja = Person.subClass({init: function () {
//需要一种调用父类构造器的方法——这里展示我们将这样做this._super(false);},dance: function () {//Ninja-specific stuff herereturn this._super();},swingSword: function () {return true;}
});//创建一个实例对Person类进行测试,看其是否能够跳舞
var person = new Person(true);
assert(person.dance(),"The person is dancing.");//创建一个实例对Ninja类进行测试,看其是否有swingSword方法以及继承过来的dance方法
var ninja = new Ninja();
assert(ninja.swingSword(),"The sword is swinging.");
assert(!ninja.dance(),"The ninja is not dancing.");//执行instanceof测试,验证类的继承
assert(person instanceof Person,"Person is a Person");
assert(ninja instanceof Ninja && ninja instanceof Person,"Ninja is a Ninja and a Person");复制代码

注意事项:

  • 通过调用现有构造器函数的subClass()方法可以创建一个新“类”,例如,通过Object创建一个Person类,以及通过Person创建一个Ninja类
  • 为了让构造器的创建更加简单。我们建议的语法是,为每个类只提供一个init()方法,就像为Person和Ninja提供的init()方法一样
  • 我们所有的“类”最终都继承于一个祖先:Object。因此,如果要创建一个新类,它必须是Object的一个子类,或者是一个在层级上继承于Object的类(完全模仿当前的原型系统)
  • 该语法的最大挑战是访问被覆盖的方法,而且有时这些方法的上下文也有可能被修改了。通过this._super()调用Person的原始init()和dance()方法,我们就可以了解这种用法

实现:

(function () {var initializing = false,
//粗糙的正则表达式用于判断函数是否可以被序列化。superPattern =/xyz/.test(function () {xyz;})?/\b_super\b/: /.*/;//给Object添加一个subClass方法Object.subClass = function (properties) {var _super = this.prototype;//初始化超类initializing = true;var proto = new this();initializing = false;for (var name in properties) {//将属性复制到prototype里proto[name] = typeof properties[name] === 'function' &&typeof _super[name] === 'function' &&superPattern.test(properties[name]) ?//定义一个重载函数(function (name, fn) {return function () {var tmp = this._super;this._super = _super[name];var ret = fn.apply(this, arguments);this._super = tmp;return ret;}})(name, properties[name]) :properties[name];}//创造一个仿真类构造器function Class() {if (!initializing && this.init) {this.init.apply(this, arguments);}}//设置类的原型Class.prototype = proto;//重载构造器引用Class.constructor = Class;//让类继续可扩展Class.subClass = arguments.callee;return Class;};
})();复制代码

检测函数是否可序列化

代码实现的一开始就很深奥,而且还可能让人困惑。在后续代码中,我们需要知道浏览器是否支持函数序列化。但该测试又有相当复杂的语法,所以现在就要得到结果,然后保存结果,以便在后续代码中不再进行复杂的操作,因为后续代码本身已经够复杂了。
函数序列化就是简单接收一个函数,然后返回该函数的源码文本。稍后,我们可以使用这种方法检查一个函数在我们感兴趣的对象中是否存在引用。
在大多数浏览器中,函数的toString()方法都会奏效。一般来说 ,一个函数在其上下文中序列化成字符串,会导致它的toString()方法被调用。所以,可以用这种方法测试函数是否可以序列化。
在设置一个名为initializing的变量为false之后,我们使用如下表达式测试一个函数是否能够被序列化:

/xyz/.test(function () { xyz; })复制代码

该表达式创建一个包含xyz的函数,将该函数传递给正则表达式的test()方法,该正则表达式对字符串“xyz”进行测试。如果函数能够正常序列化(test()方法将接收一个字符串,然后将触发函数的toString()方法),最终结果将返回true。
使用该文本表达式,我们在随后的代码中使用了该正则表达式:

superPattern =/xyz/.test(function () {xyz;}) ?/\b_super\b/ :/.*/;复制代码

建立了一个名为superPattern的变量,稍后用它来判断一个函数是否包含字符串"_super"。只有函数支持序列化才能进行判断,所以在不支持序列化的浏览器上,我们使用一个匹配任意字符串的模式进行代替。

子类的实例化

此时,我们准备开始定义一个方法用于子类化父类,我们使用如下代码进行实现:

Object.subClass = function (properties) {var _super = this.prototype;复制代码

给Object添加一个subClass()方法,该方法接收一个参数,该参数是我们期望添加到子类的属性集。
为了用函数原型模拟继承,我们创建父类的一个实例,并将其赋值给子类的原型。我们在代码中定义了一个initializing变量,每当我们想使用原型实例化一个类的时候,都将该变量设置为true。
因此,在构造实例时,我们可以确保不再实例化模式下进行构建实例,并可以相应地运行或跳过init()方法:

if (!initializing && this.init) {this.init.apply(this, arguments);
}复制代码

尤其重要的是,init()方法可以运行各种昂贵的启动代码(连接到服务器、创建DOM元素,还有其他未知内容),所以如果只是创建一个实例作为原型的话,我们要避免任何不必要的昂贵启动代码。

保留父级方法

大多数支持继承的语言中,在一个方法被覆盖时,我们保留了访问被覆盖方法的能力。这是很有用的,因为有时候我们是想完全替换方法的功能,但有时候我们却只是想增加它。在我们特定的实现中,我们创建一个名为_super的临时新方法,该方法只能从子类方法内部进行访问,并且该方法引用的是父类中的原有方法。
例如:

var Person = Object.subClass({init: function (isDancing) {this.dancing = isDancing;}
});var Ninja = Person.subClass({init: function () {this._super(false);}
});复制代码

在Ninja构造器内,我们调用了Person的构造器,并传入了一个相应的值。这可以防止重新复制代码——我们可以重用父类中已经编写好的代码。
该功能的实现是一个多步骤的过程。为了增强子类,我们向subClass()方法传入了一个对象哈希,只需要将父类的属性和传入的属性合并在一起就可以了。
首先,使用如下代码,创建一个超类的实例作为一个原型:

initializing = true;
var proto = new this();
initializing = false;复制代码

注意,我们是如何“保护”初始化代码的,正如我们在前一节中讨论的initializing变量的值。
现在,是时候将传入的属性合并到proto对象中了。如果不在意父类函数,合并代码将非常简单:

for (var name in properties)  {proto[name] = properties[name];
}复制代码

但是,我们需要关心父类的函数,所以前面的代码和除了调用父类函数的函数之外是等价的。重写函数时,可以通过_super调用父函数,我们需要通过名为_super的属性,将子类函数和父类函数的引用进行包装。但在完成该操作之前,我们需要检测即将被包装的子类函数。可以使用如下条件表达式:

typeof properties[name] === "function" &&
typeof _super[name] === "function" &&
superPattern.test( properties[name] )复制代码

这个表达式包含三个检测条件:

  • 子类属性是否是一个函数?
  • 超类属性是否是一个函数?
  • 子类函数是否都包含一个_super()引用?

只有三个条件都为true的时候,我们才能做所要做的事情,而不是复制属性值。注意,我们使用了之前设置的正则表达式,和函数序列化一起,测试函数是否会调用等效的父类。
如果条件表达式表明我们必须包装功能,我们通过给即时函数的结果进行赋值,将该结果作为子类的属性:

(function (name, fn) {return function () {var tmp = this._super;this._super = _super[name];var ret = fn.apply(this, arguments);this._super = tmp;return ret;}
})(name, properties[name])复制代码

该即时函数创建并返回了一个新函数,该新函数包装并执行了子类的函数,同时可以通过_super属性访问父函数。首先需要先保持旧的this._super引用(不管它是否存在),然后处理完以后再恢复该引用。这在同名变量已经存在的情况下会很有用(不想意外的丢失它)。
接下来,创建新的_super方法,它只是在父类原型中已经存在的一个方法的引用。值得庆幸的是,我们不需要做任何额外的代码修改或作用域修改。当函数成为我们对象的一个属性时,该函数的上下文会自动设置(this引用的是当前的子类实例,而不是父类实例)。
最后,调用原始的子类方法执行自己的工作(也有可能使用了_super),然后将_super恢复成原来的状态,并将方法调用结果进行返回。
有很多方式可以达到类似的结果(有的实现,会通过访问arguments.callee,将_super方法绑定到方法自身),但是该特定技术提供了良好的可用性和简便性。

es5 编写类风格的代码相关推荐

  1. 如何编写高质量的代码二 - 类的设计

    2019独角兽企业重金招聘Python工程师标准>>> 如何设计类, 关键点: 类的接口应该提供一贯的抽象.很多问题都是因为违背了这个简单的原则. 类的接口应该隐藏某些东西,比如系统 ...

  2. 项目质量监测(一)——代码质量检查-书写风格、代码规范、高度耦合 代码质量监测之Js检验工具-JSLint、JSHint、ESLint

    项目质量监测(一)--代码质量检查-书写风格.代码规范.高度耦合 & 代码质量监测之Js检验工具-JSLint.JSHint.ESLint 5-2 项目质量监测 课程介绍 没有规矩不成方圆,所 ...

  3. python代码怎么写出色_如何写出更具有Python风格的代码,五分钟教会你!

    我们都喜欢 Python,因为它让编程和理解变的更为简单.但是一不小心,我们就会忽略规则,以非 Pythonic 方式编写一堆垃圾代码,从而浪费 Python 这个出色的语言赋予我们的优雅.Pytho ...

  4. 表示python代码块的是_编写高质量Python代码的59个有效方法,你用过几个

    欢迎点击右上角关注小编,除了分享技术文章之外还有很多福利,私信学习资料可以领取包括不限于Python实战演练.PDF电子文档.面试集锦.学习资料等. 这个周末断断续续的阅读完了<Effectiv ...

  5. 单元测试:如何编写可测试的代码及其重要性

    原文来自互联网,由长沙DotNET技术社区编译.如译文侵犯您的署名权或版权,请联系小编,小编将在24小时内删除.限于译者的能力有限,个别语句翻译略显生硬,还请见谅. 作者:谢尔盖·科洛迪(SERGEY ...

  6. 如何写出更具有Python风格的代码

    我们都喜欢 Python,因为它让编程和理解变的更为简单.但是一不小心,我们就会忽略规则,以非 Pythonic 方式编写一堆垃圾代码,从而浪费 Python 这个出色的语言赋予我们的优雅.Pytho ...

  7. 如何编写出优秀的代码

    文章目录 前言 一.头文件 1.头文件有三部分组成 2.引用头文件的规则和建议 3.头文件的作用 4.定义文件和目录结构 二.程序的版式和格式 1.空行 2.代码行 3.代码行内的空格 4.对齐 5. ...

  8. python中如何编写代码输入多个数据并把它们放在一个列表中去_编写高质量Python代码的59个有效方法,你用过几个...

    欢迎点击右上角关注小编,除了分享技术文章之外还有很多福利,私信学习资料可以领取包括不限于Python实战演练.PDF电子文档.面试集锦.学习资料等. 这个周末断断续续的阅读完了<Effectiv ...

  9. python中如何编写代码输入多个数据并把它们放在一个列表中去_这59条编写高质量Python代码的方法你知道吗?...

    这个周末断断续续的阅读完了<Effective Python之编写高质量Python代码的59个有效方法>,感觉还不错,具有很大的指导价值. 下面将以最简单的方式记录这59条建议,并在大部 ...

最新文章

  1. 多线程真的会使用CPU所有的内核吗?
  2. 网络攻城狮怎么看待TCP/IP协议与UDP协议?
  3. python staticmethod and classmethod方法
  4. 常用的几个JavaScript调试技巧
  5. OpenCL 第8课:旋转变换(2)
  6. php对数组进行合成的函数,php合并数组函数array_merge()
  7. rabbitmq延迟队列相关
  8. JavaWeb之Cookie与Session
  9. Linux 高性能集群搭建(1)---ssh节点通信
  10. Qt QSsh 使用 windows Qt实现ssh客户端
  11. HTML中table表格与form表单
  12. utils 定时器 (一) 多级时间轮
  13. Delphi开发工具DevExpress VCL全新发布v21.1.5
  14. 进制转换(未完待续)
  15. 如何恢复录音删除的录音文件_手机通话录音后!点击这个按钮,就能将录音文件一键转为文字...
  16. 021-MongoDB数据库从入门到放弃
  17. 浙江大学发布四足机器人“绝影”,爬坡踏雪稳定性令人惊艳
  18. 人工神经网络研究综述,人工神经网络分析方法
  19. 计算机英语笔记,计算机英语学习笔记.doc
  20. 前妻贾斯汀解读马斯克何以能“极度成功”:痴迷!痴迷!痴迷!

热门文章

  1. 4月《程序员》上我讲HTML5的文章---激动人心的HTML5之美
  2. 在ApacheHTTPD服务器中使用DSO完全分析
  3. jQuery插件开发 - 其实很简单
  4. 按下回车键指向下一个位置的一个函数
  5. 动态的管理ASP.NET DataGrid数据列
  6. OpenCV支持的图像格式
  7. cppcheck源码学习(一)
  8. android 自定义radiobutton 样式,RadioButton自定义点击时的背景颜色
  9. php中怎样阻止网页进行跳转,阻止php页面跳转方法
  10. java 线程通讯_java多线程(五)线程通讯