nodejs开发指南之快速入门

nodejs快速入门

1.1 HelloWorld

打开你常用的文本编辑器,在其中输入:

console.log('Hello World'); 

将文件保存为 helloworld.js,打开终端,进入 helloworld.js 所在的目录,执行以下命令:

node helloworld.js 

1.2使用 node 的 REPL 模式

REPL (Read-eval-print loop),即输入—求值—输出循环。
$ node > console.log('Hello World');
 Hello World undefined
      > consol.log('Hello World'); 
        at repl:1:1    
        at REPLServer.eval (repl.js:80:21)     
        at REPLServer.eval (repl.js:87:5)     
        at Interface. (repl.js:182:12)    
        at Interface.emit (events.js:67:17)     
        at Interface._line (readline.js:426:8)     
        at Interface._ttyWrite (readline.js:603:14)     
        at ReadStream. (readline.js:82:12)
         进入 REP
模式以后,会出现一个“>”提示符提示你输入命令,输入后按回车,Node.js 将会解析并执行命令。如果你执行了一个函数,那么 REPL 还会在下面显示这个函数的返回 值,上面例子中的 undefined 就是 console.log 的返回值。如果你输入了一个错误的 指令,REPL 则会立即显示错误并输出调用栈。在任何时候,连续按两次 Ctrl + C 即可推出 Node.js 的 REPL 模式。

1.3 建立 HTTP 服务器

1.3.1 创建HTTP服务器

//app.js 

var http = require('http'); 

http.createServer(function(req, res) {   
res.writeHead(200, {'Content-Type': 'text/html'});   
res.write('

Node.js

'
); res.end('

Hello World

'
); }).listen(3000); console.log("HTTP server is listening at port 3000.");

接下来运行 node app.js命令,打开浏览器访问 http://127.0.0.1:3000 即可看到Hello World

    用 Node.js 实现的简单的 HTTP 服务器就这样诞生了。这个程序调用了 Node.js 提供的 http 模块,对所有 HTTP 请求答复同样的内容并监听 3000 端口。在终端中运行这个脚本 时,我们会发现它并不像 Hello World 一样结束后立即退出,而是一直等待,直到按下 Ctrl + C 才会结。

1.4 异步式 I/O 与事件式编程

Node.js 大的特点就是异步式 I/O(或者非阻塞 I/O)与事件紧密结合的编程模式。

1.4.1 阻塞的定义

什么是阻塞(block)呢?线程在执行中如果遇到磁盘读写或网络通信(统称为 I/O 操作), 通常要耗费较长的时间,这时操作系统会剥夺这个线程的 CPU 控制权,使其暂停执行,同 时将资源让给其他的工作线程,这种线程调度方式称为 阻塞。 I/O 操作完毕时,操作系统 将这个线程的阻塞状态解除,恢复其对CPU的控制权,令其继续执行。这种 I/O 模式就是通 常的同步式 I/O(Synchronous I/O)或阻塞式 I/O (Blocking I/O)。 

特点:
nodejs开发指南之快速入门_第1张图片

1.4.2 回调函数

//readfile.js 

var fs = require('fs');
fs.readFile('file.txt', 'utf-8', function(err, data) {   
    if (err) {     
        console.error(err);   
        } else {    
         console.log(data);   
         }
    }); 
console.log('end.'); 

运行的结果如下:

end. 
Contents of the file.

nodejs提供同步读取

var fs = require('fs'); 
var data = fs.readFileSync('file.txt', 'utf-8'); 
console.log(data); 
console.log('end.');

运行的结果与前面不同,如下所示:

$ node readfilesync.js 
Contents of the file. 
end. 

同步式读取文件的方式比较容易理解,将文件名作为参数传入 fs.readFileSync 函 数,阻塞等待读取完成后,将文件的内容作为函数的返回值赋给 data 变量,接下来控制台 输出 data 的值,后输出 end.

我们必须先 知道在 Node.js 中,异步式 I/O 是通过回调函数来实现的。fs.readFile 接收了三个参数, 第一个是文件名,第二个是编码方式,第三个是一个函数,我们称这个函数为回调函数。 JavaScript 支持匿名的函数定义方式

fs.readFile 调用时所做的工作只是将异步式 I/O 请求发送给了操作系统,然后立即 返回并执行后面的语句,执行完以后进入事件循环监听事件。当 fs 接收到 I/O 请求完成的 事件时,事件循环会主动调用回调函数以完成后续工作。因此我们会先看到 end.,再看到 file.txt 文件的内容。

1.4.3 事件

