Node.js学习笔记

花费5天的业余时间学习了JavaScript,接下来学习node.js,期望用于服务器端开发。

2016-04-20

安装

这里可以下载指定版本的node.js。 我是Windows用户,使用安装向导一步步安装即可。安装向导会自动把环境变量加入到PATH中,让我们可以在命令行中直接运行node.js的程序,常用的node、npm。

在命令行中使用node -v可以测试安装结果。

node

node是node.js执行js代码的程序。与大多数脚本语言一样,node也有两种执行方法。

  1. 执行指定js文件
  2. REPL方式,即命令行方式执行代码

npm

npm是随同node.js一起安装的包管理工具,能解决node.js代码部署上的很多问题,常见的使用场景有以下几种:

  • 允许用户从NPM服务器下载别人编写的第三方包到本地使用。
  • 允许用户从NPM服务器下载并安装别人编写的命令行程序到本地使用。
  • 允许用户将自己编写的包或命令行程序上传到NPM服务器供别人使用。

npm 的包安装分为本地安装(local)、全局安装(global)两种。区别为带不带参数-g。此外有下面问题:

  1. 安装位置:
    1.1 npm install moduleName则是将模块下载到当前命令行所在目录。
    1.2 npm install moduleName -g模块将被下载安装到全局目录中,win10默认在用户目录/AppData/Roaming/npm-cache中。
  2. 调用方式:
    2.1 本地安装可以在代码中直接通过require()的方式引入;var moduleName = require('moduleName');
    2.2 全局的安装是供命令行(command line)使用的,比如grunt,全局安装的方式是没有办法用require调用包的。

可以通过使用npm set global=true/false来设定安装模式,npm get global可以查看当前使用的安装模式。

2016-04-21

第一个node.js应用

创建文件demo001_hello_world.js,在文件中输入语句console.log("hello world");并保存。这样就完成了简单的一个HelloWorld应用。

在命令行中使用node执行命令node demo001\_hello\_world.js就可以执行第一个应用。在命令行中你可以看到输出了hello world文本。

第一个node.js服务端应用

下面是摘自网络的代码

var http = require('http');

http.createServer(function (request, response) {  
    // 发送 HTTP 头部 
    // HTTP 状态值: 200 : OK
    // 内容类型: text/plain
    response.writeHead(200, {'Content-Type': 'text/plain'});
    // 发送响应数据 "Hello World"
    response.end('Hello World\n');
}).listen(8888);

// 终端打印如下信息
console.log('Server running at http://127.0.0.1:8888/');  

首先,require语句引用了node.js内核模块http,并且把它赋值给http变量。

然后,调用http模块提供的函数:createServer。这个函数会返回一个对象,这个对象有一个叫做listen的方法,这个方法有一个数值参数,指定这个HTTP服务器监听的端口号。

createServer函数设置了一个方法,这个方法用于在程序接收到来自客户端的数据时进行回调。这里简单地返回了Hello World文本。

我通过遍历createServer返回的对象,可以看到该对象有很多一级的成员。主要的有下面这些:

domain  
allowHalfOpen  
pauseOnConnect  
httpAllowHalfOpen  
timeout  
setTimeout  
listen                 // 我们用了listen函数监听了端口  
address  
getConnections  
close  
listenFD  
ref  
unref  
setMaxListeners  
getMaxListeners  
emit  
addListener  
on  
once  
removeListener  
removeAllListeners  
listeners  
listenerCount  
...

其他成员的意义后续查找到再补充。

require加载模块时的搜索次序

在程序中使用require函数来加载某一个模块。该函数接收一个字符串参数。 如:var commander = require("commander");

  1. 核心模块(Core module)
    核心模块编译到了Node中。核心模块总是拥有最高的权限。当加载require("http"),那么总是加载核心的http模块。核心模块在Node源码的lib目录下。
  2. 文件模块(File module)
    非核心模块,从文件系统中加载。可以使用相对路径,绝对路径来加载或者从父目录的node_modules文件夹开始寻找加载。以斜杆开头的为绝对路径。在Windows系统中,斜杆和反斜杆都是可以的。以 . 或 .. 开头的为相对路径,相对于当前调用require函数的文件。
  3. 文件扩展名
    当require不能匹配到模块时,nodejs会尝试添加.js,.json和.node文件扩展名来寻找一遍。如果还是找不到,node会抛出一个错误。
  4. 输出模块路径
    使用require.resolve(packagename)来输出模块路径。如果是核心模块,直接输出模块名称;如果是文件模块,输出模块文件名称。
  5. 模块缓存
    一个成功加载的文件模块会被缓存到require.cache对象中。但是必须保证模块路径要保持一致,因为缓存对象缓存的是模块的resolved path。当项目依赖foo和bar两个模块,其中foo是独立的,bar依赖foo模块,那么foo会被加载两次。第一次的resolved path是your-project/nodemodules/foo,第二次(被bar引用)的是your-project/nodemodules/foo/node_modules

2016-04-23

node.js运行机制与事件模型

早期的apache、tomcat等服务器多为基于线程模型。启动服务器后,它开始等待接受连接。当收到一个连接,服务器保持连接连通直到页面或者什么事务请求完成。如果处理请求需要花几毫秒的时间去读取磁盘或者访问数据库,服务器就必须阻塞了几毫秒(这也被称之为阻塞式IO)。要服务更多的客户,就需要启动更多的线程。然而服务器资源是有限的,更多的线程需要更多的资源,二者形成的矛盾决定这种服务器模型已经走到头。

Node.js使用事件驱动模型。当服务器接收到请求就发出事件告诉你有请求到达。你的代码于是进行处理,这段时间占用服务器CPU,与上面提到的模型相比没有什么优势。但是如果你的处理流程中有需要IO操作,如读取磁盘。于是你通过node.js发起一个读取磁盘文件的调用,并设置好回调函数。这个时候node.js可以使用空闲下来的CPU去服务下一个web请求。而当你读取磁盘的请求完成后,node.js通过回调函数通知你请求已经完成。你可以在这个回调函数里继续未完的处理流程,最后把结果被返回给用户。

这个模型非常高效可扩展性非常强,因为服务器一直接受请求而不等待任何读写操作(这也被称之为非阻塞式IO或者事件驱动IO)。在事件驱动模型中,会生成一个主循环来监听事件,当检测到事件时触发回调函数。

实际上这种模型也有一些局限性。那就是在写代码的时候需要预判并设计合理的回调,这有点违反人类流式思考问题的模式。有个基于Nginx和Lua的OpenResty在这方面有更好的设计——“同步编程、异步处理”,只是我认为不算太成熟,故暂时用node.js。

node.js的内部对象与内核模块

EventEmitter:node.js事件模型的核心,一般不直接使用。
Buffer:对js的扩展,让node.js能够支持字节处理。
Stream:处理流

2016-04-24

单一平台上运行多个版本的node

ghost要求node的版本为4.2.0,node.js结合mysql使用的时候在4.2.0这个版本上出现了PROTOCOL_SEQUENCE_TIMEOUT错误,需要在4.2.1以后的版本才能去除该问题。使用nvm或者n等node.js版本管理工具可以建虚拟的版本的node.js,实现在单一平台上运行多个版本的node。

常用模块

express:用于开发web服务器
mysql:node.js的mysql连接器
async:异步接口扁平化调用,了解得还不够

ChardLau

继续阅读此作者的更多文章