设为首页 收藏本站
查看: 913|回复: 0

[经验分享] 【转】学习NodeJS第六天:JavaScript的继承

[复制链接]

尚未签到

发表于 2017-2-22 09:03:39 | 显示全部楼层 |阅读模式
  人们接触 JavaScript,都被他单纯的外表给骗了,殊不知,一下子又 FP 又 OO
又前台又跑到后台,活蹦乱跳。一旦你遇到某些障碍,面对的JavaScript也表现得脾气好,你怎么弄它,改造它,它也不会生气,却太容易让人迷惑,造
成生气的居然是你或者我。真不知道是你玩 JS 还是变成 JS 玩你……

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

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

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

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

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

      再说说 inherits() 这个方法和本身这个 inherits
单词性质(呵,真是无聊的俺)。君不见,许多的JS库都有专门针对继承的方法,Prototype.js 的 extend 纯粹是拷贝对象,早期
jQuery 还尚未考虑所谓“继承”,还好留有余地,后来作者 John 把 JavaScript 继承堆到一个层次,连模仿 Java 的
super() 语句都有,实现了基于类 Class 的 JavaScript 继承。为此 John
还写了博文,特别是这篇博文,让好奇的我很受益,了解脱壳的
JS 的方法继承——当然,那些是后话了,不过不能不提的是 jQuery 的继承方法其命名可是“extend()”,而且再说 YUI/Ext
之流亦概莫如是,然而为啥这个 NodeJS 的继承方法管叫做 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
*     prototype
* @param {function} superCtor Constructor function to inherit prototype from
*/  
exports.inherits = function (ctor, superCtor) {  
ctor.super_ = superCtor;  
ctor.prototype = Object.create(superCtor.prototype, {  
constructor: {  
value: ctor,  
enumerable: false  
}  
});  
};  
  看来 NodeJS 有点特殊,与 yui、ext
的实现不太一样。可是,究竟是什么道理令到这个继承方法与众不同呢?依据源码表述,比较关键的是,似乎在于 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引擎就不能够让程序员接触得到。原因有多种多样。总之不能够这样直接使用——无法使用。

      也就是说不提倡_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 = animal;  
}
       写一个for列出animal身上的所有成员,统统复制到rabbit这样原本空的对象身上。循环过后就算达到“继承”之目的了。再提炼一下,将for写成一个通用的apply()方法,如下:


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

     于是,ECMAScript v5.0(一说v3.1)也就是新版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 = proto;  
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;  
}  
 

参考:




  • http://ejohn.org/blog/ecmascript-5-objects-and-properties/


  • http://javascript.crockford.com/prototypal.html


  • Ext.extend()中使用super关键字


运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-345467-1-1.html 上篇帖子: Nodejs实现一个简单的静态文件合并服务器 下篇帖子: NodeJS框架express的路径映射(路由)功能及控制
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表