# 第一节 node概述

  Node为服务端JavaScript提供了一个事件驱动的、异步的平台。 专为数据密集型实时程序设计的。 node中的模块一旦被加载以后,就会被系统缓存。第二次加载该模块时,是从缓存中加载的。如果希望模块执行多次,则可以让函数返回一个函数,然后多次调用该函数。

node的优势?

  • 1、对于文件读写,采用的是非阻塞异步IO(input、output)。
  • 2、传统IO在读写文件的时候采用CPU处理,代码执行也处于等待中,浪费性能。
  • 3、非阻塞IO将读写操作交给CPU,而代码正常执行,减少等待浪费的性能。
  • 4、事件驱动,用事件驱动来完成服务器的任务调度。
  • 5、跨平台 能够在linux和window平台上有运行。Node是基于libuv实现跨平台的。

node的不足

  • 1、无法利用多核cpu
    解决方案

    • 1、pm2、nodemon等工具都可以实现创建多进程解决多核cpu的利用率问题。
    • 2、在v0.8之前,实现多进程可以使用child_process
    • 3、在v0.8之后,可以使用cluster模块,创建多个工作进程来解决多核CPU的利用率问题。
  • 2、错误会引起整个应用退出无法继续调用异步I/O
    解决方案

    • 1、Nginx反向代理、负载均衡,开启多个进程,绑定多个端口。
    • 2、pm2、forever等进程管理工具可以实现进程监控,错误自动重启。
    • 3、开多个进程监听同一端口,使用node提供的cluster或者child_process模块。
    • 4、使用try catch,抓取错误。
  • 3、大量计算占用CPU导致无法继续调用异步I/O
    解决方案

    • 1、将大量的密集计算拆分成多个子进程进行计算。

应用场景:

  • 1、实际应用:webpack、gulp、npm、http-server、json-server
  • 2、服务器中负责IO读写的中间层服务器。

Node特点

  • 1、移植了chromeV8引擎,解析和执行代码机制和浏览器js相同。
  • 2、沿用了js语法,另外扩展了自己需要的功能。
  • 3、nodejs是后台语言,其具备操作文件的能力,可以具备服务器的创建和操作能力。

1、全局对象

  • global: Node所在的全局环境
  • process: 该对象表示Node所处的当前进程.process.env是一个对象,可以通过其.属性名来获取具体的环境变量。proces.argv获取命令行参数(['node绝对路径','文件的绝对路径','参数1','参数2',...])。
  • console: 输入输出标准

2、全局变量

  • __filename: 指向当前运行的脚本文件名
  • __dirname: 指向当前运行的脚本所在的目录

3、核心模块

  • http:提供http服务器功能。
  • url:解析url。
  • fs:文本系统交互。
  • querystring:解析URL的查询字符串。
  • child_process:新建子进程。
  • util:提供的一系列使用小工具。
  • path:处理文件路径。
  • crypto:提供加密和解密功能,基本上是对OpenSSL的包装。

4、异常处理

  Node有三种方法来传递一个错误。

1、使用throw语句来抛出一个错误对象。
2、将错误对象传递给回调函数。
3、通过EventEmitter接口,来发出一个事件。

  • 1、try...catch: 无法捕获异步运行的代码抛出的错误。解决方法:将错误捕获代码,也放到异步执行。(如解析JSON.parse)
  • 2、回调函数

  node采用的方法,是将错误对象作为第一个参数传入回调函数。这样能避免捕获代码与发生错误的代码不在同一个时间段的问题。

    fs.readFile("/foo.txt",function(err,data){
        
    })
1
2
3
  • 3、EventEmitter接口的error事件
    var EventEmitter = require('events').EventEmitter;
    var emitter = new EventEmitter();
    //触发error事件
    emitter.emit('error', new Error('something bad happened'));
    //监听error错误
    emitter.on('error', function(err) {
        console.error('出错:' + err.message);
    });
1
2
3
4
5
6
7
8

  当一个异常违未被捕获,会触发uncaughtException事件。异常的上下文已经丢失,异常可能导致Node不能正常进行内存回收,出现内存泄露并记录错误日志,然后结束node进程。

    var logger = require('tracer').console();
    process.on('uncaughtException', function(err) {
        logger.log(err);
        process.exit(1);
    });
1
2
3
4
5
  • 4、unhandledRejection事件
    var promise = new Promise(function(resolve, reject) {
        reject(new Error("Broken."));
    });

    promise.then(function(result) {
        console.log(result);
    })

    //上面的错误抛出后不会有任何反应。通过监听unhandledRejection事件,就能解决这个问题。
    process.on('unhandledRejection', function (err, p) { //err是错误对象,p是产生错误的promise对象。
        console.error(err.stack);
    })
1
2
3
4
5
6
7
8
9
10
11
12

# 第二节 Node模块介绍

# 1、assert()

  主要用于断言。如果表达式不符合预期,就抛出一个错误。

    assert()<br>
    assert.ok()<br>
    assert.equal()<br>
    assert.notEqual()<br>
    assert.deepEqual()<br>
    assert.notDeepEqual()<br>
    assert.strictEqual()<br>
    assert.notStrictEqual()<br>
    assert.throws()<br>
    assert.doesNotThrow()<br>
    assert.ifError()<br>
    assert.fail()<br>
1
2
3
4
5
6
7
8
9
10
11
12

# 2、Buffer

  Buffer对象是Node处理二进制数据(如TCP数据流)的一个接口。它是Node原生提供的全局对象,可以直接使用,不需要require('buffer')。它是一个构造函数,生成的实例代表了V8引擎分配的一段内存,是一个类似数组的对象,成员都为0到255的整数值,即一个8位的字节。

 2.1、Buffer对象的直接赋值和取值

    // 生成一个256字节的Buffer实例
    var bytes = new Buffer(256);
    // 遍历每个字节,写入内容
    for (var i = 0; i < bytes.length; i++) {
        bytes[i] = i;
    }
    // 生成一个buffer的view
    // 从240字节到256字节
    var end = bytes.slice(240, 256);//slice方法创造原内存的一个视图
    end[0] // 240
    end[0] = 0;
    end[0] // 0
1
2
3
4
5
6
7
8
9
10
11
12

 2.2、Buffer实例的拷贝生成

    var bytes = new Buffer(8);
    for (var i = 0; i < bytes.length; i++) {
        bytes[i] = i;
    }
    var more = new Buffer(4);
    bytes.copy(more, 0, 4, 8);
    more[0] // 4
    copy方法将bytes实例的4号成员到7号成员的这一段,都拷贝到了more实例从0号成员开始的区域。
1
2
3
4
5
6
7
8

 2.3、Buffer构造函数

  用new命令生成一个实例,它可以接受多种形式的参数。

    // 参数是整数,指定分配多少个字节内存
    var hello = new Buffer(5);

    // 参数是数组,数组成员必须是整数值
    var hello = new Buffer([0x48, 0x65, 0x6c, 0x6c, 0x6f]);
    hello.toString() // 'Hello'

    // 参数是字符串(默认为utf8编码)
    var hello = new Buffer('Hello');
    hello.length // 5
    hello.toString() // "Hello"

    // 参数是字符串(不省略编码)
    var hello = new Buffer('Hello', 'utf8');

    // 参数是另一个Buffer实例,等同于拷贝后者
    var hello1 = new Buffer('Hello');
    var hello2 = new Buffer(hello1);  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

 2.4、Buffer类的方法

    Buffer.isEncoding():返回一个布尔值,表示Buffer实例是否为指定编码。
    Buffer.isBuffer():接受一个对象作为参数,返回一个布尔值,表示该对象是否为Buffer实例。
    Buffer.byteLength('Hello', 'utf8') // 5 返回字符串实际占据的字节长度,默认编码方式为utf8。
    Buffer.concat()//将一组Buffer对象合并为一个Buffer对象。
    var i1 = new Buffer('Hello');
    var i2 = new Buffer(' ');
    var i3 = new Buffer('World');
    Buffer.concat([i1, i2, i3], 10).toString()
    // 'Hello Worl'
    //如果Buffer.concat的参数数组只有一个成员,就直接返回该成员。如果有多个成员,就返回一个多个成员合并的新Buffer对象。Buffer.concat方法还可以接受第二个参数,指定合并后Buffer对象的总长度。
1
2
3
4
5
6
7
8
9
10

 2.5、实例属性

   length:返回Buffer对象所占据的内存长度。注意,这个值与Buffer对象的内容无关。

    buf = new Buffer(1234);
    buf.length // 1234
    buf.write("some string", 0, "ascii");
    buf.length // 1234
