1 JS是脚本语言,脚本语言都需要一个解析器才能运行。对于写在HTML页面里的JS,浏览器充当了解析器的角色。而对于需要独立运行的JS,NodeJS就是一个解析器。
2 运行在浏览器中的JS的用途是操作DOM,浏
览器就提供了 document 之类的内置对象。而运行在NodeJS中的JS的用途是操作磁盘文件或搭
建HTTP服务器,NodeJS就相应提供了 fs、http 等内置对象。
3 在Linux系统下,使用NodeJS监听80或443端口提供HTTP(S)服务时需要root权限.
一种方式是使用 sudo 命令运行NodeJS。例如通过以下命令运行的 server.js 中有权限使用80
和443端口。一般推荐这种方式,可以保证仅为有需要的JS脚本提供root权限。
$ sudo node server.js
4 编写稍大一点的程序时一般都会将代码模块化。在NodeJS中,一般将代码合理拆分到不同的JS
文件中,每一个文件就是一个模块,而文件路径就是模块名。
在编写每个模块时,都有 require、exports、module 三个预先定义好的变量可供使用。
require 函数用于在当前模块中加载和使用别的模块,传入一个模块名,返回一个模块导出对
象。模块名可使用相对路径(以 ./ 开头),或者是绝对路径(以 / 或 C: 之类的盘符开头)。模块名中的 .js 扩展名可以省略。
如果是加载和使用一个JSON文件,模块名中 .json 扩展名不可省略。
exports 对象是当前模块的导出对象,用于导出模块公有方法和属性。别的模块通过 require
函数使用当前模块时得到的就是当前模块的 exports 对象。
通过 module 对象可以访问到当前模块的一些相关信息,但最多的用途是替换当前模块的导出
对象。
5 一个模块中的JS代码仅在模块第一次被使用时执行一次,并在执行过程中初始化模块的导出对
象。之后,缓存起来的导出对象被重复利用。
6 通过命令行参数传递给NodeJS以启动程序的模块被称为主模块。主模块负责调度组成整个程序
的其它模块完成工作。
7 虽然一般我们使用JS编写模块,但NodeJS也支持使用C/C++编写二进制模块。编译好的二进制模
块除了文件扩展名是 .node 外,和JS模块的使用方式相同.但是,除非JS模块不能满足需求,否则不要轻易使用二进制模块
8 NodeJS是一个JS脚本解析器,任何操作系统下安装NodeJS本质上做的事情都是把
NodeJS 执行程序复制到一个目录,然后保证这个目录在系统PATH环境变量下,以便终
端下可以使用node命令。
9 NodeJS使用 CMD 模块系统,主模块作为程序入口点,所有模块在执行过程中只初始化
一次。
10 NodeJS定义了一个特殊的 node_modules 目录用于存放模块。实际工程中,node_modules里主要存放第三方包.
如某个模块的绝对路径是 /home/user/hello.js,在该模块中使用 require('foo/bar') 方式加载模
块时,则NodeJS依次尝试使用以下路径。
◦ /home/user/node_modules/foo/bar
◦ /home/node_modules/foo/bar
◦ /node_modules/foo/bar
11 与PATH环境变量类似,NodeJS允许通过NODE_PATH环境变量来指定额外的模块搜索路
径。NODE_PATH环境变量中包含一到多个目录路径,路径之间在*nix下使用 : 分隔;
例如定义了以下NODE_PATH环境变量:
NODE_PATH=/home/user/lib:/home/lib
当使用 require('foo/bar') 的方式加载模块时,则NodeJS依次尝试以下路径。
◦ /home/user/lib/foo/bar
◦ /home/lib/foo/bar
12 JS模块的基本单位是单个JS文件,但复杂些的模块往往由多个子模块组成。为了
便于管理和使用,可以把由多个子模块组成的大模块称做包,并把所有子模块放在同一个目
录里。在组成一个包的所有子模块中,需要有一个入口模块,入口模块的导出对象被作为包的导出对象。
13 设置包的入口模块:
(1) 使用index.js作为入口模块的文件名,则加载模块时可以使用模块所在目录的路径代替模块文件路径.
以下两条语句等价。
var cat = require('/home/user/lib/cat');
var cat = require('/home/user/lib/cat/index');
(2) 设置package.json文件:想自定义入口模块的文件名和存放位置,就需要在包目录下包含一个 package.json 文
件,并在其中指定入口模块的路径。假如某个cat模块包的package.json 内容如下。
{
"name": "cat",
"main": "./lib/main.js"
}
则指定了main.js作为入口模块,并且可以直接使用 require('/home/user/lib/cat') 的方式加载模块。NodeJS
会根据包目录下的 package.json 找到入口模块所在位置。
14 在工程目录下打开终端,使用以下命令来下载三方包。
$ npm install argv
如果想要下载指定版本的话,可以在包名后边加上@<version>,例如通过以下命令可下载0.0.1版的 argv。
$ npm install argv@0.0.1
如果使用到的三方包比较多,在终端下一个包一条命令地安装未免太人肉了。因此NPM对
package.json 的字段做了扩展,允许在其中申明三方包依赖。因此,上边例子中的
package.json 可以改写如下:
{
"name": "node-echo",
"main": "./lib/echo.js",
"dependencies": {
"argv": "0.0.2"
}
}
15 使用NodeJS编写的东西,要么是一个包,要么是一个命令行程序,而前者最终也会用于开发后
者。
16 设置命令行程序:
假如用NodeJS写了一个程序,放在/home/vagrant/workspace/node-echo/bin/node-echo.js这个位置.
为了能在任何目录下都能运行该程序,即把JS文件当作shell脚本来运行,需要使用以下终端命令:
(1) 在shell脚本中,可以通过 '#!' 注释来指定当前脚本使用的解析器。所以首先在
node-echo.js 文件顶部增加以下一行注释,表明当前脚本使用NodeJS解析。
#! /usr/bin/env node
(2) 然后,使用以下命令赋予 node-echo.js 文件执行权限。
$ chmod +x /home/vagrant/workspace/node-echo/bin/node-echo.js
(3) 最后,在PATH环境变量中指定的某个目录下,例如在 /usr/local/bin 下边创建
一个软链文件,文件名与我们希望使用的终端命令同名,命令如下:
$ sudo ln -s /home/vagrant/workspace/node-echo/bin/node-echo.js /usr/local/bin/node-echo
这样处理后,就可以在任何目录下使用node-echo命令了。
17 实现文件拷贝:
1 var fs = require('fs'); 2 function copy(src, dst) { 3 fs.createReadStream(src).pipe(fs.createWriteStream(dst)); 4 } 5 function main(argv) { 6 copy(argv[0], argv[1]); 7 } 8 main(process.argv.slice(2));
18 process 是一个全局变量,可通过 process.argv 获得命令行参数。由于 argv[0] 固定
等于NodeJS执行程序的绝对路径,argv[1] 固定等于主模块的绝对路径,因此第一个命令
行参数从 argv[2] 这个位置开始。
19
JS语言自身只有字符串数据类型,没有二进制数据类型,因此NodeJS提供了一个与 String 对
等的全局构造函数 Buffer 来提供对二进制数据的操作。
Buffer 与字符串类似,除了可以用 .length 属性得到字节长度外,还可以用 [index] 方式
读取指定位置的字节.
Buffer 与字符串能够互相转化,例如可以使用指定编码将二进制数据转化为字符串:
var str = bin.toString('utf-8');
或者反过来,将字符串转换为指定编码下的二进制数据:
var bin = new Buffer('hello', 'utf-8');
Buffer 与字符串有一个重要区别。字符串是只读的,并且对字符串的任何修改得到的都是一个
新字符串,原字符串保持不变。至于 Buffer,更像是可以做指针操作的C语言数组。例如,可
以用 [index] 方式直接修改某个位置的字节。
bin[0] = 0x48;
而 .slice 方法也不是返回一个新的 Buffer,而更像是返回了指向原 Buffer 中间的某个位
置的指针,因此对 .slice 方法返回的 Buffer 的修改会作用于原 Buffer.
var bin = new Buffer([ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]);
var sub = bin.slice(2);
sub[0] = 0x65;
console.log(bin); // => <Buffer 68 65 65 6c 6f>
20 如果想要拷贝一份 Buffer,得首先创建一个新的 Buffer,并通过 .copy 方法把原
Buffer 中的数据复制过去。这个类似于申请一块新的内存,并把已有内存中的数据复制过去。
以下是一个例子。
1 var bin = new Buffer([ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]); 2 var dup = new Buffer(bin.length); 3 bin.copy(dup); 4 dup[0] = 0x48; 5 console.log(bin); // => <Buffer 68 65 6c 6c 6f> 6 console.log(dup); // => <Buffer 48 65 65 6c 6f>
总之,Buffer 将JS的数据处理能力从字符串扩展到了任意二进制数据。