nodejs
Node.js借助事件驱动,非阻塞I/O模型变得轻量和高效,非常适合 运行在分布式设备 的 数据密集型 的实时应用。
- Visual Studio Code由微软出品,但它不是那个大块头的Visual Studio,它是一个精简版的迷你Visual Studio。VS Code以文件夹作为工程目录(Workspace Dir),所有的JavaScript文件都存放在该目录下。此外,VS Code在工程目录下还需要一个.vscode的配置目录,里面存放了VS Code需要的配置文件。
- process是一个全局内置对象,可以在代码中的任何位置访问此对象,这个对象代表我们的node.js代码宿主的操作系统进程对象。使用process对象可以截获进程的异常、退出等事件,也可以获取进程的当前目录、环境变量、内存占用等信息,还可以执行进程退出、工作目录切换等操作。
- stdout是标准输出流。console.log = function(d){ process.stdout.write(d+’\n’); } console.log就是封装了它。
- fs模块的文件I/O是对标准POSIX函数的简单封装。fs模块提供writeFile函数,可以异步的将数据写入一个文件, 如果文件已经存在则会被替换。
- 如果要读取目录下所有的文件应该怎么办呢?readdir函数可以读取到指定目录下所有的文件,回调函数(callback)接受两个参数(err, files)其中files是一个存储目录中所包含的文件名称的数组,数组中不包括’.’和’..’。
- node.js为互联网而生,和url打交道是无法避免的了,url模块提供一些基础的url处理。
- Query String模块用于实现URL参数字符串与参数对象之间的互相转换,提供了”stringify”、”parse”等一些实用函数来针对字符串进行处理,通过序列化和反序列化,来更好的应对实际开发中的条件需求。
- util模块,是一个Node.js核心模块,提供常用函数的集合,用于弥补核心JavaScript的一些功能过于精简的不足。并且还提供了一系列常用工具,用来对数据的输出和验证。
- util.inspect(object,[showHidden],[depth],[colors])是一个将任意对象转换为字符串的函数,通常用于调试和错误输出。
- Node保持了javascript在浏览器中单线程的特性,而且在Node中,javascript与其余线程是无法共享任何状态的。单线程的最大好处是不用像多线程编程那样出现状态的同步问题,这里没有死锁,也没有线程上下文切换所带来的性能上的开销。但是,单线程也有他自身的弱点,这些弱点是学习Node的过程中必须要面对的。单线程主要有以下弱点:1、无法利用多核CPU 2、错误会引起整个应用退出,应用的健壮性值得考验 3、大量计算占用CPU导致无法继续调用异步I/O。其中最后一点是最不能够忍受的,如果大量计算长期占用CPU导致后续I/O无法调用,已完成的I/O的回调函数也会得不到及时执行,这也就违背了当初创建高性能web服务器初衷。Node采用了Html5制定的Web Worker的标准相同的思路解决单线程中大量计算的问题:child_process。子进程的出现同时意味着Node可以从容的应对单线程在健壮性和无法利用多核CPU方面的问题。通过将计算分发到各个子进程,分解大量计算,然后再通过进程间的事件消息来传递结果,可以很好的保持应用模型的简单和低耦合。通过Master-Worker的管理方式,也可以很好的管理各个工作进程,已达到更高的健壮性。目前最新版本6.9.5已经支持Windows、Linux、MacOS、SunOS、Docker镜像。
- child_process模块提供了四个创建子进程的函数,分别是spawn,exec,execFile和fork。
- 如果在一个函数内部发生了错误,它自身没有捕获,错误就会被抛到外层调用函数,如果外层函数也没有捕获,该错误会一直沿着函数调用链向上抛出,直到被JavaScript引擎捕获,代码终止执行。
- WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。有一个优秀的第三方API,名为Socket.IO。Socket.IO是一个WebSocket库,包括了客户端的js和服务器端的nodejs,它的目标是构建可以在不同浏览器和移动设备上使用的实时应用。
除了标准的异步读取模式外,fs也提供相应的同步读取函数。同步读取的函数和异步函数相比,多了一个Sync后缀,并且不接收回调函数,函数直接返回结果。如果同步读取文件发生错误,则需要用try…catch捕获该错误。如果我们要获取文件大小,创建时间等信息,可以使用fs.stat(),它返回一个Stat对象,能告诉我们文件或目录的详细信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38try {
var data = fs.readFileSync('sample.txt', 'utf-8');
console.log(data);
} catch (err) {
// 出错了
}
```
+ 异步还是同步---在fs模块中,提供同步方法是为了方便使用。那我们到底是应该用异步方法还是同步方法呢?由于Node环境执行的JavaScript代码是服务器端代码,所以,绝大部分需要在服务器运行期反复执行业务逻辑的代码,必须使用异步代码,否则,同步代码在执行时期,服务器将停止响应,**因为JavaScript只有一个执行线程。**服务器启动时如果需要读取配置文件,或者结束时需要写入到状态文件时,可以使用同步代码,因为这些代码只在启动和结束时执行一次,不影响服务器正常运行时的异步执行。
+ stream是Node.js提供的又一个仅在服务区端可用的模块,目的是支持“流”这种数据结构。流的特点是数据是有序的,而且必须依次读取,或者依次写入,不能像Array那样随机定位。在Node.js中,流也是一个对象,我们只需要响应流的事件就可以了:data事件表示流的数据已经可以读取了,end事件表示这个流已经到末尾了,没有数据可以读取了,error事件表示出错了。要注意,data事件可能会有多次,每次传递的chunk是流的一部分数据。所有可以读取数据的流都继承自stream.Readable,所有可以写入的流都继承自stream.Writable。
```html
//从文件流读取文本内容
'use strict';
var fs = require('fs');
// 打开一个流:
var rs = fs.createReadStream('sample.txt', 'utf-8');
rs.on('data', function (chunk) {
console.log('DATA:')
console.log(chunk);
});
rs.on('end', function () {
console.log('END');
});
rs.on('error', function (err) {
console.log('ERROR: ' + err);
});
```
```html
//以流的形式写入文件,只需要不断调用write()方法,最后以end()结束
'use strict';
var fs = require('fs');
var ws1 = fs.createWriteStream('output1.txt', 'utf-8');
ws1.write('使用Stream写入文本数据...\n');
ws1.write('END.');
ws1.end();
var ws2 = fs.createWriteStream('output2.txt');
ws2.write(new Buffer('使用Stream写入二进制数据...\n', 'utf-8'));
ws2.write(new Buffer('END.', 'utf-8'));
ws2.end();一个Readable流和一个Writable流串起来后,所有的数据自动从Readable流进入Writable流,这种操作叫pipe。在Node.js中,Readable流有一个pipe()方法。
1
2
3
4
5
6
7//源文件的所有数据就自动写入到目标文件里了,所以,这实际上是一个复制文件的程序
'use strict';
var fs = require('fs');
var rs = fs.createReadStream('sample.txt');
var ws = fs.createWriteStream('copied.txt');
rs.pipe(ws);
//默认情况下,当Readable流的数据读取完毕,end事件触发后,将自动关闭Writable流。要开发HTTP服务器程序,从头处理TCP连接,解析HTTP是不现实的。这些工作实际上已经由Node.js自带的http模块完成了。应用程序并不直接和HTTP协议打交道,而是操作http模块提供的request和response对象。request对象封装了HTTP请求,我们调用request对象的属性和方法就可以拿到所有HTTP请求的信息;response对象封装了HTTP响应,我们操作response对象的方法,就可以把HTTP响应返回给浏览器。
- 处理本地文件目录需要使用Node.js提供的path模块,它可以方便地构造目录。使用path模块可以正确处理操作系统相关的文件路径。在Windows系统下,返回的路径类似于C:\Users\michael\static\index.html。
1
2
3
4
5
6
7'use strict';
var path = require('path');
// 解析当前目录:
var workDir = path.resolve('.'); // '/Users/michael'
// 组合完整的文件路径:当前目录+'pub'+'index.html':
var filePath = path.join(workDir, 'pub', 'index.html');
// '/Users/michael/pub/index.html'
1 | //文件服务器file_server.js 在命令行运行node file_server.js /path/to/dir |
在Node.js诞生后的短短几年里,出现了无数种Web框架、ORM框架、模版引擎、测试框架、自动化构建工具,数量之多,即使是JavaScript老司机,也不免眼花缭乱。
1
2
3
4
5常见的Web框架包括:Express,Sails.js,koa,Meteor,DerbyJS,Total.js,restify……
ORM框架比Web框架要少一些:Sequelize,ORM2,Bookshelf.js,Objection.js……
模版引擎PK:Jade,EJS,Swig,Nunjucks,doT.js……
测试框架包括:Mocha,Expresso,Unit.js,Karma……
构建工具有:Grunt,Gulp,Webpack……用generator实现异步比回调简单了不少,但是generator的本意并不是异步。Promise才是为异步设计的,但是Promise的写法……想想就复杂。为了简化异步代码,ES7(目前是草案,还没有发布)引入了新的关键字async和await。这是JavaScript未来标准的异步代码。
1
2
3
4//轻松地把一个function变为异步模式
async function () {
var data = await fs.read('/file1');
}由async标记的函数称为异步函数,在异步函数中,可以用await调用另一个异步函数,这两个关键字将在ES7中引入。要让Node.js运行ES7代码,需要把ES7代码“转换”为ES6代码,这样,Node.js就可以运行转换后的代码。这个转换工作可以用Babel实现。用Babel转码时,需要指定presets和plugins。presets是规则,我们stage-3规则,stage-3规则是ES7的stage 0~3的第3阶段规则。plugins可以指定插件来定制转码过程,一个preset就包含了一组指定的plugin。
- 为什么先加载babel-core/register,再加载app.js,魔法就会生效?原因是第一个require()是Node正常加载babel-core/register的过程,然后,Babel会用自己的require()替换掉Node的require(),随后用require()加载的所有代码均会被Babel自动转码后再加载。
- koa middleware—让我们再仔细看看koa的执行逻辑。核心代码是:
1
2
3
4
5app.use(async (ctx, next) => {
await next();
ctx.response.type = 'text/html';
ctx.response.body = '<h1>Hello, koa2!</h1>';
});
每收到一个http请求,koa就会调用通过app.use()注册的async函数,并传入ctx和next参数。我们可以对ctx操作,并设置返回内容。但是为什么要调用await next()?原因是koa把很多async函数组成一个处理链,每个async函数都可以做一些自己的事情,然后用await next()来调用下一个async函数。我们把每个async函数称为middleware,这些middleware可以组合起来,完成很多有用的功能。
例如,可以用以下3个middleware组成处理链,依次打印日志,记录处理时间,输出HTML:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17app.use(async (ctx, next) => {
console.log(`${ctx.request.method} ${ctx.request.url}`); // 打印URL
await next(); // 调用下一个middleware
});
app.use(async (ctx, next) => {
const start = new Date().getTime(); // 当前时间
await next(); // 调用下一个middleware
const ms = new Date().getTime() - start; // 耗费时间
console.log(`Time: ${ms}ms`); // 打印耗费时间
});
app.use(async (ctx, next) => {
await next();
ctx.response.type = 'text/html';
ctx.response.body = '<h1>Hello, koa2!</h1>';
});
middleware的顺序很重要,也就是调用app.use()的顺序决定了middleware的顺序。
此外,如果一个middleware没有调用await next(),会怎么办?答案是后续的middleware将不再执行了。这种情况也很常见,例如,一个检测用户权限的middleware可以决定是否继续处理请求,还是直接返回403错误:1
2
3
4
5
6
7app.use(async (ctx, next) => {
if (await checkUserPermission(ctx)) {
await next();
} else {
ctx.response.status = 403;
}
});
理解了middleware,我们就已经会用koa了!(高内聚,低耦合)
- 用post请求处理URL时,我们会遇到一个问题:post请求通常会发送一个表单,或者JSON,它作为request的body发送,但无论是Node.js提供的原始request对象,还是koa提供的request对象,都不提供解析request的body的功能!
- Nunjucks是什么东东?其实它是一个模板引擎。模板引擎就是基于模板配合数据构造出字符串输出的一个组件。模板引擎最常见的输出就是输出网页,也就是HTML文本。当然,也可以输出任意格式的文本,比如Text,XML,Markdown等等。Nunjucks是Mozilla开发的一个纯JavaScript编写的模板引擎,既可以用在Node环境下,又可以运行在浏览器端。但是,主要还是运行在Node环境下,因为浏览器端有更好的模板解决方案,例如MVVM框架。如果你使用过Python的模板引擎jinja2,那么使用Nunjucks就非常简单,两者的语法几乎是一模一样的,因为Nunjucks就是用JavaScript重新实现了jinjia2。注意,模板引擎是可以独立使用的,并不需要依赖koa虽然模板引擎内部可能非常复杂,但是使用一个模板引擎是非常简单的,因为本质上我们只需要构造这样一个函数:
1
2
3function render(view, model) {
// TODO:...
}
其中,view是模板的名称(又称为视图),因为可能存在多个模板,需要选择其中一个。model就是数据,在JavaScript中,它就是一个简单的Object。render函数返回一个字符串,就是模板的输出。
- Nunjucks模板引擎最强大的功能在于模板的继承。仔细观察各种网站可以发现,网站的结构实际上是类似的,头部、尾部都是固定格式,只有中间页面部分内容不同。如果每个模板都重复头尾,一旦要修改头部或尾部,那就需要改动所有模板。Nunjucks的性能问题主要出现在从文件读取模板内容这一步。这是一个IO操作,在Node.js环境中,我们知道,单线程的JavaScript最不能忍受的就是同步IO,但Nunjucks默认就使用同步IO读取模板文件。好消息是Nunjucks会缓存已读取的文件内容,也就是说,模板文件最多读取一次,就会放在内存中,后面的请求是不会再次读取文件的,只要我们指定了noCache: false这个参数。在开发环境下,可以关闭cache,这样每次重新加载模板,便于实时修改模板。在生产环境下,一定要打开cache,这样就不会有性能问题。
- 所有的第三方包都可以通过npm官网搜索并查看其文档:https://www.npmjs.com/
- 集成Nunjucks:集成Nunjucks实际上也是编写一个middleware,这个middleware的作用是给ctx对象绑定一个render(view, model)的方法,这样,后面的Controller就可以调用这个方法来渲染模板了。
- Node.js在全局变量process中定义了一个环境变量env.NODE_ENV,为什么要使用该环境变量?因为我们在开发的时候,环境变量应该设置为’development’,而部署到服务器时,环境变量应该设置为’production’。在编写代码的时候,要根据当前环境作不同的判断。
- 我们在使用上面编写的处理静态文件的middleware时,也可以根据环境变量判断:
1
2
3
4if (! isProduction) {
let staticFiles = require('./static-files');
app.use(staticFiles('/static/', __dirname + '/static'));
}
这是因为在生产环境下,静态文件是由部署在最前面的反向代理服务器(如Nginx)处理的,Node程序不需要处理静态文件。而在开发环境下,我们希望koa能顺带处理静态文件,否则,就必须手动配置一个反向代理服务器,这样会导致开发环境非常复杂。
nodejs处理静态文件只是为了方便调试,部署的时候前端放一个nginx,用nginx处理静态文件。
运行前,我们再检查一下app.js里的middleware的顺序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24//第一个middleware是记录URL以及页面执行时间:
app.use(async (ctx, next) => {
console.log(`Process ${ctx.request.method} ${ctx.request.url}...`);
var
start = new Date().getTime(),
execTime;
await next();
execTime = new Date().getTime() - start;
ctx.response.set('X-Response-Time', `${execTime}ms`);
});
//第二个middleware处理静态文件:
if (! isProduction) {
let staticFiles = require('./static-files');
app.use(staticFiles('/static/', __dirname + '/static'));
}
//第三个middleware解析POST请求:
app.use(bodyParser());
//第四个middleware负责给ctx加上render()来使用Nunjucks:
app.use(templating('view', {
noCache: !isProduction,
watch: !isProduction
}));
//最后一个middleware处理URL路由:
app.use(controller());注意到ctx.render内部渲染模板时,Model对象并不是传入的model变量,而是:
1
Object.assign({}, ctx.state || {}, model || {})
这个小技巧是为了扩展。首先,model || {}确保了即使传入undefined,model也会变为默认值{}。Object.assign()会把除第一个参数外的其他参数的所有属性复制到第一个参数中。第二个参数是ctx.state || {},这个目的是为了能把一些公共的变量放入ctx.state并传给View。
- node –inspect node.js 与 node node.js –inspect 有无效果
- In the simplest terms, the tilde matches the most recent minor version (the middle number). ~1.2.3 will match all 1.2.x versions but will miss 1.3.0.The caret, on the other hand, is more relaxed. It will update you to the most recent major version (the first number). ^1.2.3 will match any 1.x.x release including 1.3.0, but will hold off on 2.0.0.