1
2
3
4

 2.6、实例方法

  write():可以向指定的Buffer对象写入数据。它的第一个参数是所写入的内容,第二个参数(可省略)是所写入的起始位置(默认从0开始),第三个参数(可省略)是编码方式,默认为utf8。
  slice():返回一个按照指定位置、从原对象切割出来的Buffer实例。它的两个参数分别为切割的起始位置和终止位置。
  toString():将Buffer实例,按照指定编码(默认为utf8)转为字符串。toString方法可以只返回指定位置内存的内容,它的第二个参数表示起始位置,第三个参数表示终止位置,两者都是从0开始计算。

    var buf = new Buffer('just some data');
    console.log(buf.toString('ascii', 5, 9));
    // "some"
1
2
3

  toJSON():将Buffer实例转为JSON对象。如果JSON.stringify方法调用Buffer实例,默认会先调用toJSON方法。

    var buf = new Buffer('test');
    var json = JSON.stringify(buf);
    json // '[116,101,115,116]'

    var copy = new Buffer(JSON.parse(json));
    copy // <Buffer 74 65 73 74>
1
2
3
4
5
6

# 3、Child Process模块

  child_process模块用于新建子进程。子进程的运行结果储存在系统缓存之中(最大200KB),等到子进程运行结束以后,主进程再用回调函数读取子进程的运行结果。

 3.1、主要方法

  • 1、exec()

  用于执行bash命令,它的参数是一个命令字符串。最多可以接受两个参数,第一个参数是所要执行的shell命令,第二个参数是回调函数,该函数接受三个参数,分别是发生的错误、标准输出的显示结果、标准错误的显示结果。exec方法会直接调用bash(/bin/sh程序)来解释命令,所以如果有用户输入的参数,exec方法是不安全的。

    var exec = require('child_process').exec;
    var ls = exec('ls -l', function (error, stdout, stderr) {
    if (error) {
        console.log(error.stack);
        console.log('Error code: ' + error.code);
    }
    console.log('Child Process STDOUT: ' + stdout);
    });
    由于标准输出和标准错误都是流对象(stream),可以监听data事件,因此上面的代码也可以写成下面这样。
    //推荐写法
    var exec = require('child_process').exec;
    var child = exec('ls -l');

    child.stdout.on('data', function(data) {
    console.log('stdout: ' + data);
    });
    child.stderr.on('data', function(data) {
    console.log('stdout: ' + data);
    });
    child.on('close', function(code) {
    console.log('closing code: ' + code);
    });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  • 2、execSync()

  是exec的同步执行版本。它可以接受两个参数,第一个参数是所要执行的命令,第二个参数用来配置执行环境。

  • 3、execFile()

  直接执行特定的程序,参数作为数组传入,不会被bash解释,因此具有较高的安全性。

    var child_process = require('child_process');
    var path = ".";
    child_process.execFile('/bin/ls', ['-l', path], function (err, result) {
        console.log(result)
    });
1
2
3
4
5
  • 4、spawn()

  创建一个子进程来执行特定命令,用法与execFile方法类似,但是没有回调函数,只能通过监听事件,来获取运行结果。它属于异步执行,适用于子进程长时间运行的情况。
  spawn方法接受两个参数,第一个是可执行文件,第二个是参数数组。
  spawn对象返回一个对象,代表子进程。该对象部署了EventEmitter接口,它的data事件可以监听,从而得到子进程的输出结果。
  spawn方法与exec方法非常类似,只是使用格式略有区别。

    child_process.exec(command, [options], callback)
    child_process.spawn(command, [args], [options])

    var child_process = require('child_process');
    var path = '.';
    var ls = child_process.spawn('/bin/ls', ['-l', path]);
    ls.stdout.on('data', function (data) {
        console.log('stdout: ' + data);
    });

    ls.stderr.on('data', function (data) {
        console.log('stderr: ' + data);
    });

    ls.on('close', function (code) {
        console.log('child process exited with code ' + code);
    });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  • 5、fork()

  直接创建一个子进程,执行Node脚本,fork('./child.js') 相当于 spawn('node', ['./child.js']) 。与spawn方法不同的是,fork会在父进程与子进程之间,建立一个通信管道,用于进程之间的通信。
  fork方法返回一个代表进程间通信管道的对象,对该对象可以监听message事件,用来获取子进程返回的信息,也可以向子进程发送信息。

    var n = child_process.fork('./child.js');
    n.on('message', function(m) {
        console.log('PARENT got message:', m);
    });
    n.send({ hello: 'world' });
    //child.js
    process.on('message', function(m) {
        console.log('CHILD got message:', m);
    });
    process.send({ foo: 'bar' });
1
2
3
4
5
6
7
8
9
10
  • 6、send()

  使用 child_process.fork() 生成新进程之后,就可以用 child.send(message, [sendHandle]) 向新进程发送消息。新进程中通过监听message事件,来获取消息。

# 4、cluster

  集群模块允许设立一个主进程和若干个工人进程,由主进程监控和协调工人进程的运行。worker之间采用进程间通信交换消息,集群模块内置一个负载均衡器,采用循环赛算法协调各个工人进程之间的负载运行。所有新建立的链接都由主进程完成,然后主进程再把TCP连接分配给指定的工人进程。

 4.1、worker对象

  是cluster.fork()的返回值,代表一个worker进程。有如下属性:

  1、workid:是cluster.workers中指向当前进程的索引值。
  2、worker.process:所有的worker进程都是用child_process.fork()生成的。child_process.fork()返回的对象,被保存在work.process之中。通过这个属性,可以获取worker所在的进程对象。
  3、work.send():用于在主进程中,向子进程发送信息。

    if (cluster.isMaster) {
        var worker = cluster.fork();
            worker.send('hi there');
        } else if (cluster.isWorker) {
        //监听主进程的消息。
        process.on('message', function(msg) {
            process.send(msg);//
        });
    }
1
2
3
4
5
6
7
8
9

 4.2、cluster.workers对象

  只有主进程才有,包含了所有worker进程。每个成员的键值就是一个worker进程对象,键名就是该worker进程的worker.id属性。

 4.3、cluster模块的属性和方法

  • 1、isWorker、isMaster

  属性由process.env.NODE_UNIQUE_ID决定,如果process.env.NODE_UNIQUE_ID为未定义,就表示该进程是主进程。

  • 2、fork()

  用于创建一个worker进程,上下文都是复制主进程。只有主进程才能调用这个方法。

  • 3、kill()

  终止worker进程。如果当前是主进程,就会终止与worker.process的联络,然后将系统信号法发向worker进程。如果当前是worker进程,就会终止与主进程的通信,然后退出,返回0。

  • 4、listening事件

  worker进程调用listening方法以后,“listening”事件就传向该进程的服务器,然后传向主进程。

  该事件的回调函数接受两个参数,一个是当前worker对象,另一个是地址对象,包含网址、端口、地址类型(IPv4、IPv6、Unix socket、UDP)等信息。这对于那些服务多个网址的Node应用程序非常有用。

    cluster.on('listening', function (worker, address) {
        console.log("A worker is now connected to " + address.address + ":" + address.port);
    });
1
2
3

 4.4、PM2

  PM2模块是cluster模块的一个包装层。它的作用是尽量将cluster模块抽象掉,让用户像使用单进程一样,部署多进程Node应用。

 4.5、nodemon   是一个node自动重启工具。通过nodemon app.js来启动程序。程序改变后,不用重启程序,直接刷新浏览器就可以看到更新的内容。

# 5、Event

  Node 提供 Event Emitter 接口。通过事件,解决多状态异步操作的响应问题。events模块的EventEmitter是一个构造函数,可以用来生成事件发生器的实例emitter。

    var EventEmitter = require('events').EventEmitter;
    var emitter = new EventEmitter();
    //然后,事件发生器的实例方法on用来监听事件,实例方法emit用来发出事件。EventEmitter对象的事件触发和监听是同步的,即只有事件的回调函数执行以后,函数f才会继续执行。
    emitter.on('someEvent', function () {
        console.log('event has occured');
    });

    function f() {
        console.log('start');
        emitter.emit('someEvent');
        console.log('end');
    }

    f()
    // start
    // event has occured
    // end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