Node.js 所有的异步 I/O 操作在完成时都会发送一个事件到事件队列。在开发者看来,事 件由 EventEmitter 对象提供。前面提到的 fs.readFile 和 http.createServer 的回 调函数都是通过 EventEmitter 来实现的。下面我们用一个简单的例子说明 EventEmitter 的用法:

//event.js 

var EventEmitter = require('events').EventEmitter; 
var event = new EventEmitter(); 

event.on('some_event', function() {   
    console.log('some_event occured.'); 
}); 

setTimeout(function() {   
event.emit('some_event'); }, 1000); 

运行这段代码,1秒后控制台输出了 some_event occured.。其原理是 event 对象 注册了事件 some_event 的一个监听器,然后我们通过 setTimeout 在1000毫秒以后向 event 对象发送事件 some_event,此时会调用 some_event 的监听器。

1.4.3.1 nodejs的时间循环机制

    Node.js 在什么时候会进入事件循环呢?答案是 Node.js 程序由事件循环开始,到事件循 环结束,所有的逻辑都是事件的回调函数,所以 Node.js 始终在事件循环中,程序入口就是 事件循环第一个事件的回调函数。事件的回调函数在执行的过程中,可能会发出 I/O 请求或 直接发射(emit)事件,执行完毕后再返回事件循环,事件循环会检查事件队列中有没有未 处理的事件,直到程序结束。


    与其他语言不同的是,Node.js 没有显式的事件循环,类似 Ruby 的 EventMachine::run() 的函数在 Node.js 中是不存在的。Node.js 的事件循环对开发者不可见,由 libev 库实现。libev 支持多种类型的事件,如 ev_io、ev_timer、ev_signal、ev_idle 等,在 Node.js 中均被 EventEmitter 封装。libev 事件循环的每一次迭代,在 Node.js 中就是一次 Tick,libev 不 断检查是否有活动的、可供检测的事件监听器,直到检测不到时才退出事件循环,进程结束。

nodejs开发指南之快速入门_第2张图片

1.5 模块和包

    开发一个具有一定规模的程 序不可能只用一个文件,通常需要把各个功能拆分、封装,然后组合起来,模块正是为了实 现这种方式而诞生的。
    Node.js 提供了 require 函数来调用其他模块,而且模块都是基于 文件的,机制十分简单。 
    我们经常把 Node.js 的模块和包相提并论,因为模块和包是没有本质区别的,两个概念 也时常混用。如果要辨析,那么可以把包理解成是实现了某个功能模块的集合,用于发布 和维护。对使用者来说,模块和包的区别是透明的,因此经常不作区分。

1.5.1 什么是模块

    模块是 Node.js 应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个 Node.js 文件就是一个模块,这个文件可能是 JavaScript 代码、JSON 或者编译过的 C/C++ 扩展

1.5.2 创建和加载模块

    1. 创建模块 
    在 Node.js 中,创建一个模块非常简单,因为一个文件就是一个模块,我们要关注的问 题仅仅在于如何在其他文件中获取这个模块。Node.js 提供了 exports 和 require 两个对 象,其中 exports 是模块公开的接口,require 用于从外部获取一个模块的接口,即所获 取模块的 exports 对象。 
    让我们以一个例子来了解模块。创建一个 module.js 的文件,内容是:
        //module.js 

        var name; 

        exports.setName = function(thyName) {   name = thyName; }; 

        exports.sayHello = function() {   console.log('Hello ' + name); }; 

在同一创建tmodule.js,内容是:

        //getmodule.js 

        var myModule = require('./module'); 

        myModule.setName('BYVoid'); 
        运

行node getmodule.js,结果是:

Hello BYVoid 

在以上示例中,module.js 通过 exports 对象把 setName 和 sayHello 作为模块的访 问接口,在 getmodule.js 中通过 require(‘./module’) 加载这个模块,然后就可以直接访 问 module.js 中 exports 对象的成员函数了。

2. 单次加载 
    上面这个例子有点类似于创建一个对象,但实际上和对象又有本质的区别,因为 require 不会重复加载模块,也就是说无论调用多少次 require,获得的模块都是同一个。 我们在 getmodule.js 的基础上稍作修改: 
        //loadmodule.js 

        var hello1 = require('./module'); 
        hello1.setName('BYVoid'); 

        var hello2 = require('./module');
        hello2.setName('BYVoid 2'); 
        hello1.sayHello(); 

运行后发现输出结果是 Hello BYVoid 2,这是因为变量 hello1 和 hello2 指向的是 同一个实例,因此 hello1.setName 的结果被 hello2.setName 覆盖,终输出结果是 由后者决定的。

