nodejs基础

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。V8引擎速度非常快,不用担心多线程,锁,并行计算产生的问题。
如果你编码不认真,java会给你丘,而nodejs会给你很多坑,使用WebStorm开发有的错误会报错,有的错误则会运行通过,产生意料之外的行为。

node擅长处理io密集型业务,在处理CPU密集计算的时候可选择以下方案:
1.node业务逻辑都以单线程进行处理,那如果其中包括耗时的复杂计算,可以以rpc 的方式让其他编译型语言处理,node 只负责搬运。 也就是说node负责接受请求、执行io、发送回复,其他逻辑由擅长计算的语言处理。
2.业务逻辑交给node,便于快速开发。其他内容交给其他语言,或者直接交给c++,用node-gyp编译一下。

同步一般只在基础框架的启动时使用,用来加载配置文件、初始化程序等。

Node.js Manual & Documentation
http://nodejs.cn/api/
http://shouce.qdfuns.com/nodejs/index.html
https://www.cnblogs.com/chaojidan/p/4126894.html
http://www.runoob.com/nodejs/nodejs-tutorial.html

node的构架
主要分为三层:应用app >> V8及node内置架构 >> 操作系统。
V8是node运行的环境,可以理解为node虚拟机。node内置架构又可分为三层: 核心模块(javascript实现)>> c++绑定 >> libuv + CAes + http.

事件循环

Node采用的是单线程的处理机制(所有的I/O请求都采用非阻塞的工作方式),至少从Node.js开发者的角度是这样的。而在底层,Node.js借助libuv来作为抽象封装层,从而屏蔽不同操作系统的差异,Node可以借助libuv来实现线程。Libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个事件循环,以异步的方式将任务的执行结果返回给V8引擎。
event loop其实就是一个事件队列,先加入先执行,执行完一次队列再次循环遍历看有没有新事件加入队列。执行中的叫IO events, setImmediate是在当前队列立即执行,setTimout/setInterval是把执行定时到下一个队列,process.nextTick是在当前执行完下次遍历前执行。所以总体顺序是: IO events >> setImmediate >> setTimeout/setInterval >> process.nextTick

node核心模块
有EventEmitter, Stream, FS, Net和全局对象
全局对象有process, console, Buffer和exports

EventEmitter

EventEmitter是node中一个实现观察者模式的类,主要功能是监听和发射消息,用于处理多模块交互问题。EventEmitter典型应用有:1) 模块间传递消息 2) 回调函数内外传递消息 3) 处理流数据,因为流是在EventEmitter基础上实现的. 4) 观察者模式发射触发机制相关应用。
实现一个EventEmitter分三步:定义一个子类,调用构造函数,继承EventEmitter

    var util = require('util');
    var EventEmitter = require('events').EventEmitter;

    function MyEmitter() {
        EventEmitter.call(this);
    } // 构造函数

    util.inherits(MyEmitter, EventEmitter); // 继承

    var em = new MyEmitter();
    em.on('hello', function(data) {
        console.log('收到事件hello的数据:', data);
    }); // 接收事件,并打印到控制台
    em.emit('hello', 'EventEmitter传递消息真方便!');

捕获EventEmitter的错误事件,监听error事件即可。如果有多个EventEmitter,可以用domain来统一处理错误事件。

    var domain = require('domain');
    var myDomain = domain.create();
    myDomain.on('error', function(err){
        console.log('domain接收到的错误事件:', err);
    }); // 接收事件并打印
    //run中隐式的绑定事件分发器
    myDomain.run(function(){
        var emitter1 = new MyEmitter();
        emitter1.emit('error', '错误事件来自emitter1');
        emitter2 = new MyEmitter();
        emitter2.emit('error', '错误事件来自emitter2');
    });

EventEmitter中的newListenser事件可以用来做事件机制的反射,事件管理等。当任何on事件添加到EventEmitter时,就会触发newListener事件。

定时功能
setTimeout/clearTimeout、setInterval/clearInterval、setImmediate/clearImmediate、process.nextTick

Stream
stream是基于事件EventEmitter的数据管理模式。
Stream典型应用有:文件,网络,数据转换,音频视频等。

调节node执行单元的内存大小,用–max-old-space-size 和 –max-new-space-size 来设置 v8 使用内存的上限。

启动调试node程序:node –debug app.js 和 node-inspector

风格

错误优先的回调函数(Error-First Callback)同时返回错误和数据。第一个参数返回错误,并且验证它是否出错;其他参数返回数据。错误优先的回调函数用于传递错误和数据。第一个参数始终应该是一个错误对象, 用于检查程序是否发生了错误。其余的参数用于传递数据。

避免回调地狱

模块化:将回调函数分割为独立的函数。
流程控制库: 例如async
generators结合Promise模块:
Q模块:
co库:使用yield
async/await

流程控制库 async

安装async:npm install async –save,其中–save参数会在工程package.json中dependencies中增加一条依赖。
async主要实现了三个部分的流程控制功能:
集合: Collections
流程控制: Control Flow
工具类: Utils
async不仅适用在node.js里,浏览器中也可以使用。

Nodejs异步流程控制Async

Async提供了很多针对集合的函数,简化对集合的异步操作步骤
https://my.oschina.net/chinacaptain/blog/469810

常用的流程控制:
parallel 并行
series 串行,保证顺序,不依赖
waterfall 依赖串行、瀑布流,在流程之间需要传递结果时使用。
auto 并行加依赖串行
apply 给函数预置第一个参数,然后返回一个新的函数。

async.series({
	one: function(callback){
		callback(null, 1);
	},
	two: function(callback){
		callback(null, 2);
	}
},function(err, results) {
	console.log(results);
});
或者
async.series([
            function(callback){
                callback(null, 1);
            },
            function(callback){
                callback(null, 2);
            }
        ],function(err, results) {
            console.log(results);
        });

