Node.js入门

Node.js入门

什么是Node.js

简单来说,Node.js是一个服务器端的javascript运行时环境,使得我们可以在服务器端运行javascript代码。

至于安装Node.js,Node.js官网有已编译的二进制文件,也有源文件包。我自己是下载源文件重新编译安装的。

Hello,World

方法一:直接执行无参数的node命令,进入node命令行界面,打印Hello, World。

屏幕快照 2015-04-12 下午2.38.20

方法二:执行node hello.js命令,调用写好的javacript脚本。

屏幕快照 2015-04-12 下午2.39.06

一个基本的Web Server

这是nodejs.org的一个官方例子,使用Node.js建立一个web服务器。打开编辑器,建立一个名为app.js的文件,内容如下:

然后运行node app.js命令

屏幕快照 2015-04-12 下午3.15.33

打开浏览器访问http://127.0.0.1:1337/

屏幕快照 2015-04-12 下午3.18.18

另外,注意到没,跟之前的hello.js脚本不一样,运行这个app.js脚本后,程序并没有自动退出,而是一直在等待http链接。这是因为在listen函数中创建了事件监听器。。。

Node.js的异步回调

我们写一个读取文件内容的脚本,来展示Node.js的异步回调机制。

屏幕快照 2015-04-12 下午7.37.19

看到输出没,按照传统思路,程序应该是先读取文件,打印文件内容,最后才打印readfile end。而Node.js的I/O默认是异步式的,当程序需要I/O操作时候,并不阻塞来等待I/O完成,而是将I/O请求交给系统,然后接着运行后续任务,系统执行完I/O操作后会事件的形式通知该程序,触发程序中定义的回调函数。如上面代码中的function(err, data) {…}。

Node.js也有定义阻塞式读取文件的API:

屏幕快照 2015-04-12 下午7.50.32

 

javascript的面向对象

学习Node.js的基础是要学好javascript。以前一直觉得javascript只要会调用jQuery以及各种开源插件就行了,从来没去研究过他们的实现方法,对javacript的高级用法也从来没兴趣研究过。但到了Node.js这里,感觉像duang~的一下,被摔一脸,简直门外汉。先来这里恶补一下javascript比较反人类的面向对象概念

javascript并不是常规意义上的面向对象,因为它没有class,但却又有对象的概念。像C++这样的面向对象语言,对象是基于类(class)的,一个对象就是类的一个实例。而javascript的对象是基于原型(prototype)的,同一个构造函数创建的对象,都保留一个对原型对象prototype的引用。

原型继承函数util.inherits(subConstructor, baseConstructor)

刚定义完BaseClass与SubClass以及s1对象时候他们内存空间如下:

屏幕快照 2015-04-23 下午11.43.36 屏幕快照 2015-04-23 下午11.43.57屏幕快照 2015-04-23 下午11.45.53

调用原型继承函数util.inherits(SubClass, BaseClass),并定义了SubClass.prototype.talk2()方法后的内存空间:

屏幕快照 2015-04-23 下午11.48.47屏幕快照 2015-04-23 下午11.55.09屏幕快照 2015-04-23 下午11.53.40

由上图可以看出,util.inherits(SubClass, BaseClass)后,SubClass.prototype.__proto__由之前的基本Object对象变成了BaseClass.prototype,并且为SubClass定义了SubClass.super_属性,该属性指向BaseClass函数。

另外有个很重要的一点,在inherits(SubClass, BaseClass)之前定义的s1对象,它的原型仍然是旧的SubClass,而不是新的继承了BaseClass.prototype的SubClass。原因是因为javascript的对象是引用类型的。s1.__proto__指向对旧的SubClass.prototype的引用,而当inherits继承后,SubClass实际上是新建了一个prototype对象,旧的prototype则游离在内存中,仍被s1引用。而后所有新建的SubClass对象则是引用新的SubClass.prototype。并且新建的s2对象内部构成一个原型链:s2.__proto__(=SubClass.prototype).__proto__(=BaseClass.prototype).__proto__(=Object)。因此s2可以调用到SubClass.prototype上定义的属性和BaseClass.prototype上定义的属性。

下面是util.inherits的源码:

util.inherits只是继承了“父类”的原型,并没有继承“父类”构造函数中定义的本地属性。这是因为当我们将函数作为构造函数调用时候,并不会追溯到其“父类”的构造函数中,这点可以参考javascript的面向对象概念这篇博文。那如果我想C++中的那样,构造“子类”对象时候会先调用“父类”构造函数,应该怎么办?这里我们参考一下对Node.js EventEmitter的继承,EventEmitter是Node.js中处理事件、回调的核心模块,很多对象(包括程序员自定义的对象)都要继承EventEmitter。

这里用到了javascript Function的call方法,类似的还有apply, bind方法,具体用法自己百度。

exports与require

Node.js中,一个文件就是一个模块,在文件中用exports标记模块的接口,然后在外部使用require加载该模块。我们做一个简单的例子

运行node testModule.js,结果出错,myModule对象没有sayHi方法,因为在myModule.js中未将sayHi方法标记为模块的接口。正确的写法应该是:

 

接下来再来看一下exports与module.exports的区别:

运行node testModule.js输出的是:

屏幕快照 2015-04-14 下午8.52.19

在这个例子可以看出,exports与module.exprots在导出成员变量与成员函数时作用是一样的。

我们可以看一下此时的myModule变量是什么东西:(截图来自WebStorm,一款javascript IDE,很方便我们调试javacript脚本,支持Node.js)

屏幕快照 2015-04-14 下午8.56.15

myModule是一个对象,它有一个name成员属性,一个sayHi成员函数,以及一个__proto__原型对象。

再来看一下,如果我们直接给module.exports赋值,修改myModule.js文件为如下:

这时我们在运行node testModule.js就会出错,提示TypeError: Object hello world has no method ‘sayHi’,我们看一下此时的myModule变量在内存中是什么样:

屏幕快照 2015-04-14 下午9.42.52myModule是一个字符串。

可见,如果将module.exprots直接赋值为数值、字符串、数组、函数,那么在外部require该模块,得到的就会是相应类型的变量。而exports直接赋值的话,则不会被导出。这是因为require最终返回的其实是module.exports而不是exports,在模块中定义的所有exports.xxx都会追加到module.exports上。

如果我们希望require模块后得到的是一个对象,该对象有成员属性与成员函数作为接口,那就用exports.xxx =。(当然也可以用module.exports.xxx =,但这样略显繁琐一点)

如果我们希望reqiure模块后得到的是一个特定类型,比如说数值、字符串、数组、函数,那么就必须要使用module.exprots =。

 

一个模块作为一个“类”

在上面我们看到可以用module.exprots = function的方法导出函数,那么如果我们把这个函数作为构造函数呢,那这个模块不就相当于一个类的定义了嘛。看下面这个例子(看懂下面这个代码的前提是要了解javascript的面向对象):

构造完bill与kate两个Person对象后,他们的内容是这样的:

屏幕快照 2015-04-14 下午10.34.57 屏幕快照 2015-04-14 下午10.35.15

 

什么是npm

npm是javascript软件包管理工具,它协助Node.js管理第三方javacript模块。在安装Node.js时候,默认会为我们装上npm。就像CentOS上的yum install xxx命令一样,使用npm install xxx命令就能下载第三方javascript模块。

ps:由于国内网络的原因,npm下载有时候会超慢,我们可以将npm的registry修改为国内淘宝镜像。

参考资料

One thought on “Node.js入门

发表评论

电子邮件地址不会被公开。 必填项已用*标注