1、Event Emitter 接口的部署

  Event Emitter 接口可以部署在任意对象上,使得这些对象也能订阅和发布消息。

    var EventEmitter = require('events').EventEmitter;
    function Dog(name) {
        this.name = name;
    }
    Dog.prototype.__proto__ = EventEmitter.prototype;
    // 另一种写法
    // Dog.prototype = Object.create(EventEmitter.prototype);
    var simon = new Dog('simon');
    simon.on('bark', function () {
    console.log(this.name + ' barked');
    });
    setInterval(function () {
    simon.emit('bark');
    }, 500);
    //使ratio继承EventEmitter
    util.inherits(Radio, EventEmitter);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

 2、Event Emitter 的实例方法

    emitter.on(name, f):对事件name指定监听函数f。

    emitter.addListener(name, f):addListener是on方法的别名。
    emitter.once(name, f):与on方法类似,但是监听函数f是一次性的,使用后自动移除。
    emitter.listeners(name):返回一个数组,成员是事件name所有监听函数
    emitter.removeListener(name, f):移除事件name的监听函数f
    emitter.removeAllListeners(name):移除事件name的所有监听函数
1
2
3
4
5
6
7
  • 2.1、emit()

  EventEmitter实例对象的emit方法,用来触发事件。它的第一个参数是事件名称,其余参数都会依次传入回调函数。

    var EventEmitter = require('events').EventEmitter;
    var myEmitter = new EventEmitter();
    var connection = function (id) {
    console.log('client id: ' + id);
    };
    myEmitter.on('connection', connection);
    myEmitter.emit('connection', 6);
    // client id: 6
1
2
3
4
5
6
7
8
  • 2.2、once()

  该方法类似于on方法,但是回调函数只触发一次。该方法返回一个EventEmitter对象,因此可以链式加载监听函数。

  • 2.3、removeListener()

  该方法用于移除回调函数。它接受两个参数,第一个是事件名称,第二个是回调函数名称。这就是说,不能用于移除匿名函数。

  • 2.4、setMaxListeners()

  Node默认允许同一个事件最多可以指定10个回调函数。

  • 2.5、removeAllListeners()

  该方法用于移除某个事件的所有回调函数。

  • 2.6、listeners()

  listeners方法接受一个事件名称作为参数,返回该事件所有回调函数组成的数组。

  • 2.7、事件捕获

  事件处理过程中抛出的错误,可以使用try...catch捕获。监听函数抛出的错误被try...catch代码块捕获了。一旦被捕获,该事件后面的监听函数都不会再执行了。   如果不使用try...catch,可以让进程监听uncaughtException事件。

  • 2.8、默认事件

  Events模块默认支持两个事件。 newListener事件:添加新的回调函数时触发。 removeListener事件:移除回调时触发。

# 6、express

  Express是目前最流行的基于Node.js的Web开发框架,可以快速地搭建一个完整功能的网站。

 1、运行原理

  • 1.1、底层:http模块

  Express框架建立在node.js内置的http模块上。http模块生成服务器的原始代码如下。

    var http = require("http");
    var app = http.createServer(function(request, response) {
        response.writeHead(200, {"Content-Type": "text/plain"});
        response.end("Hello world!");
    });
    app.listen(3000, "localhost");
    //Express框架的核心是对http模块的再包装。Express框架等于在http模块之上,加了一个中间层。
    var express = require('express');
    var app = express();
    app.get('/', function (req, res) {
    res.send('Hello world!');
    });
    app.listen(3000);
1
2
3
4
5
6
7
8
9
10
11
12
13
  • 1.2、中间件

  中间件(middleware)就是处理HTTP请求的函数。它最大的特点就是,一个中间件处理完,再传递给下一个中间件。   每个中间件可以接收三个参数,依次为request对象(代表HTTP请求)、response对象(代表HTTP回应),next回调函数(代表下一个中间件)。每个中间件都可以对HTTP请求(request对象)进行加工,并且决定是否调用next方法,将request对象再传给下一个中间件。中间件可以分为:

  • 1)应用中间件
  • 2)路由中间件
    使用步骤:
    1、获取路由中间件对象 `let router=express.Router()`2、配置路由规则  `router.请求方式(url,(req,res,next)=>{})`3、将router加入到应用 `app.use(router)`
1
2
3
4
  • 3)内置中间件
  • 4)第三方中间件
    const body-parser=require(" body-parser")
    app.use(body-parser())

1
2
3
  • 5)错误处理中间件

  一个不进行任何操作、只传递request对象的中间件,就是下面这样。

    function uselessMiddleware(req, res, next) {
        next();
    }
1
2
3
  • 1.3、use()

use是express注册中间件的方法,它返回一个函数

    var express = require("express");
    var http = require("http");
    var app = express();
    app.use(function(request, response, next) {
        console.log("In comes a " + request.method + " to " + request.url);
        next();
    });
    app.use(function(request, response) {
        response.writeHead(200, { "Content-Type": "text/plain" });
        response.end("Hello world!\n");
    });
    http.createServer(app).listen(1337);
    //上面代码使用app.use方法,注册了两个中间件。收到HTTP请求后,先调用第一个中间件,在控制台输出一行信息,然后通过next方法,将执行权传给第二个中间件,输出HTTP回应。由于第二个中间件没有调用next方法,所以request对象就不再向后传递了。

    1)use方法内部可以对访问路径进行判断,据此就能实现简单的路由,根据不同的请求网址,返回不同的网页内容。

    var express = require("express");
    var http = require("http");
    var app = express();
    app.use(function(request, response, next) {
        if (request.url == "/") {
            response.writeHead(200, { "Content-Type": "text/plain" });
            response.end("Welcome to the homepage!\n");
        } else {
            next();
        }
    });
     2)use方法也允许将请求网址写在第一个参数。这代表,只有请求路径匹配这个参数,后面的中间件才会生效。
    app.use('/path', someMiddleware);
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

 2、Express的方法

  • 2.1、all方法和HTTP动词方法

  针对不同的请求,Express提供了use方法的一些别名。比如,上面代码也可以用别名的形式来写。

    var express = require("express");
    var http = require("http");
    var app = express();
    //所有请求都必须通过该中间件,参数中的“*”表示对所有路径有效。主要是拦截404
    app.all("*", function(request, response, next) {
        response.writeHead(200, { "Content-Type": "text/plain" });
        next();
    });
    //除了get方法以外,Express还提供post、put、delete方法,即HTTP动词都是Express的方法。
    app.get("/", function(request, response) {
        response.end("Welcome to the homepage!");
    });
    app.get("/about", function(request, response) {
        response.end("Welcome to the about page!");
    });
    app.get("*", function(request, response) {
        response.end("404!");
    });
    http.createServer(app).listen(1337);
    //这些方法的第一个参数,都是请求的路径。除了绝对匹配以外,Express允许模式匹配。
    app.get("/hello/:who", function(req, res) {
        res.end("Hello, " + req.params.who + ".");
        //res.end()只能响应string||读文件中的data Buffer
    });
    // 匹配/commits/71dbb9c
    // 或/commits/71dbb9c..4c084f9这样的git格式的网址
    app.get(/^\/commits\/(\w+)(?:\.\.(\w+))?$/, function(req, res){
        var from = req.params[0];
        var to = req.params[1] || 'HEAD';
        //默认是utf-8
        res.send('commit range ' + from + '..' + to);
    });
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
  • 2.2、set()
   set方法用于指定变量的值。
   app.set("views", __dirname + "/views");
   app.set("view engine", "jade");
   //使用get()方法获取
   app.get("views");
1
2
3
4
5
  • 2.3、response对象

    1)Response.Redirect方法
    Response.Redirect的方法允许网址的重定向。

    2)response.sendFile方法
    response.sendFile方法用于发送文件。

    3)response.render方法
    response.render方法用于渲染网页模板。

    4)response.end方法
    response.end方法用于将数据返回给客户端。

    5)response.json
    response.json方法用于将接送数据返回给客户端。

    6)response.jsonp
    response.jsonp方法用于跨域。

    7)response.download
    response.download方法用于将下载。

    8)response.setHeader("a","234");
    用于写头。

    9)response.write("a","234");
    response.write方法用于将数据返回给客户端。

   
    app.get("/", function(request, response) {
        response.render("index", { message: "Hello World" });
    });
1
2
3
4
  • 2.4、request对象
    1)request.ip: request.ip属性用于获得HTTP请求的IP地址。
    2)request.files: request.files用于获取上传的文件。

 3、配置路由

  所谓“路由”,就是指为不同的访问路径,指定不同的处理方法。

  • 3.1、指定根路径
    app.get('/', function(req, res) {
        res.send('Hello World');//send方法,表示向浏览器发送一个字符串
    });
    //回调函数的req参数表示客户端发来的HTTP请求,res参数代表发向客户端的HTTP回应,这两个参数都是对象。
    //如果需要指定HTTP头信息,回调函数就必须换一种写法,要使用setHeader方法与end方法。
    app.get('/', function(req, res){
        var body = 'Hello World';
        res.setHeader('Content-Type', 'text/plain');
        res.setHeader('Content-Length', body.length);
        res.end(body);
    });