async对数据的操作:
async.map 异步执行多个数组,返回结果数组;async.filter异步过滤多个数组,返回结果数组。

Promise

nodejs标准的回调模式在我们处理请求时需要同时提供回调函数,如果有很多个回调就会形成callback瀑布级嵌套回调,或者称为回调陷阱。 Promise能够解决这种问题,允许低层代码创建一个request然后返回一个对象,其代表着未完成的操作,让调用者去决定应该加入什么回调。promise是一个异步编程的抽象,它是一个返回值或抛出exception的代理对象,一般promise对象都有一个then方法,这个then方法是我们如何获得返回值(成功实现承诺的结果值,称为fulfillment)或抛出exception(拒绝承诺的理由,称为rejection),then是用两个可选的回调作为参数,我们可以称为onFulfilled和OnRejected。.then()总是返回一个新的promise,被.then()返回的promise是一个新的promise,它不同于那些.then()被调用的promise,最后调用.then()可能代表失败,那么如果你不捕获这种失败,那么容易导致你的错误exception消失。

Promise.reduce 方法才是一个顺序执行 Promise 的方法,一些顺序执行的方法,比如 Promise.mapSeries 和 Promise.each 都是基于 Promise.reduce 实现的,这里的 reduce 和 Array.reduce 一样。

Promise 函数依靠链式then操作的特性和catch exception 的函数,可以很好的避免函数的callback hell。但还是需要把各个逻辑分别封装在不同的then() 函数中,每一个函数都有自己独特的作用域。如果要共用某个常量或是变量还要把它定义在函数外,Generator 函数最早实现了异步函数同步化的功能。

可以使用 process.on(‘unhandledRejection’,err) 这个工具,它会打印出所有未处理的 reject 状态的 Promise。

co库

借着ES6的Generator迭代器,TJ大神写出了co 库最早实现异步编程同步化的功能。通过co(function *(){})可以使函数内部通过迭代器来控制。而co则是充当了启动器的角色。

async/await

ES7的async/await的出现为实现异步函数操作提供了另一种方式,await 关键字的作用与generator 函数的yield 作用相类似。至此Nodejs无缝链接async函数,Nodejs异步的难题才算是取得了里程碑式的进展。

环境变量 get-env

#app.js中代码:
app.env = require('get-env')({
  init_env1:'init_env1',
  init_env2:'init_env2',
});

#根据以上代码,在命令行中调用时有效的NODE_ENV值有:dev,prod,init_env1,init_env2
export NODE_ENV=init_env1
node app.js 

传递参数

例如:node const-generate.js cx-kqy.txt
process.argv的值相当于是把整个命令按空格分割成字符串数组。

var arguments = process.argv.splice(2);
console.log('所传递的参数是:', arguments);
process.argv.forEach(function (val, index, array) {
    console.log(index + ': ' + val);
});

node端的缓存策略

如果缓存数据量不大,不需要多进程共享,可以node-cache方案或自己实现。如果需要多node.js进程共享并持久化,可以考虑用redis来实现,Redis的优势在于能够供多进程共享,有完善的备份和恢复机制。

child-process

node是异步非阻塞的,这对高并发非常有效。还有其它一些常用需求,比如和操作系统shell命令交互、调用可执行文件、创建子进程进行阻塞式访问或高CPU计算等,child-process就是为满足这些需求而生。child-process顾名思义就是把node阻塞的工作交给子进程去做。

    var cp = require('child_process');
    var child = cp.spawn('echo', ['你好', "钩子"]); // 执行命令
    child.stdout.pipe(process.stdout); // child.stdout是输入流,process.stdout是输出流,这句的意思是将子进程的输出作为当前程序的输入流,然后重定向到当前程序的标准输出,即控制台。

exec、execFile、spawn和fork:exec可以用操作系统原生的方式执行各种命令,如管道 cat ab.txt | grep hello; execFile是执行一个文件; spawn是流式和操作系统进行交互; fork是两个node程序(javascript)之间时行交互。
用fork两个node程序之间交互,原理是子程序用process.on、process.send,父程序里用child.on、child.send进行交互.

    1) fork-parent.js
    var cp = require('child_process');
    var child = cp.fork('./fork-child.js');
    child.on('message', function(msg){
        console.log('老爸从儿子接受到数据:', msg);
    });
    child.send('我是你爸爸,送关怀来了!');

    2) fork-child.js
    process.on('message', function(msg){
        console.log("儿子从老爸接收到的数据:", msg);
        process.send("我不要关怀,我要银民币!");
    });

Node.js模块

Node.js模块分为核心模块和文件模块;
核心模块是Node.js标准API中提供的模块,可以直接通过require获取;文件模块是存储为单独的文件的模块,可以是javascript代码、Json或编译好的C/C++代码。
核心模块拥有最高的加载优先级,如果有模块与其明明冲突,Node.js总是加载核心模块;
文件模块如果不显式指定文件模块扩展名,则会按照.js、.json、.node的顺序加上扩展名;
文件模块的加载有两种方式,一种是按路径加载,一种是查找node_modules文件夹;
文件模块按路径加载又分为按相对路径加载和按绝对路径加载两种;

加载顺序:
优先加载核心模块;
以路径形式加载文件模块:
① 如果显示指定文件模块扩展名,则在该路径下查找该模块是否存在;
② 如果未显示指定文件模块扩展名,则在该路径下,依次查找以.js、.json、.node为扩展名的模块是否存在;
既不是核心模块,又不是以路径形式表示的模块,则首先在当前目录的node_modules目录中查找该模块是否存在,若不存在则继续在其父目录的node_modules目录中查找,反复执行这一过程直到根目录为止。

发表评论