打开你最喜欢的编辑器,创建一个helloworld.js文件。我们要做就是向STDOUT输出“Hello World”,如下是实现该功能的代码:
console.log("Hello World");
保存该文件,并通过Node.js来执行:
环境安装正常的话,就会在终端输出Hello World 。
在使用Node.js的模块之前,我们首先了解一下Node.js的require机制。
在Node.js中,定义一个模块十分方便。我们以计算圆形的面积和周长两个方法为例,来表现Node.js中模块的定义方式。
将这个文件存为module.js,并新建一个module_test.js文件,并写入以下代码:
可以看到模块调用也十分方便,只需要require需要调用的文件即可。
在require了这个文件之后,定义在exports对象上的方法便可以随意调用。Node.js将模块的定义和调用都封装得极其简单方便,从API对用户友好这一个角度来说,Node.js的模块机制是非常优秀的。
Node.js的模块分为两类,一类为原生(核心)模块,一类为文件模块。原生模块在Node.js源代码编译的时候编译进了二进制执行文件,加载的速度最快。另一类文件模块是动态加载的,加载速度比原生模块慢。但是Node.js对原生模块和文件模块都进行了缓存,于是在第二次require时,是不会有重复开销的。其中原生模块都被定义在lib这个目录下面,文件模块则不定性。
由于通过命令行加载启动的文件几乎都为文件模块。我们从Node.js如何加载文件模块开始谈起。加载文件模块的工作,主要由原生模块module来实现和完成,该原生模块在启动时已经被加载,进程直接调用到runMain静态方法。
_load静态方法在分析文件名之后执行
并根据文件路径缓存当前模块对象,该模块实例对象则根据文件名加载。
实际上在文件模块中,又分为3类模块。这三类文件模块以后缀来区分,Node.js会根据后缀名来决定加载方法。
这里我们将详细描述js后缀的编译过程。Node.js在编译js文件的过程中实际完成的步骤有对js文件内容进行头尾包装。以app.js为例,包装之后的app.js将会变成以下形式:
这段代码会通过vm原生模块的runInThisContext方法执行(类似eval,只是具有明确上下文,不污染全局),返回为一个具体的function对象。最后传入module对象的exports,require方法,module,文件名,目录名作为实参并执行。
这就是为什么require并没有定义在app.js 文件中,但是这个方法却存在的原因。从Node.js的API文档中可以看到还有__filename、__dirname、module、exports几个没有定义但是却存在的变量。其中__filename和__dirname在查找文件路径的过程中分析得到后传入的。module变量是这个模块对象自身,exports是在module的构造函数中初始化的一个空对象({},而不是null)。
在这个主文件中,可以通过require方法去引入其余的模块。而其实这个require方法实际调用的就是load方法。
load方法在载入、编译、缓存了module后,返回module的exports对象。这就是circle.js文件中只有定义在exports对象上的方法才能被外部调用的原因。
以上所描述的模块载入机制均定义在lib/module.js中。
Node.js中大部分的模块,都继承自Event模块(http://nodejs.org/docs/latest/api/events.html )。Event模块(events.EventEmitter)是一个简单的事件监听器模式的实现。具有addListener/on,once,removeListener,removeAllListeners,emit等基本的事件监听模式的方法实现。它与前端DOM树上的事件并不相同,因为它不存在冒泡,逐层捕获等属于DOM的事件行为,也没有preventDefault()、stopPropagation()、 stopImmediatePropagation() 等处理事件传递的方法。
在这段HTTP request的代码中,程序员只需要将视线放在onRequest这些业务事件点即可,至于内部的流程如何,无需过于关注。
实现一个继承了EventEmitter类是十分简单的,以下是Node.js中流对象继承EventEmitter的例子:
Node.js在工具模块中封装了继承的方法,所以此处可以很便利地调用。程序员可以通过这样的方式轻松继承EventEmitter对象,利用事件机制,可以帮助你解决一些问题。
在略微大一点的应用中,数据与Web服务器之间的分离是必然的,如新浪微博、Facebook、Twitter等。这样的优势在于数据源统一,并且可以为相同数据源制定各种丰富的客户端程序。以Web应用为例,在渲染一张页面的时候,通常需要从多个数据源拉取数据,并最终渲染至客户端。Node.js在这种场景中可以很自然很方便的同时并行发起对多个数据源的请求。
Node.js通过异步机制使请求之间无阻塞,达到并行请求的目的,有效的调用下层资源。
有的语言为了设计得使应用程序调用方便,将程序设计为同步I/O的模型。这意味着程序中的后续任务都需要等待I/O的完成。在等待I/O完成的过程中,程序无法充分利用CPU。为了充分利用CPU,和使I/O可以并行,目前有两种方式可以达到目的:
理想的异步I/O模型
理想的异步I/O应该是应用程序发起异步调用,而不需要进行轮询,进而处理下一个任务,只需在I/O完成后通过信号或是回调将数据传递给应用程序即可。
很多同学在遇见Node.js后必然产生过对回调函数究竟如何被调用产生过好奇。在文件I/O这一块与普通的业务逻辑的回调函数不同在于它不是由我们自己的代码所触发,而是系统调用结束后,由系统触发的。下面我们以最简单的fs.open方法来作为例子,探索Node.js与底层之间是如何执行异步I/O调用和回调函数究竟是如何被调用执行的。
fs.open的作用是根据指定路径和参数,去打开一个文件,从而得到一个文件描述符,是后续所有I/O操作的初始操作。
在JavaScript层面上调用的fs.open方法最终都透过node_file.cc调用到了libuv中的uv_fs_open方法,这里libuv作为封装层,分别写了两个平台下的代码实现,编译之后,只会存在一种实现被调用。
在uv_fs_open的调用过程中,Node.js创建了一个FSReqWrap请求对象。从JavaScript传入的参数和当前方法都被封装在这个请求对象中,其中回调函数则被设置在这个对象的oncomplete_sym属性上。
对象包装完毕后,调用QueueUserWorkItem方法将这个FSReqWrap对象推入线程池中等待执行。
QueueUserWorkItem接受三个参数,第一个是要执行的方法,第二个是方法的上下文,第三个是执行的标志。当线程池中有可用线程的时候调用uv_fs_thread_proc方法执行。该方法会根据传入的类型调用相应的底层函数,以uv_fs_open为例,实际会调用到fs__open方法。调用完毕之后,会将获取的结果设置在req->result上。然后调用PostQueuedCompletionStatus通知我们的IOCP对象操作已经完成。
PostQueuedCompletionStatus方法的作用是向创建的IOCP上相关的线程通信,线程根据执行状况和传入的参数判定退出。
至此,由JavaScript层面发起的异步调用第一阶段就此结束。
在调用uv_fs_open方法的过程中实际上应用到了事件循环。以在Windows平台下的实现中,启动Node.js时,便创建了一个基于IOCP的事件循环loop,并一直处于执行状态。
每次循环中,它会调用IOCP相关的GetQueuedCompletionStatus方法检查是否线程池中有执行完的请求,如果存在,poll操作会将请求对象加入到loop的pending_reqs_tail属性上。 另一边这个循环也会不断检查loop对象上的pending_reqs_tail引用,如果有可用的请求对象,就取出请求对象的result属性作为结果传递给oncomplete_sym执行,以此达到调用JavaScript中传入的回调函数的目的。 至此,整个异步I/O的流程完成结束。其流程如下:
事件循环和请求对象构成了Node.js的异步I/O模型的两个基本元素,这也是典型的消费者生产者场景。在Windows下通过IOCP的GetQueuedCompletionStatus、PostQueuedCompletionStatus、QueueUserWorkItem方法与事件循环实。对于*nix平台下,这个流程的不同之处在与实现这些功能的方法是由libeio和libev提供。
我们来分解一下这个应用,为了实现上文的用例,我们需要实现哪些部分呢?
最后,要实现我们的用例:允许用户上传图片,并将该图片在浏览器中显示出来。
这里我们要用到的外部模块是Felix Geisendörfer开发的node-formidable模块。它对解析上传的文件数据做了很好的抽象。
使用该模块,首先需要安装该模块。Node.js有它自己的包管理器,叫NPM。它可以让安装Node.js的外部模块变得非常方便。通过如下一条命令就可以完成该模块的安装:
如果终端输出如下内容:
就说明模块已经安装成功了。
现在我们就可以用formidable模块了——使用外部模块与内部模块类似,用require语句将其引入即可:
var formidable = require("formidable");
追根原因是fs.rename 问题,临时文件写成功了,但是就是无法移动...
是由于磁盘分区导致的,我建的工程在F盘,符合这个条件 C: -> F:
(2)将fs.renameSync(files.upload.path, "/tmp/sophie.jpg");中的"/tmp/sophie.jpg"改为 "./tmp/sophie.jpg"。
好了,写到这儿,也累了。Node.js还有很多知识需要我们去深入学习,
如何操作数据库、如何进行单元测试、Node.js的异步IO原理、Connect模块等等,