1
2
3
4
5
6
7
8
9
10
11
  • 3.2、指定特定路径
    app.get('/api', function(request, response) {
        response.send({name:"张三",age:40});
        //res.json(200, {name:"张三",age:40});
    });
1
2
3
4
  • 3.3、静态网页模板

  sendfile()专门用于发送文件。 在项目目录之中,建立一个子目录views,用于存放网页模板。

    var express = require('express');
    var app = express();
    app.get('/', function (req, res) {
        res.sendFile(__dirname + '/views/index.html');
    });
    app.get('/about', (req, res) => {
        res.sendFile(__dirname + '/views/about.html');
    });
    app.get('/article', (req, res) => {
        res.sendFile(__dirname + '/views/article.html');
    });
    app.listen(3000);
1
2
3
4
5
6
7
8
9
10
11
12
  • 3.4、动态网页模板
    //安装模板引擎
    npm install hbs --save-dev
    //使用模板引擎
    //render方法的参数就是模板的文件名,默认放在子目录views之中,后缀名已经在前面指定为html,
    app.set('view engine', 'html');
    app.engine('html', hbs.__express);
    //中间件
    app.use(express.bodyParser());
    app.get('/', function(req, res) {
        res.render('index',{title:"最近文章", entries:blogEngine.getBlogEntries()});
    });
    app.get('/about', function(req, res) {
        res.render('about', {title:"自我介绍"});
    });

    app.get('/article/:id', function(req, res) {
        var entry = blogEngine.getBlogEntry(req.params.id);
        res.render('article',{title:entry.title, blog:entry});
    });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  • 3.5、指定静态文件目录
    app.use(express.static('public'));
    //使用public指代public目录下的文件
    app.use("public",express.static('public'));
1
2
3

 4、Express.Router用法

  • 4.1、基本用法

  Express.Router是一个构造函数,调用后返回一个路由器实例。然后,使用该实例的HTTP动词方法,为不同的访问路径,指定回调函数;最后,挂载到某个路径。

    var router = express.Router();
    //为不同的路径,指定回调函数
    router.get('/', function(req, res) {
        res.send('首页');
    });

    router.get('/about', function(req, res) {
        res.send('关于');
    });
    //将路由挂载到某个路径
    app.use('/app', router);
1
2
3
4
5
6
7
8
9
10
11
  • 4.2、router.route方法

  router实例对象的route方法,可以接受访问路径作为参数。

    var router = express.Router();
    router.route('/api')
        .post(function(req, res) {
            // ...
        })
        .get(function(req, res) {
            Bear.find(function(err, bears) {
                if (err) res.send(err);
                res.json(bears);
            });
        });

    app.use('/', router);
1
2
3
4
5
6
7
8
9
10
11
12
13
  • 4.3、router中间件

  use方法为router对象指定中间件,即在数据正式发给用户之前,对数据进行处理。下面就是一个中间件的例子。

    router.use(function(req, res, next) {
        console.log(req.method, req.url);
        next();
    });
    //函数体中的next(),表示将数据传递给下一个中间件。
    //注意,中间件的放置顺序很重要,等同于执行顺序。而且,中间件必须放在HTTP动词方法之前,否则不会执行。
1
2
3
4
5
6
  • 4.4、对路径参数的处理

  router对象的param方法用于路径参数的处理

    router.param('name', function(req, res, next, name) {
        // 对name进行验证或其他处理……
        console.log(name);
        req.name = name;
        next();
    });

    router.get('/hello/:name', function(req, res) {
        res.send('hello ' + req.name + '!');
    });
    注意,param方法必须放在HTTP动词方法之前。
1
2
3
4
5
6
7
8
9
10
11
  • 4.5、app.route

  app.route实际上是express.Router()的缩写形式,除了直接挂载到根路径。因此,对同一个路径指定get和post方法的回调函数,可以写成链式形式。

    app.route('/login')
    .get(function(req, res) {
        res.send('this is the login form');
    })
    .post(function(req, res) {
        console.log('processing');
        res.send('processing the login form!');
    });
1
2
3
4
5
6
7
8

# 9、os模块

  os模块提供与操作系统相关的方法。

 9.1、os.EOL

  该属性是一个常量,返回当前操作系统的换行符(Windows系统是\r\n,其他系统是\n)

 9.2、os.arch()

  os.arch方法返回当前计算机的架构。

    require(`os`).arch()
    // "x64"
1
2

 9.3、os.tmpdir()

  os.tmpdir方法返回操作系统默认的临时文件目录。

# 10、process对象

  process对象是 Node 的一个全局对象,提供当前 Node 进程的信息。它可以在脚本的任意位置使用,不必通过require命令加载。该对象部署了EventEmitter接口。

 10.1、属性

  • 1、process.argv:返回一个数组,成员是当前进程的所有命令行参数。
  • 2、process.env:返回一个对象,成员为当前Shell的环境变量,比如process.env.HOME。
  • 3、process.installPrefix:返回一个字符串,表示 Node 安装路径的前缀,比如/usr/local。相应地,Node 的执行文件目录为/usr/local/bin/node。
  • 4、process.pid:返回一个数字,表示当前进程的进程号。
  • 5、process.platform:返回一个字符串,表示当前的操作系统,比如Linux。
  • 6、process.title:返回一个字符串,默认值为node,可以自定义该值。
  • 7、process.version:返回一个字符串,表示当前使用的 Node 版本,比如v7.10.0。

 10.2、process对象还有一些属性,用来指向 Shell 提供的接口

  • 1、process.stdout

  返回一个对象,表示标准输出。该对象的write方法等同于console.log,可用在标准输出向用户显示内容。

    console.log = function(d) {
        process.stdout.write(d + '\n');
    };
1
2
3
  • 2、process.stdin

  返回一个对象,表示标准输入。

    process.stdin.pipe(process.stdout)//将标准输入导向标准输出。
    //stdin和stdout都部署了stream接口,所以可以使用stream接口的方法。
    process.stdin.setEncoding('utf8');

    process.stdin.on('readable', function() {
    var chunk = process.stdin.read();
    if (chunk !== null) {
        process.stdout.write('data: ' + chunk);
    }
    });

    process.stdin.on('end', function() {
    process.stdout.write('end');
    });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • 3、stderr

  process.stderr属性指向标准错误。

  • 4、process.argv,process.execPath,process.execArgv
  • 1)process.argv属性返回一个数组,由命令行执行脚本时的各个参数组成。它的第一个成员总是node,第二个成员是脚本文件名,其余成员是脚本文件的参数。
    //node.js
    console.log("argv: ", process.argv);
    var myArgs = process.argv.slice(2);
    console.log(myArgs);
    //在git bash中运行
    $ node argv.js a b c
    [ 'node', '/path/to/argv.js', 'a', 'b', 'c' ]
1
2
3
4
5
6
7
  • 2)process.execPath属性返回执行当前脚本的Node二进制文件的绝对路径。

  • 3)process.execArgv属性返回一个数组,成员是命令行下执行脚本时,在 Node 可执行文件与脚本文件之间的命令行参数。

  • 5、process.env

  process.env属性返回一个对象,包含了当前Shell的所有环境变量。比如,process.env.HOME返回用户的主目录。   通常的做法是,新建一个环境变量NODE_ENV,用它确定当前所处的开发阶段,生产阶段设为production,开发阶段设为develop或staging,然后在脚本中读取process.env.NODE_ENV即可。   运行脚本时,改变环境变量,可以采用下面的写法。

    $ export NODE_ENV=production && node app.js
    # 或者
    $ NODE_ENV=production node app.js
1
2
3

 10.3、方法

    process.chdir():切换工作目录到指定目录。
    process.cwd():返回运行当前脚本的工作目录的路径(绝对路径)。
    process.exit():立刻退出当前进程。
    process.getgid():返回当前进程的组ID(数值)。
    process.getuid():返回当前进程的用户ID(数值)。
    process.nextTick():指定回调函数在当前执行栈的尾部、下一次Event Loop之前执行。
    process.on():监听事件。
    process.setgid():指定当前进程的组,可以使用数字ID,也可以使用字符串ID。
    process.setuid():指定当前进程的用户,可以使用数字ID,也可以使用字符串ID