3. 覆盖 exports
    有时候我们只是想把一个对象封装到模块中,例如: 
    //singleobject.js 

        function Hello() {  
        var name;      
        this.setName = function (thyName) {    
             name = thyName;   
            };   
        this.sayHello = function () {    
             console.log('Hello ' + name);  
              }; 
        };
        exports.Hello = Hello;

此时我们在其他文件中需要通过 require(‘./singleobject’).Hello 来获取 Hello 对象,这略显冗余,可以用下面方法稍微简化:

//hello.js 

    function Hello() {   
    var name;      
    this.setName = function(thyName) {    
     name = thyName;   };      
     this.sayHello = function() {   
       console.log('Hello ' + name);  
        }; 
       }; 
    module.exports = Hello; 

这样就可以直接获得这个对象了:

//gethello.js 

var Hello = require('./hello'); 

hello = new Hello();
hello.setName('BYVoid'); 
hello.sayHello(); 

注意,模块接口的唯一变化是使用 module.exports = Hello 代替了 exports.Hello= Hello。在外部引用该模块时,其接口对象就是要输出的 Hello 对象本身,而不是原先的 exports。 事实上,exports 本身仅仅是一个普通的空对象,即 {},它专门用来声明接口,本 质上是通过它为模块闭包①的内部建立了一个有限的访问接口。因为它没有任何特殊的地方, 所以可以用其他东西来代替,譬如我们上面例子中的 Hello 对象。

不可以通过对 exports 直接赋值代替对 module.exports 赋值。 exports 实际上只是一个和 module.exports 指向同一个对象的变量, 它本身会在模块执行结束后释放,但 module 不会,因此只能通过指定 module.exports 来改变访问接口。

1.6 Node.js 包管理器

Node.js包管理器,即npm是 Node.js 官方提供的包管理工具①,它已经成了 Node.js 包的 标准发布平台,用于 Node.js 包的发布、传播、依赖控制。npm 提供了命令行工具,使你可 以方便地下载、安装、升级、删除包,也可以让你作为开发者发布并维护包。

  1. 获取一个包 使用 npm 安装包的命令格式为:
    npm [install/i] [package_name] 

例如你要安装 express,可以在命令行运行:

$ npm install express

在使用 npm 安装包的时候,有两种模式:本地模式和全局模式。默认情况下我们使用 npm install命令就是采用本地模式,即把包安装到当前目录的 node_modules 子目录下。Node.js 的 require 在加载模块时会尝试搜寻 node_modules 子目录,因此使用 npm 本地模式安装 的包可以直接被引用。 npm 还有另一种不同的安装模式被成为全局模式,使用方法为:

npm [install/i] -g [package_name]

为什么要使用全局模式呢?多数时候并不是因为许多程序都有可能用到它,为了减少多 重副本而使用全局模式,而是因为本地模式不会注册 PATH 环境变量。举例说明,我们安装 supervisor 是为了在命令行中运行它,譬如直接运行 supervisor script.js,这时就需要在 PATH 环境变量中注册 supervisor。npm 本地模式仅仅是把包安装到 node_modules 子目录下,其中 的 bin 目录没有包含在 PATH 环境变量中,不能直接在命令行中调用。而当我们使用全局模 式安装时,npm 会将包安装到系统目录,譬如 /usr/local/lib/node_modules/,同时 package.json 文 件中 bin 字段包含的文件会被链接到 /usr/local/bin/。/usr/local/bin/ 是在PATH 环境变量中默认 定义的,因此就可以直接在命令行中运行 supervisor script.js命令了。

使用全局模式安装的包并不能直接在 JavaScript 文件中用 require 获 得,因为 require 不会搜索 /usr/local/lib/node_modules/。我们会在第 6章 详细介绍模块的加载顺序.

nodejs开发指南之快速入门_第3张图片

npm 提供了一个有趣的命令 npm link,它的功能是在本地包和全局包之间创建符号链 接。我们说过使用全局模式安装的包不能直接通过 require 使用,但通过 npm link命令 可以打破这一限制。举个例子,我们已经通过 npm install -g express 安装了 express, 这时在工程的目录下运行命令:

$ npm link express ./node_modules/express -> /usr/local/lib/node_modules/express

我们可以在 node_modules 子目录中发现一个指向安装到全局的包的符号链接。通过这 种方法,我们就可以把全局包当本地包来使用了。

npm link 不支持windows

你可能感兴趣的:(node.js)