人们接触 JavaScript,都被他单纯的外表给骗了,殊不知,一下子又  Functional Programming 又 Object Oriented 又前台又跑到后台,活蹦乱跳。一旦你遇到某些障碍,面对的 JavaScript 也表现得脾气好,你怎么弄它,改造它,它也不会生气,却太容易让人迷惑,造成生气的居然是你或者我。真不知道是你玩 JavaScript 还是变成 JavaScript 玩你……

许多人被 JavaScript “蛊惑”过之后,深感不爽,立意要重新改造乃万恶的 JavaScript,首当其冲抓住的是便是“原型继承(Prototypical Inherit)”。关于“原型继承”和“类继承(Class Inherit)”,JavaScript 业界教父、Yahoo!UI 架构师 Douglas Crockford(D.C.) 认为是派别的问题(School),就像 Functional Programming 函数式较之于 Object Oriented,Object Oriented 蔚然成为主流却不等于 Functional Programming 便消退其光芒,而难以能成为为一宗一派立论,否则便是非黑即白的二元对立。

如右图是 D.C 本人,老人家了,常言道,老马识途,呵呵。

D.C 主要的意思是,学术上讨论“原型继承”向来占有一席位置,也有一批语言的思想亦立足于此“原型继承”,但是,当今人们之所以不认识或少见识“原型继承”的 Object Oriented 方法论,本质里头受 Java/C# 一派的影响,造成熟悉“类继承”的人群就占绝大多数。而回到“原型继承”的问题上,“原型继承”肯定也有“原型继承”的优点,有其可取的地方,不然也不能自成一派。至于具体是什么的优点?恕在下技浅、鲜知,大家有空问 Google 或 D.C 的文章当可,让小弟说也是重复 D.C 说过的话。不管怎么样,甚幸 D.C 如此替 JavaScript  的“原型继承”说话,尽管大家还不容易接受,然而那自然是一定无疑的——试问,你我眼中,类的概念已经普遍深入民心,根深蒂固,怎么可以说改就改?过于颠覆了吧,于是你我继续改造 JavaScript 的继承,使之符合为自己一套的生产经验,去实践应用……

随着 JavaScript 一路发展,现在已有几套可实现类的继承的方式或者途径呈现在大家面前,如今 node.js 的继承却是怎么的一种样子呢?咱们一起观察一下吧。

Node.js 的继承一方面没摒弃原型继承,一方面也大量应用类继承,一个类继承一个类,一个类继承一个类下去……sys.inherits() 即是继承任意两个类的方法。该方法支持传入两个 function 参数:sys.inherits(subFn, baseFn);,sunFn 是子类,baseFn 是父类。

一、process.inherits() v.s sys.inherits()

值得稍作讨论的是继承方法所属的命名空间。原本 inherits() 是依存在 process 对象身上的,后来改为 sys 对象。如果用户键入 process.inherits(...) 旧方法,NodeJS 会提示你这个用法已经弃置了,改用 sys.inherits ,即源码中:

……
process.inherits = removed("process.inherits() has moved to sys.inherits.");
……

新版 node.js 还有其他 API 命名的修改,inherits 只是其中的一项。显然作者 Ry 作修改有他自己的原因,才会有这样的决定,新版总是比旧版来的有改进,但有没有其他人建议他那样做却无从而知了:)。不过私下判断,从语意上来说继承方法应该定义在语言核心层次的,至少在 sys(System)上比在 process 的语意更为合适,更为贴切。要不然,进程 process 怎么会有继承的功能呢,感觉怪怪的,呵呵。不过话说回来,sys 必须要 require 一下才能用,而 process 却是默认的全局对象,不需要使用到 require 才能访问。