1
2
3
4
5
6
7
8
9
  • 1、process.cwd()与__dirname的区别。

  前者进程发起时的位置,后者是脚本的位置,两者可能是不一致的。比如,node ./code/program.js,对于process.cwd()来说,返回的是当前目录(.);对于__dirname来说,返回是脚本所在目录,即./code/program.js。

  • 2、process.nextTick()

  process.nextTick将任务放到当前一轮事件循环(Event Loop)的尾部。

    process.nextTick(function () {
        console.log('下一次Event Loop即将开始!');
    });
    //上面代码可以用setTimeout(f,0)改写,效果接近,但是原理不同。
    setTimeout(f,0)是将任务放到下一轮事件循环的头部,因此nextTick会比它先执行。另外,nextTick的效率更高,因为不用检查是否到了指定时间。
1
2
3
4
5
  • 3、process.exit()

  process.exit方法用来退出当前进程。它可以接受一个数值参数,如果参数大于0,表示执行失败;如果等于0表示执行成功。

    if (err) {
        process.exit(1);
    } else {
        process.exit(0);
    }
    //如果不带有参数,exit方法的参数默认为0。
    更安全的方法是使用exitcode属性,指定退出状态,然后再抛出一个错误。
    process.exitCode = 1;
    throw new Error("xx condition failed");
1
2
3
4
5
6
7
8
9
  • 4、process.on()

  process对象部署了EventEmitter接口,可以使用on方法监听各种事件,并指定回调函数。

  process支持的事件还有下面这些。

  • 1)uncaughtException:当前进程抛出一个没有被捕捉的错误时,会触发uncaughtException事件。。

  • 2)data事件:数据输出输入时触发

  • 3)SIGINT事件:接收到系统信号SIGINT时触发,主要是用户按Ctrl + c时触发。

  • 4)SIGTERM事件:系统发出进程终止信号SIGTERM时触发

  • 5)exit事件:进程退出前触发

  • 5、process.kill()

  process.kill方法用来对指定ID的线程发送信号,默认为SIGINT信号。

  process.kill(process.pid, 'SIGTERM'); 上面代码用于杀死当前进程。

  • 6、beforeExit事件

  beforeExit事件在Node清空了Event Loop以后,再没有任何待处理的任务时触发。正常情况下,如果没有任何待处理的任务,Node进程会自动退出,设置beforeExit事件的监听函数以后,就可以提供一个机会,再部署一些任务,使得Node进程不退出。
  beforeExit事件与exit事件的主要区别是,beforeExit的监听函数可以部署异步任务,而exit不行。

  • 7、信号事件

  主要对SIGTERM和SIGINT信号部署监听函数,这两个信号在非Windows平台会导致进程退出,但是只要部署了监听函数,Node进程收到信号后就不会退出。

  • 8、进程的退出码
    0,正常退出
    1,发生未捕获错误
    5V8执行错误
    8,不正确的参数
    128 + 信号值,如果Node接受到退出信号(比如SIGKILLSIGHUP),它的退出码就是128加上信号值。由于128的二进制形式是10000000, 所以退出码的后七位就是信号值。
1
2
3
4
5

# 11、url

  url模块用于生成和解析URL。该模块使用前,必须加载。

 11.1、url.resolve(from, to)

    url.resolve方法用于生成URL。它的第一个参数是基准URL,其余参数依次根据基准URL,生成对应的位置。
    url.resolve('/one/two/three', 'four')
    // '/one/two/four'
    url.resolve('http://example.com/', '/one')
    // 'http://example.com/one'

    url.resolve('http://example.com/one/', 'two')
    // 'http://example.com/one/two'

    url.resolve('http://example.com/one', '/two')
    // 'http://example.com/two'
1
2
3
4
5
6
7
8
9
10
11

# 12、querystring

  querystring模块主要用来解析查询字符串。

 1、querystring.parse()

  用于将一个查询字符串解析为 JavaScript 对象。

    parse方法一共可以接受四个参数。
    str:需要解析的查询字符串
    sep:多个键值对之间的分隔符,默认为&
    eq:键名与键值之间的分隔符,默认为=
    options:配置对象,它有两个属性,decodeURIComponent属性是一个函数,用来将编码后的字符串还原,默认是querystring.unescape(),maxKeys属性指定最多解析多少个属性,默认是1000,如果设为0就表示不限制属性的最大数量。
parse方法也可以用来解析一般的字符串。
    var str = 'name:Sophie;shape:fox;condition:new';
    querystring.parse(str, ';', ':')
    // {
    //   name: 'Sophie',
    //   shape: 'fox',
    //   condition: 'new',
    // }
1
2
3
4
5
6
7
8
9
10
11
12
13

# 13、stream

 1、概念

  "数据流"(stream)是处理系统缓存的一种方式。操作系统采用数据块(chunk)的方式读取数据,每收到一次数据,就存入缓存。Node应用程序有两种缓存的处理方式,第一种是等到所有数据接收完毕,一次性从缓存读取,这就是传统的读取文件的方式;第二种是采用"数据流"的方式,收到一块数据,就读取一块,即在数据还没有接收完成时,就开始处理它。
  第一种方式先将数据全部读入内存,然后处理,优点是符合直觉,流程非常自然,缺点是如果遇到大文件,要花很长时间,才能进入数据处理的步骤。第二种方式每次只读入数据的一小块,像"流水"一样,每当系统读入了一小块数据,就会触发一个事件,发出"新数据块"的信号。应用程序只要监听这个事件,就能掌握数据读取的进展,做出相应处理,这样就提高了程序的性能。

数据流接口最大特点就是通过事件通信,具有readable、writable、drain、data、end、close等事件,既可以读取数据,也可以写入数据。读写数据时,每读入(或写入)一段数据,就会触发一次data事件,全部读取(或写入)完毕,触发end事件。如果发生错误,则触发error事件。

 2、可读数据流

  "可读数据流"用来产生数据。它表示数据的来源,只要一个对象提供"可读数据流",就表示你可以从其中读取数据。

    var Readable = require('stream').Readable;
    var rs = new Readable();
    rs.push('beep ');
    rs.push('boop\n');
    rs.push(null);
    //rs.push(null)中的null,用来告诉rs,数据输入完毕。
    rs.pipe(process.stdout);
1
2
3
4
5
6
7

  "可读数据流"有两种状态:流动态和暂停态。处于流动态时,数据会尽快地从数据源导向用户的程序;处于暂停态时,必须显式调用stream.read()等指令,"可读数据流"才会释放数据。刚刚新建的时候,"可读数据流"处于暂停态。

  三种方法可以让暂停态转为流动态。

  • 1、添加data事件的监听函数
  • 2、调用resume方法
  • 3、调用pipe方法将数据送往一个可写数据流。

  如果转为流动态时,没有data事件的监听函数,也没有pipe方法的目的地,那么数据将遗失。

  以下两种方法可以让流动态转为暂停态。

  • 1、不存在pipe方法的目的地时,调用pause方法
  • 2、存在pipe方法的目的地时,移除所有data事件的监听函数,并且调用unpipe方法,移除所有pipe方法的目的地。

  注意,只移除data事件的监听函数,并不会自动引发数据流进入"暂停态"。另外,存在pipe方法的目的地时,调用pause方法,并不能保证数据流总是处于暂停态,一旦那些目的地发出数据请求,数据流有可能会继续提供数据。每当系统有新的数据,该接口可以监听到data事件,从而回调函数。

    var fs = require('fs');
    var readableStream = fs.createReadStream('file.txt');
    var data = '';

    readableStream.setEncoding('utf8');

    readableStream.on('data', function(chunk) {
        data+=chunk;
    });

    readableStream.on('end', function() {
        console.log(data);
    });
1
2
3
4
5
6
7
8
9
10
11
12
13

  监听readable事件,也可以读到数据。

    var fs = require('fs');
    var readableStream = fs.createReadStream('file.txt');
    var data = '';
    var chunk;
    readableStream.setEncoding('utf8');
    readableStream.on('readable', function() {
        while ((chunk=readableStream.read()) !== null) {
            data += chunk;
        }
    });

    readableStream.on('end', function() {
        console.log(data)
    });
