`
niuzai
  • 浏览: 68165 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

【转】学习NodeJS第六天:JavaScript的继承

阅读更多

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

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

    如右图是 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[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(一说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[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;  
    }  
 

参考:

分享到:
评论

相关推荐

    nodejs-nlw04:第四周下周

    "nodejs-nlw04:第四周下周"可能是NLW活动中关于Node.js的第四周课程或挑战,侧重于准备下周的学习内容。 在本主题中,我们重点关注的是TypeScript,它是JavaScript的一个超集,添加了静态类型系统和现代化的语言...

    nodejs_learning:学习

    总之,“nodejs_learning”资料将带你走进 Node.js 的世界,通过实践和学习,你可以掌握使用 JavaScript 进行服务器端开发的能力,实现从前端到后端的无缝衔接。无论是初学者还是有经验的开发者,都可以从中受益,...

    nodejs 相关PDF书集

    Node.js是一种基于Chrome V8引擎的JavaScript运行环境,它允许开发者在服务器端使用JavaScript进行编程。这个PDF书集集合了多种关于Node.js的学习资源,涵盖了从基础到高级的各种主题,对于想要深入理解和掌握Node....

    learn-nodejs:学习-nodejs

    在本文中,我们将深入探讨"learn-nodejs"项目,这是一个专为学习Node.js设计的资源集合。Node.js是一个基于Chrome V8引擎的JavaScript运行环境,它允许开发者在服务器端使用JavaScript进行开发,打破了传统的前端与...

    NodeJs开发指南

    1. **回调函数中的错误处理**: 在Node.js中,通常在回调函数的第一个参数中传入错误对象。 2. **Promise和async/await**: 使用这些现代错误处理机制可以更好地组织代码,避免回调地狱。 **九、部署与性能优化** 1. ...

    fundamentos-nodejs-2021:到2021年火箭座位基础上的基础设施建设

    综上所述,"fundamentos-nodejs-2021"这个课程可能涵盖以上提及的Node.js和JavaScript知识点,通过学习这些内容,开发者可以掌握构建服务器端应用的基础技能,并了解如何利用Node.js进行全栈开发。压缩包中的...

    nodejs数据.rar

    在“nodejs数据.rar”这个压缩包中,我们可以推测它包含与Node.js相关的数据或项目。Node.js是一个基于Chrome V8引擎的JavaScript运行环境,它让JavaScript能够在服务器端执行,开启了后端开发的新篇章。Node.js以其...

    full-nodejs:整个nodejs课程

    "full-nodejs:整个nodejs课程"这个标题暗示了一个全面的 Node.js 学习资源,可能是包含一系列教程、项目案例或完整的教学课程。下面,我们将深入探讨 Node.js 的核心概念、关键模块以及如何通过实践来学习和提升 ...

    nodejs入门资料案例加详细使用手册.zip

    这个"nodejs入门资料案例加详细使用手册.zip"压缩包文件提供了学习Node.js的基础和实践材料,非常适合初学者入门。 一、Node.js基础知识 1. 非阻塞I/O模型:Node.js的核心特性之一是其非阻塞I/O模型,基于事件驱动...

    nodejs-fun:只是在节点周围闲逛并表达

    通过 "nodejs-fun" 项目,我们可以学习到如何使用 Node.js 构建实际的应用,理解事件驱动编程、模块化设计以及如何利用 JavaScript 处理服务器端任务。无论是初学者还是有经验的开发者,都能从中找到乐趣和挑战。

    DH-FS-NODE-T12:原始语言JavaScript NodeJS

    【标题】"DH-FS-NODE-T12:原始语言JavaScript NodeJS" 是一个与JavaScript和Node.js相关的项目,可能是教学资源或者一个实际的应用示例。Node.js是一个基于Chrome V8引擎的JavaScript运行环境,它允许开发者在...

    nodejs-api-learn:了解Node.js API

    Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,它允许开发者在服务器端使用 JavaScript 进行编程。Node.js 提供了一个丰富的 API,涵盖了网络、文件系统、加密、流处理等多个方面,使得开发高效且可...

    nodejs 基础教程.zip

    Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,它让开发者能够使用 JavaScript 来编写服务器端程序,极大地扩展了 JavaScript 的应用范围。Node.js 使用了一个事件驱动、非阻塞 I/O 模型,使其轻量又...

    example_plugin:使用typescript和nodejs的示例插件项目

    **example_plugin:使用typescript和nodejs的示例插件项目** 该项目是一个基于TypeScript和Node.js的插件开发示例,旨在帮助开发者了解如何利用这两种技术来构建可扩展的应用程序组件。TypeScript是一种静态类型的...

    fsoa-seminar-2015:Nodejs研讨会文件

    **标题与描述解析** 标题"**fsoa-seminar-2015:Nodejs研讨会文件**"表明这是一个关于2015年...这个研讨会的文件可能包括幻灯片、代码示例、笔记、讨论记录等,为参与者提供了深入学习Node.js和JavaScript的宝贵资源。

    demoNodejs:nodejs演示

    总结来说,`demoNodejs` 项目是一个学习和实践 Node.js 的良好起点,通过这个项目,你可以深入理解 JavaScript 在服务器端的应用,掌握事件驱动编程和非阻塞I/O的概念,以及如何使用 Node.js 构建实际应用。...

    文章:NodeJS的学校教程

    - **原型链**:JavaScript 对象可以通过原型链实现继承。 - **异步编程**:事件循环和回调函数是 JavaScript 的异步处理基础,还有 Promise 和 async/await 用于更优雅的异步控制流。 ### 2. Node.js 环境搭建与...

Global site tag (gtag.js) - Google Analytics