再说说 inherits() 这个方法和本身这个 inherits 单词性质(呵,真是无聊的俺)。君不见,许多的 JavaScript 库都有专门针对继承的方法,Prototype.js 的 extend 纯粹是拷贝对象,早期 jQuery 还尚未考虑所谓“继承”,还好留有余地,后来作者 John 把 JavaScript 继承推到一个层次,连模仿 Java 的 super() 语句都有,实现了基于类 Class 的 JavaScript 继承。为此 John 还写了博文,特别是这篇博文,让好奇的我很受益,了解脱壳的 JS 的方法继承——当然,那些是后话了,不过不能不提的是 jQuery 的继承方法其命名可是“extend()”,而且再说 YUI/Ext JS 之流亦概莫如是,然而为啥这个 node.js 的继承方法管叫做 inherits 呢,并有意无意地还加上第三人称的 -s 的时态!话说 inherits 照译也都是“继承”的意思,跟足了 OO 的原意,但却好像没有 extend 好记好背,敲键盘时便知道……

前面交待了一些背景后,目的只是想增加大家对继承 inherit 的兴趣,以便接着更深入主题。好了,真的进入主题,立马看看 sys.inherits 源码(exports.inherits,lib/sys.js 第327行):

/*** Inherit the prototype methods from one constructor into another.** The Function.prototype.inherits from lang.js rewritten as a standalone* function (not on Function.prototype). NOTE: If this file is to be loaded* during bootstrapping this function needs to be revritten using some native* functions as prototype setup using normal JavaScript does not work as* expected during bootstrapping (see mirror.js in r114903).** @param {function} ctor Constructor function which needs to inherit the*     prot

看来 node.js 有点特殊,与 YUI、Ext.js 的实现不太一样。可是,究竟是什么道理令到这个继承方法与众不同呢?依据源码表述,比较关键的是,似乎在于 Object.create() 该方法之上。Object.create() 又是什么呢?要疱丁解牛,揭开谜底的答案,我们可以从“基于对象的继承”和“基于类的继承”的认识来入手。

二、基于对象

首先是“基于对象”的继承。“基于对象”继承的概念,是可以允许没有“类(Class)”的概念存在的。所有的对象都是对 Object 的继承。我要从一个父对象,得到一个新的子对象,例如,兔子可以由“动物”这一对象直接继承。我们在 js 中:

// 定义父对象animal
var animal = new Object();
animal.age = new Number();
animal.eat = function(food){...}
// 定义子对象兔子rabbit
var rabbit = new Object();
rabbit.__proto__ = animal;

所以这里我们一律说“什么、什么对象”,而不出现“类”。Object 就是最原始的“对象”,处于顶层的父对象。JavaScript 中任何子对象其终极的对象便是这个 Object。“new Object”的意思是调用命令符 new,执行 Object 构造函数。这是一个空的对象。animal.age = new Number();这一句是分配一个名叫 age 的属性予以 animal 对象,其类型是数字 number;animal.eat = function(food){...} 就是分配一个名叫 eat 的方法予以 animal 对象,其参数是 food 食物。这样,animal 动物对象拥有了年龄 age 的属性和吃 eat 的方法,形成一个标准的对象。

接着,因为兔子肯定符合对象的意思,所以先声明一个空对象,赋予给 rabbit 变量。像这句话:var rabbit = new Object(); 然后注意了,rabbit.__proto__ = animal;就是建立继承关系的语句。_proto_ 是任何对象都有的属性(前提是在 Firefox 的 JS Enginer 运行下),也就是说每一个对象都有 _proto_ 属性。改变 _proto_ 的指向等于改变对象原型——也就是我们所说的“父对象”到底是哪一个。没错就是这么简单完成的 JavaScript 的继承。

但是有一个兼容性的问题。这么好用的 _proto_ 居然只准在 Firefox 的 JS 引擎中开放,别家的 JS 引擎就不能够让程序员接触得到。(Thx to qinfanpeng)原因有多种多样。总之不能够这样直接使用——无法使用。

也就是说不提倡 _proto_ 的用法。还好我们知道 JavaScript 作为动态语言,是支持晚绑定的特性的,就是可以让用户任意在某个对象上添加或删除某个成员。既然可以那样,我们就可以透过复制对象的成员达到派生新的对象的操作,如下例:

// 定义父对象animal
var animal = new Object();
animal.age = new Number();
animal.eat = function(food){...}
// 定义子对象兔子rabbit
var rabbit = new Object();
for(var i in animal){rabbit[i] = animal[i];
}

写一个 for 列出 animal 身上的所有成员,统统复制到 rabbit 这样原本空的对象身上。循环过后就算达到“继承”之目的了。再提炼一下,将 for 写成一个通用的 apply() 方法,如下:

Object.apply = function(superObject, sonObject){for(var i in superObject)sonObject[i] = sonObject[i];
}

应当指出,上面的“复制成员理念”可以是可以,并且运行无误,但大家有没有留意到,apply() 主要是一个 for(...){...} 循环。咱们一想到“循环语句”便很容易联想到耗时、是否会引致死循环等的问题,都是不好的问题,所以看能不能使用这个 for 循环,总之可以避免循环就好。——问题的深层次就涉及到代码是否优雅的问题:使用 apply() 被认为是不优雅的,尤其当越来越多使用 apply() 的时候,结果是遍布 for(...){...}。当然解决是否优雅最直接的方法是,JavaScript 语言提供直接可以代替 apply()。你们看,虽然那是如此小的问题,还是值得去修正,看来一再提倡的,追求完美、追求极致、追求越来越好可不是空喊的一句口号。

于是,ECMAScript v5.0 也就是新版 JavaScript 规定了 Object.create() 方法,提供了一个由父对象派生子对象的方法。新用法如下:

// 定义父对象animal
var animal = new Object();
animal.age = new Number();
animal.eat = function(food){...}
var rabbit = Object.create(animal);

非常直观是吧~一个 create() 搞掂了~实则回头看看也是表达要封装 _proto_ 的这么一层意义,此处顺便给出实现的方法(唠叨一下,除 Mozllia,V8 写法亦如此,参见 v8natives.js 第 694  行):

Object.create = function( proto) {var obj = new Object();obj.__proto__ = proto;return obj;
};

当然,for 的方法也等价的,

Object.create = function( proto) {var obj = new Object();for(var i in proto)obj[i] = proto[i];return obj;
};

如果你偏要走捷径,仅仅理解 es3.1 的改变只是换了马甲的话,变为 Object.create(),那只能说是“捷径”。其实它背后还有其他内容的(一些过程、一些参数……若干原理),俺作了删减,但绝不影响主干意思。如来大家能够理解到这里,就不错了,留个机会大家发掘其他的内容,也省得我费口舌^_^。(重点提示那个 constructor,在第二参数)。

到了这里已经完成了第一个派别“基于对象”的继承。我觉得,“基于对象继承”的说法可以说是多余的,因为对象就像一个框,什么都可往里在装。继承除了为对象服务外总不会指别的的意思吧!?所以基于对象的说法,可以说,只为后来,出现更高明的其他思想与之相对,才有基于对象的说法。

到这里,可以了解“原型继承(Prototypical Inherit)”是怎么一回事了。process.inherits 它的原理,在揭开 Object.create() 神秘面纱后,大概已经呼之欲出了。

三、类

前文里头卖了一个关子,所谓更高明的“思想”,就是类啦!表面上,类其实和对象没什么不同,也有方法、属性、事件等的概念,实际上,类就是对象的模板。好,明确这点后,我们清楚“类”作为一种特殊的“事物”,当然也不是凭空而生的。下面的 JS 语句结果是一样的,我们可以通过两者对比理解一下由“对象”到“类对象”的过程:

// 定义一个JS类(类在js中表现为Function)
function Foo(){// ……构造器过程
}
var o = new Object();
o.constructor = Foo;
Foo.call(o); // <---此步就是调用Foo(),更确切地说,是调用Foo构造函数。 其作用相当于var o = new Foo();

为什么要 call() 呢?因为 new 命令调用构造器 function Foo(){},最后必然会返回 this 当前实例对象,即:

funtion Foo(){// ……构造器过程// return this;
}

这个当前实例对象是啥呢?——在例子中便是 o 了。o 事先声明罢了。这样我们看到对象到类的“升华”!

是不是还是觉得不够透切呢?咱们还没说完咧~我们可以结合兔子的例子,同样是动物和兔子,写成类,从而诞生了 JS 中一种类的写法!

animal = function(){}
animal.prototype.age = new Number();
animal.prototype.eat = function(food){
}
rabbit = function(){}
rabbit.prototype = new animal();

开玩笑了, 这种写法才是 JS 的地道写法,老早就有了。上述写法彻底告别一个对象一个对象去定义层次关系。简单说,其涵义就是通过函数的原型 prototype,加多一层 function 来确定父对象是什么。首先有个认识,就是比起“基于对象”的继承,我们现在可以加入了“构造函数”,例如 animial = function(){} 和 rabbit = function(){} 分别就是父类的构造函数和子类的构造函数。但是如果我不需要子类的构造函数,却又不行,因为不可能不写一个 function,只有 function 才可以有 prototype 属性去定义成员。前面我们不是说过  _proto_ 是不开放的属性吗?惟独 Function 的 _proto_ 就总是开放的,也就是说 Function 对象都有的 _proto_ 的作用 apply() 和 call() 的作用,但是 _proto_ 的名字就变为没有下划线了,也就是 Function.prototype。况且 JS 之中,借助 Function 定义对象的模板是经常的写法,new 某个类就是建立对象,也让 prototype 发挥定义继承链作用。

既然 Function.prototype 总是开放的,那么用它代替 _proto_ 也行吧?没错,借助一个空的构造函数就行了,原来 Object.create 也可以这样写的:

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

当然这个 object 方法又回归到“基于对象继承”的方法上了 呵呵。我们可以从 D.C 介绍过的方法看出一点源头,借助网络,这些渊源都是有迹可循的。详见参考网址http://javascript.crockford.com/prototypal.html。实际上 Object.create 应该就从 D.C 方法来,好像他也是极力的推动者,不知道了……最后抄多个 Extend 代码帮助理解,原理没啥区别,关键胜在够简单清晰。

extend = function (Klass, Zuper) {Klass.prototype = Object.create(Zuper.prototype);Klass.prototype.constructor = Klass;
}

四、结语

今天说的大概可分后两部分,前部分打算从 node.js 的继承说开去,虽然不知道是不是废话较多,以致于后半部分衔接得不够好,但是还是说出了我的心底话,来来去去都是那几样事物,因为高度抽象,可能不易厘清,俺尽量也想说的圆,可不容易,他日有机会修正文章,这份读到的便当草稿吧,有缘人请将就过目,感觉有疑的话请尽管斧正,当然有窍门请解囊告知,多多提携!

参考:

  • http://ejohn.org/blog/ecmascript-5-objects-and-properties/
  • http://javascript.crockford.com/prototypal.html
  • Ext.extend() 中使用 super 关键字

学习NodeJS第五天:JavaScript的继承相关推荐

  1. 学习笔记(五)——JavaScript(二)

    文章目录 一.自定义对象 1.1.添加属性 1.2.读取属性 1.3.修改属性 1.4.删除属性 1.5.补充 二.函数对象 2.1.函数创建 2.2.注意事项 2.3.内置对象 三.数组对象 3.1 ...

  2. Java 学习手记(五)第二部分 继承与接口

    五.abstract 类和 abstract 方法 用关键字 abstract 修饰的类称为 abstract 类(抽象类).如: abstract class A { ... } abstract ...

  3. 重学前端学习笔记(五)-JavaScript原型

    JavaScript原型 中文中有个成语叫做"照猫画虎",这里的猫看起来就是虎的原型. 最为成功的流派是使用"类"的方式来描述对象,这诞生了诸如 C++.Jav ...

  4. python继承和多态心得_python学习第十五天 -面向对象之继承和多态

    大家都知道面向对象的三大特性:封装,继承,多态.封装特性在上一章节已经讲解过.这一章节主要讲解继承和多态. 继承:当定义一个类的时候,可以从现有的类进行继承.那么新定义的类可以称为子类,被继承的现有的 ...

  5. 学习笔记(六)——JavaScript(三)

    文章目录 一.鼠标事件 1.1.鼠标移动事件(onmousemove) 1.2.鼠标按键按下与松开事件(onmousedown/onmouseup) 1.3.鼠标滚轮事件(onmousewheel) ...

  6. 学习笔记(四)——JavaScript(一)

    JavaScript 是互联网上最流行的脚本语言,这门语言可用于 HTML 和 web,更可广泛用于服务器.PC.笔记本电脑.平板电脑和智能手机等设备. 文章目录 一.了解 JavaScript 二. ...

  7. JavaScript学习(五十二)—继承、call方法和apply方法

    JavaScript学习(五十二)-继承.call方法和apply方法 学习内容 一.继承 二.call方法 三.apply方法 一.继承 所谓继承就是两个构造方法建立起来的某种联系,通过某种联系,可 ...

  8. 前端面试+学习笔记(HTML+CSS+JavaScript+ES6+Vue+NodeJs)

    前端面试+学习笔记(HTML+CSS+JavaScript+ES6+Vue+NodeJs) 一. HTML 1. 盒子模型 是什么:每个元素被表示为一个矩形的盒子,有四个部分组成:内容(content ...

  9. JavaScript学习笔记(五)---cookie、Proxy、服务器、PHP语言、http协议、同步异步、事件轮循机制、ajax编写、接口

    JavaScript学习笔记(五)---cookie.Proxy.服务器.PHP语言.http协议.同步异步.事件轮循机制.ajax编写.接口 1.cookie 1.1cookie概念 1.2cook ...

最新文章

  1. Delphi数据库开发之TTable篇1
  2. [文章存档]Kudu 的 Debug Console 窗口如何查看更多文件
  3. 5.2 TensorFlow:模型的加载,存储,实例
  4. 怎么删除mysql的所有文件内容_mysql删除全部数据库
  5. 【项目总结】达能益力--官网
  6. 第六章 核心API (二)
  7. 关于多属性查找问题的sphinx解决方案
  8. 查不到元素_浓重中国元素游戏的本地化地狱
  9. xml与实体互相转换
  10. 服务器显示器分辨率,屏幕分辨率修改
  11. Win10 64位系统运行汇编程序(使用masm与dosbox)
  12. 中艺人脸识别考勤机使用方法_中控人脸识别考勤机说明书 人脸识别考勤机的使用方法...
  13. tumblr_如何制作私人Tumblr博客
  14. [吐血推荐]超冷笑话集锦!
  15. W3C 标准 较详细
  16. Windows 内存机制说明
  17. 如何进入华为P40 debug 模式/开发者选项模式
  18. 解决方案:macOS Mojave下Pycharm运行pygame无法加载外星人游戏图片以及无法修改颜色
  19. IE提示“存储空间不足,无法完成此操作”的错误(彻底解决包括产生原因)...
  20. h3c imc-dig 7 linux,H3C iMC iLP安装指导-7.0-5PW100

热门文章

  1. 计算机主机电子器件发展顺序,计算机采用的主机电子器件的发展顺序是什么
  2. 200道网络安全常见面试题合集(附答案解析+配套资料)
  3. matlab怎么画两个自变量的图_tcpip四层模型怎么画?画模型图的好用软件推荐
  4. 跳出互联网做互联网
  5. 几个简单易懂的 OSPF 实验
  6. 中国物流企业近三年快速发展前10名
  7. 2023护网面试题200道(附答案)
  8. 俄罗斯方块,4个方块的形状有5个,5个方块呢?6个?
  9. 动态规划经典题目——最大连续子序列之和
  10. 云计算学习路线图讲解:想学云计算怎么入门?