1
2
3
4
5
6
7
8
9
10
11
12
13
14

  readable事件表示系统缓冲之中有可读的数据,使用read方法去读出数据。如果没有数据可读,read方法会返回null。   "可读数据流"除了read方法,还有以下方法。

  • 1、Readable.pause() :暂停数据流。已经存在的数据,也不再触发data事件,数据将保留在缓存之中,此时的数据流称为静态数据流。如果对静态数据流再次调用pause方法,数据流将重新开始流动,但是缓存中现有的数据,不会再触发data事件。

  • 2、Readable.resume():恢复暂停的数据流。

  • 3、readable.unpipe():从管道中移除目的地数据流。如果该方法使用时带有参数,会阻止“可读数据流”进入某个特定的目的地数据流。如果使用时不带有参数,则会移除所有的目的地数据流。

  • 2、1 readable 属性

  一个数据流的readable属性返回一个布尔值。如果数据流是一个仍然打开的可读数据流,就返回true,否则返回false。

  • 2、2 read()

  read方法从系统缓存读取并返回数据。如果读不到数据,则返回null。该方法可以接受一个整数作为参数,表示所要读取数据的数量,然后会返回该数量的数据。如果读不到足够数量的数据,返回null。如果不提供这个参数,默认返回系统缓存之中的所有数据。

    var readable = getReadableStreamSomehow();
    readable.on('readable', function() {
        var chunk;
        while (null !== (chunk = readable.read())) {
            console.log('got %d bytes of data', chunk.length);
        }
    });
    //如果该方法返回一个数据块,那么它就触发了data事件。
1
2
3
4
5
6
7
8
  • 2、3 _read()

  可读数据流的_read方法,可以将数据放入可读数据流。

    var Readable = require('stream').Readable;
    var rs = Readable();
    var c = 97;
    rs._read = function () {
        rs.push(String.fromCharCode(c++));
        if (c > 'z'.charCodeAt(0)) 
        rs.push(null);
    };
    rs.pipe(process.stdout);
1
2
3
4
5
6
7
8
9
  • 2、4 setEncoding()

  调用该方法,会使得数据流返回指定编码的字符串,而不是缓存之中的二进制对象。比如,调用setEncoding('utf8'),数据流会返回UTF-8字符串。

  • 2、5 resume()

  resume方法会使得“可读数据流”继续释放data事件,即转为流动态。

  • 2、6 pause()

  pause方法使得流动态的数据流,停止释放data事件,转而进入暂停态。任何此时已经可以读到的数据,都将停留在系统缓存。

  • 2、7 isPaused()

  该方法返回一个布尔值,表示"可读数据流"被客户端手动暂停(即调用了pause方法),目前还没有调用resume方法。

    var Readable = require('stream').Readable;
    var readable = Readable();

    readable.isPaused() // === false
    readable.pause()
    readable.isPaused() // === true
    readable.resume()
    readable.isPaused() // === false
1
2
3
4
5
6
7
8
  • 2、8 pipe()

  pipe方法是自动传送数据的机制,就像管道一样。它从"可读数据流"读出所有数据,将其写出到指定的目的地。整个过程是自动的。

    src.pipe(dst)
    //pipe方法必须在可读数据流上调用,它的参数必须是可写数据流。
    var fs = require('fs');
    var readableStream = fs.createReadStream('node.md');
    var writableStream = fs.createWriteStream('file2.doc');
    readableStream.pipe(writableStream);
    //pipe方法返回目的地的数据流,因此可以使用链式写法,将多个数据流操作连在一起。
    a.pipe(b).pipe(c).pipe(d)
    // 等同于
    a.pipe(b);
    b.pipe(c);
    c.pipe(d);
    //当来源地的数据流读取完成,默认会调用目的地的end方法,就不再能够写入。对pipe方法传入第二个参数{ end: false },可以让目的地的数据流保持打开。
    reader.pipe(writer, { end: false });
    reader.on('end', function() {
        writer.end('Goodbye\n');
    });
    //上面代码中,目的地数据流默认不会调用end方法,只能手动调用,因此“Goodbye”会被写入。
    //test.js
    process.stdin.pipe(process.stdout);
    node test.js //將在cmd中輸入什麼,就將輸出什麼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  • 2、9 unpipe()

  该方法移除pipe方法指定的数据流目的地。如果没有参数,则移除所有的pipe方法目的地。如果有参数,则移除该参数指定的目的地。如果没有匹配参数的目的地,则不会产生任何效果。

    var fs = require('fs');
    var readable = fs.createReadStream('file.txt');
    var writable = fs.createWriteStream('file1.txt');
    readable.pipe(writable);
    setTimeout(function() {
        console.log('停止写入file.txt');
        readable.unpipe(writable);
        console.log('手动关闭file.txt的写入数据流');
        writable.end();
    }, 1000);
1
2
3
4
5
6
7
8
9
10
  • 2、10 事件
  • 1)readable事件

   readable事件在数据流能够向外提供数据时触发。

    var readable = getReadableStreamSomehow();
    readable.on('readable', function() {
        // there is some data to read now
    });
    //将标准输入的数据读出。
    process.stdin.on('readable', function () {
        var buf = process.stdin.read();
        console.dir(buf);
    });
1
2
3
4
5
6
7
8
9
  • 2)data事件

  对于那些没有显式暂停的数据流,添加data事件监听函数,会将数据流切换到流动态,尽快向外提供数据。

  • 3)end事件

  无法再读取到数据时,会触发end事件。也就是说,只有当前数据被完全读取完,才会触发end事件,比如不停地调用read方法。

  • 4)close

  数据源关闭时,close事件被触发。并不是所有的数据流都支持这个事件。

  • 5)error

  当读取数据发生错误时,error事件被触发。

 3、继承可读数据流接口

  可读数据流又分成两种,一种是 pull 模式,自己拉数据,就好像用吸管吸水,只有你吸了,水才会上来;另一种是 push 模式,数据自动推送给你,就好像水从水龙头自动涌出来。如果监听data事件,那么自动激活 push 模式;如果自己从数据流读取数据,那就是在使用 pull 模式。
  注意,数据流新建以后,默认状态是暂停,只有指定了data事件的回调函数,或者调用了resume方法,数据才会开发发送。

    var fs = require('fs');
    var stream = fs.createReadStream('readme.txt');
    stream.setEncoding('utf8'); 
    stream.pause();
    //显式调用pause方法,会使得readable事件释放一个data事件,否则data监听无效
    var pulledData = '';
    var pushedData = '';
    stream.on('readable', function() {
    var chunk;
    while(chunk = stream.read()) {
            pulledData += chunk;
        }
    });

    stream.on('data', function(chunk) {
        pushedData += chunk;
    });  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

 4、可写数据流

  "可读数据流"用来对外释放数据,"可写数据流"则是用来接收数据。它允许你将数据写入某个目的地。它是数据写入的一种抽象,不同的数据目的地部署了这个接口以后,就可以用统一的方法写入。

  以下是部署了可写数据流的一些场合。

    1、客户端的http requests
    2、服务器的http responses
    3、fs write streams
    4、zlib streams
    5、crypto streams
    6、tcp sockets
    7、child process stdin
    8、process.stdout, process.stderr
1
2
3
4
5
6
7
8

  部署"可写数据流",必须继承stream.Writable,以及实现stream._write方法。下面是一个例子,数据库的写入接口部署“可写数据流”接口。

    var Writable = require('stream').Writable;
    var util = require('util');

    module.exports = DatabaseWriteStream;

    function DatabaseWriteStream(options) {
        if (! (this instanceof DatabaseWriteStream))
            return new DatabaseWriteStream(options);
        if (! options) options = {};
            options.objectMode = true;
            Writable.call(this, options);
    }
    //继承
    util.inherits(DatabaseWriteStream, Writable);

    DatabaseWriteStream.prototype._write = function write(doc, encoding, callback) {
        insertIntoDatabase(JSON.stringify(doc), callback);
    };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

  下面是fs模块的可写数据流的例子。

    var fs = require('fs');
    var readableStream = fs.createReadStream('file1.txt');
    var writableStream = fs.createWriteStream('file2.txt');
    readableStream.setEncoding('utf8');
    readableStream.on('data', function(chunk) {
        writableStream.write(chunk);
    });
1
2
3
4
5
6
7
  • 4.1、 writable属性

  writable属性返回一个布尔值。如果数据流仍然打开,并且可写,就返回true,否则返回false。

  • 4.2、 write()

  write方法用于向"可写数据流"写入数据。它接受两个参数,一个是写入的内容,可以是字符串,也可以是一个stream对象(比如可读数据流)或buffer对象(表示二进制数据),另一个是写入完成后的回调函数,它是可选的。

    s.write(buffer);          // 写入二进制数据
    s.write(string, encoding) // 写入字符串,编码默认为utf-8  
1
2

  write方法返回一个布尔值,表示本次数据是否处理完成。如果返回true,就表示可以写入新的数据了。如果等待写入的数据被缓存了,就返回false,表示此时不能立刻写入新的数据。不过,返回false的情况下,也可以继续传入新的数据等待写入。只是这时,新的数据不会真的写入,只会缓存在内存中。为了避免内存消耗,比较好的做法还是等待该方法返回true,然后再写入。

  • 4.3、 cork(),uncork()

  cork方法可以强制等待写入的数据进入缓存。当调用uncork方法或end方法时,缓存的数据就会吐出。

  • 4.4、 setDefaultEncoding()

  setDefaultEncoding方法用于将写入的数据编码成新的格式。它返回一个布尔值,表示编码是否成功,如果返回false就表示编码失败。

  • 4.5、end()

  end方法用于终止"可写数据流"。该方法可以接受三个参数,全部都是可选参数。第一个参数是最后所要写入的数据,可以是字符串,也可以是stream对象或buffer对象;第二个参数是写入编码;第三个参数是一个回调函数,finish事件发生时,会触发这个回调函数。 调用end方法之后,再写入数据会报错。

  • 4.6、事件
  • 1)drain事件

  writable.write(chunk)返回false以后,当缓存数据全部写入完成,可以继续写入时,会触发drain事件,表示缓存空了。

  • 2) finish事件

  调用end方法时,所有缓存的数据释放,触发finish事件。该事件的回调函数没有参数。

    var fs=require("fs");
    var writer = fs.createWriteStream('example.txt');
    for (var i = 0; i < 100; i ++) {
        writer.write('hello, #' + i + '!\n');
    }
    writer.end('this is the end\n');
    writer.on('finish', function() {
    console.error('all writes are now complete.');
    });
1
2
3
4
5
6
7
8
9
  • 3) pip事件

  "可读数据流"调用pipe方法,将可读数据流写入目的地时,触发该事件。该事件的回调函数,接受发出该事件的"可读数据流"对象作为参数。

    var writer = getWritableStreamSomehow();
    var reader = getReadableStreamSomehow();
    writer.on('pipe', function(src) {
        console.error('something is piping into the writer');
        assert.equal(src, reader);
    });
    reader.pipe(writer);
1
2
3
4
5
6
7
  • 4)unpipe事件

  "可读数据流"调用unpipe方法,将可写数据流移出写入目的地时,触发该事件。 该事件的回调函数,接受发出该事件的"可读数据流"对象作为参数。

  • 5)error事件

  如果写入数据或pipe数据时发生错误,就会触发该事件。该事件的回调函数,接受一个Error对象作为参数。

  • 4.7、错误处理
    var http=require("http"); 
    var onFinished = require('on-finished')
    http.createServer(function (req, res) {
        // set the content headers
        fs.createReadStream('filename.txt')
        .on('error', onerror)
        .pipe(zlib.createGzip())
        .on('error', onerror)
        .pipe(res)

        function onerror(err) {
            console.error(err.stack)
        }
        onFinished(res, function () {
            // make sure the stream is always destroyed
            stream.destroy()
        })
    })  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 14、Net模块和DNS模块

  net模块用于底层的网络通信。 下面是一段简单的监听2000端口的代码。

    var net = require('net');
    var server = net.createServer();
        server.listen(2000, function () { 
            console.log('Listening on port 2000'); 
        });
        server.on('connection', function (stream) {
            console.log('Accepting connection from', stream.remoteAddress);
            stream.on('data', function (data) { 
                stream.write(data); 
            });
            stream.on('end', function (data) { 
                console.log('Connection closed');
            });
    });
1
2
3
4
5
6
7
8
9
10
11
12
13
14

 1、isIP()

  isIP方法用于判断某个字符串是否为IP地址。

 2、DNS模块用于解析域名

  resolve4方法用于IPv4环境,resolve6方法用于IPv6环境,lookup方法在以上两种环境都可以使用,返回IP地址(address)和当前环境(IPv4或IPv6)

# 15、CommonJS规范

 1、概述

  Node 应用由模块组成,采用 CommonJS 模块规范。 每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。 CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。

    var x = 5;
    var addX = function (value) {
    return value + x;
    };
    module.exports.x = x;
    module.exports.addX = addX;
1
2
3
4
5
6
    CommonJS模块的特点如下。
        1、所有代码都运行在模块作用域,不会污染全局作用域。
        2、模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
        3、模块加载的顺序,按照其在代码中出现的顺序。

 2、module对象

  Node内部提供一个Module构建函数。所有模块都是Module的实例。每个模块内部,都有一个module对象,代表当前模块。它有以下属性。

  • 1)module.id 模块的识别符,通常是带有绝对路径的模块文件名。

  • 2)module.filename 模块的文件名,带有绝对路径。

  • 3)module.loaded 返回一个布尔值,表示模块是否已经完成加载。

  • 4)module.parent 返回一个对象,表示调用该模块的模块。module.parent就是调用它的父模块。利用这一点,可以判断当前模块是否为入口脚本。

  • 5)module.children 返回一个数组,表示该模块要用到的其他模块。

  • 6)module.exports 表示模块对外输出的值。

  • 2、1 module.exports属性

  module.exports属性表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取module.exports变量。

  • 2、2 exports变量

  Node为每个模块提供一个exports变量,指向module.exports。这等同在每个模块头部,有一行这样的命令。

    var exports = module.exports;
1

  注意,不能直接将exports变量指向一个值,因为这样等于切断了exports与module.exports的联系。 如果一个模块的对外接口,就是一个单一的值,不能使用exports输出,只能使用module.exports输出。

    module.exports = function (x){ console.log(x);};
1

 3、AMD规范与CommonJS规范的兼容性

  CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD规范则是非同步加载模块,允许指定回调函数。由于Node.js主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以CommonJS规范比较适用。但是,如果是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,因此浏览器端一般采用AMD规范。

# 16、npm包管理器

 1、npm安装模块时,对应的版本可以加上各种限定,主要有以下几种:

  • 1)指定版本:比如1.2.2,遵循“大版本.次要版本.小版本”的格式规定,安装时只安装指定版本。
  • 2)波浪号(tilde)+指定版本:比如~1.2.2,表示安装1.2.x的最新版本(不低于1.2.2),但是不安装1.3.x,也就是说安装时不改变大版本号和次要版本号。
  • 3)插入号(caret)+指定版本:比如ˆ1.2.2,表示安装1.x.x的最新版本(不低于1.2.2),但是不安装2.x.x,也就是说安装时不改变大版本号。需要注意的是,如果大版本号为0,则插入号的行为与波浪号相同,这是因为此时处于开发阶段,即使是次要版本号变动,也可能带来程序的不兼容。
  • 4)latest:安装最新版本。

 2、npm安装:

    $ npm install npm@latest -g
1

 3、npm init

    npm init用来初始化生成一个新的package.json文件。
1

 4、npm set

    npm set用来设置环境变量。
    $ npm set init-author-name 'Your name'
    $ npm set init-author-email 'Your email'
    $ npm set init-author-url 'http://yourdomain.com'
    $ npm set init-license 'MIT'
    
    $ npm set save-exact true
    上面命令设置加入模块时,package.json将记录模块的确切版本,而不是一个可选的版本范围。
1
2
3
4
5
6
7
8

 5、npm config

    npm config set prefix $dir
    //上面的命令将指定的$dir目录,设为模块的全局安装目录。
    npm config set save-prefix ~
    //上面的命令使得npm install --save和npm install --save-dev安装新模块时,允许的版本范围从克拉符号(^)改成波浪号(~),即从允许小版本升级,变成只允许补丁包的升级。
    $ npm config set init.author.name $name
    $ npm config set init.author.email $email
    //上面命令指定使用npm init时,生成的package.json文件的字段默认值。
1
2
3
4
5
6
7

 6、npm info

    //npm info命令可以查看每个模块的具体信息。比如,查看underscore模块的信息。
    npm info underscore
    //上面命令返回一个JavaScript对象,包含了underscore模块的详细信息。这个对象的每个成员,都可以直接从info命令查询。
    npm info underscore description
1
2
3
4

 7、npm search

    npm search命令用于搜索npm仓库,它后面可以跟字符串,也可以跟正则表达式。
    npm search webpack
1
2

 8、npm list

    npm list命令以树型结构列出当前项目安装的所有模块,以及它们依赖的模块。
    加上global参数,会列出全局安装的模块。
    npm list -global
1
2
3

 9、npm install

    //npm install也支持直接输入Github代码库地址。
    $ npm install git://github.com/package/path.git
    $ npm install git://github.com/package/path.git#0.1.0
//如果你希望,一个模块不管是否安装过,npm 都要强制重新安装,可以使用-f或--force参数。
    $ npm install <packageName> --force
1
2
3
4
5
  • 1)install命令总是安装模块的最新版本,如果要安装模块的特定版本,可以在模块名后面加上@和版本号。
    $ npm install sax@latest
    $ npm install sax@0.1.1
    $ npm install sax@">=0.1.0 <0.2.0"
1
2
3
  • 2)如果使用--save-exact参数,会在package.json文件指定安装模块的确切版本。
    $ npm install readable-stream --save --save-exact
1
  • 3)npm install默认会安装dependencies字段和devDependencies字段中的所有模块,如果使用--production参数,可以只安装dependencies字段的模块。
                $ npm install --production
                # 或者
                $ NODE_ENV=production npm install
1
2
3

 10、npm run

  • 1)npm run命令会自动在环境变量$PATH添加node_modules/.bin目录,所以scripts字段里面调用命令时不用加上路径,这就避免了全局安装NPM模块。
  • 2)npm run如果不加任何参数,直接运行,会列出package.json里面所有可以执行的脚本命令。
    "build": "npm run build-js && npm run build-css"
    //上面的写法是先运行npm run build-js,然后再运行npm run build-css,两个命令中间用&&连接。如果希望两个命令同时平行执行,它们中间可以用&连接。
1
2

  1)参数

    "scripts": {
        "test": "mocha test/"
    }
    //上面代码指定npm test,实际运行mocha test/。如果要通过npm test命令,将参数传到mocha,则参数之前要加上两个连词线。
    $ npm run test -- anothertest.js
    # 等同于
    $ mocha test/ anothertest.js
    //上面命令表示,mocha要运行所有test子目录的测试脚本,以及另外一个测试脚本anothertest.js。
    //npm run本身有一个参数-s,表示关闭npm本身的输出,只输出脚本产生的结果。
    // 输出npm命令头
    $ npm run test
    // 不输出npm命令头
    $ npm run -s test
1
2
3
4
5
6
7
8
9
10
11
12
13

  2) npm-run-all

    $ npm install npm-run-all --save-dev
    //继发执行
    $ npm-run-all build:html build:js
    //等同于
    $ npm run build:html && npm run build:js

    //并行执行
    $ npm-run-all --parallel watch:html watch:js
    //等同于
    $ npm run watch:html & npm run watch:js

    //混合执行
    $ npm-run-all clean lint --parallel watch:html watch:js
    //等同于
    $ npm-run-all clean lint
    $ npm-run-all --parallel watch:html watch:js

    //通配符
    $ npm-run-all --parallel watch:*
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  • 3)start命令   如果start脚本没有配置,npm start命令默认执行下面的脚本,前提是模块的根目录存在一个server.js文件。 $ node server.js
  • 4)dev脚本命令   规定开发阶段所要做的处理,比如构建网页资源。
  • 5)serve脚本命令用于启动服务。
    "serve": "live-server dist/ --port=9090"
    //上面命令启动服务,用的是live-server模块,将服务启动在9090端口,展示dist子目录。

    live-server模块有三个功能。
        1)启动一个HTTP服务器,展示指定目录的index.html文件,通过该文件加载各种网络资源,这是file://协议做不到的。
        2)添加自动刷新功能。只要指定目录之中,文件有任何变化,它就会刷新页面。
        3)npm run serve命令执行以后,自动打开浏览器。
1
2
3
4
5
6
7
  • 6)pre- 和 post- 脚本

   以npm run lint为例,执行这条命令之前,npm会先查看有没有定义prelint和postlint两个钩子,如果有的话,就会先执行npm run prelint,然后执行npm run lint,最后执行npm run postlint。

 11、内部变量

  scripts字段可以使用一些内部变量,主要是package.json的各种字段。 比如,package.json的内容是{"name":"foo", "version":"1.2.5"},那么变量npm_package_name的值是foo,变量npm_package_version的值是1.2.5。

    {
    "scripts":{
        "bundle": "mkdir -p build/$npm_package_version/"
        }
    }
    //-p 确保目录名称存在,如果目录不存在的就新创建一个。
    //config字段也可以用于设置内部字段。
    "name": "fooproject",
    "config": {
        "reporter": "xunit"
    },
    "scripts": {
        "test": "mocha test/ --reporter $npm_package_config_reporter"
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • 1)通配符
    * 匹配0个或多个字符
    ? 匹配1个字符
    [...] 匹配某个范围的字符。如果该范围的第一个字符是!^,则匹配不在该范围的字符。
    !(pattern|pattern|pattern) 匹配任何不符合给定的模式
    ?(pattern|pattern|pattern) 匹配0个或1个给定的模式
    +(pattern|pattern|pattern) 匹配1个或多个给定的模式
    *(a|b|c) 匹配0个或多个给定的模式
    @(pattern|pat*|pat?erN) 只匹配给定模式之一
    ** 如果出现在路径部分,表示0个或多个子目录。
1
2
3
4
5
6
7
8
9

 12、npm link

  npm link就能起到这个作用,会自动建立这个符号链接。

    1、在模块目录(src/myModule)下运行npm link命令。
        npm link 
    //会在NPM的全局模块目录内,生成一个符号链接文件,该文件的名字就是package.json文件中指定的模块名。
    //这个时候,已经可以全局调用myModule模块了。但是,如果我们要让这个模块安装在项目内,还要进行下面的步骤。
    2、切换到项目目录,再次运行npm link命令,并指定模块名。
        npm link studynotes
    3、然后,就可以在你的项目中,加载该模块了。
        var myModule = require('studynotes');
    4、如果你的项目不再需要该模块,可以在项目目录内使用
    npm unlink命令,删除符号链接。
        npm unlink studynotes//删除固定模块的符号链接
        npm unlink 删除全局符号链接
1
2
3
4
5
6
7
8
9
10
11
12

 13、npm bin

  npm bin命令显示相对于当前目录的,Node模块的可执行脚本所在的目录(即.bin目录)。

 14、npm adduser

  npm adduser用于在npmjs.com注册一个用户。

 15、npm publish

  npm publish用于将当前模块发布到npmjs.com。执行之前,需要向npmjs.com申请用户名。

    $ npm adduser
1

  如果已经注册过,就使用下面的命令登录。

    $ npm login
1

  登录以后,就可以使用npm publish命令发布。

    $ npm publish
1

  如果当前模块是一个beta版,比如1.3.1-beta.3,那么发布的时候需要使用tag参数,将其发布到指定标签,默认的发布标签是latest。

    $ npm publish --tag beta
1

  如果你的模块是用ES6写的,那么发布的时候,最好转成ES5。首先,需要安装Babel。

    npm install --save-dev babel-cli@6 babel-preset-es2015@6
    "scripts": {
        "build": "babel source --presets babel-preset-es2015 --out-dir distribution",
        "prepublish": "npm run build"
    } 
1
2
3
4
5

  运行上面的脚本,会将source目录里面的ES6源码文件,转为distribution目录里面的ES5源码文件。然后,在项目根目录下面创建两个文件.npmignore和.gitignore,分别写入以下内容。

    // .npmignore
    source
    // .gitignore
    node_modules
    distribution 
1
2
3
4
5

 16、npm deprecate

  废弃某个版本的模块。

    $ npm deprecate my-thing@"< 0.2.3" "critical bug fixed in v0.2.3"
    //运行上面的命令以后,小于0.2.3版本的模块的package.json都会写入一行警告,用户安装这些版本时,这行警告就会在命令行显示。
1
2

 17、npm owner

  模块的维护者可以发布新版本。npm owner命令用于管理模块的维护者。

    //列出指定模块的维护者
    $ npm owner ls <package name>
    //新增维护者
    $ npm owner add <user> <package name>
    //删除维护者
    $ npm owner rm <user> <package name>
1
2
3
4
5
6

 18、npm outdated

  npm outdated命令检查当前项目所依赖的模块,是否已经有新版本。 它会输出当前版本(current version)、应当安装的版本(wanted version)和最新发布的版本(latest version)。

# 第三节 其它

nrm:镜像源管理工具工具

  • 1、nrm ls 查看当前镜像源列表。
  • 2、nrm add key value 添加镜像源
  • 3、nrm use key 选择使用镜像源

多文件上传包——formidable

const form=require("formidable")
1
Last Updated: 6/4/2020, 10:33:20 AM