深入浅出nodejs笔记
模块机制
-
发布npm包(commonJs规范)
npm adduser 绑定npm账号 npm publish . 上传发布npm包 npm owner ls <package name> npm owner add <user> <package name> npm owner rm <user> <package name> -
兼容CMD和AMD规范
;(function() { //检测上下文环境是否为AMD或者CMD var hasDefine = typeof define === 'function', //检查上下文环境是否为node hasExports = typeof module !== 'undefined' && module.exports; if( hasDefine) { // AMD 环境或者CMD 环境 define(definition); } else if(hasExports) { // 定义为普通node模块 modules.exports = definition(); } else { // 将模块指向结果挂载在window变量中,在浏览器this执行window对象 this[name] = definition(); } }('hello', function() { var hello = function() { return hello; } }));
异步io
-
事件循环、观察者、请求对象 、I/O线程池这四者共同构成了Node异步I/O模
-
非 I/O的异步api
-
定时器(setTimeout、setInterval)
-
process.nextTick(异步立即执行)
process.nextTick = function(callback) { if (process._exiting) return; if (tickDepth >= process.maxTickDepth) { maxTickWarn(); } var tock = { callback:callback }; if(process.domain) { tock.domain = process.domain; } nexTickQueue.push(tock); if (nextTickQueue.length) { process._needTickCaallback(); } } -
setImmediate()
// 加入nextTick()的回调函数 process.nextTick(function () { console.log('nextTick执行1'); }); process.nextTick(function () { console.log('nextTick执行2'); }); // 加入setImmediate()的回调函数 setImmediate(function () { console.log('setImmediate执行1'); // 进入循环 process.nextTick(function () { console.log('强势势入'); }); }); setImmediate(function () { console.log('setImmediate执行2'); }); console.log('正常执行'); // 正常执行 // nextTick延迟执行1 // nextTick延迟执行2 // setImmediate延迟执行1 // 强势势入 // setImmediate延迟执行2process.nextTick()中的回调函数执行的优先级要高于setImmediate()。 这里的原因在于事件循环对观察者的检查是有先后顺序的,process.nextTick()属于hide 观察者,setImmediate() 属于check观察者。在每一个轮询检查中,idle观察者先于I/O观察者,I/O观察者先于check观察者。
process.nextTick() 在每轮循环中会将数组中的回调函数全部执行完,而setImmediate() 在每轮循环中执行链表中的一个回调函数。
-
异步编程
-
异步编程的难点
编写异步方法的原则:
- 必须执行调用者传入的回调函数
- 正确传递回异常供调用者判断
-
异步编程的解决方案
-
事件发布订阅/订阅模式
-
Promise/Deferred 模式
-
流程控制库
Promise基础实现 var Promise = function() { EventEmitteer.call(this); } util.inherits(Promise, EventEmitter); Promise.prototype.then = function(fulfilledHanler, errorHandler, progressHandler) { if(typeof fulfilledHanler === 'function') { // 利用once方法,保证成功回调只有一次 this.once('success', fulfilledHanler); } if(typeof errorHandler === 'function') { // 利用once方法,保证成功回调只有一次 this.once('error', errorHandler); } if(typeof progressHandler === 'function') { this.on('progress', progressHandler); } return this; } // 触发执行回调过程 var Deferred = function() { this.state = 'unfulfilled'; this.pormise = new Promise(); } Deferred.prototype.resolve = function (obj) { this.state = 'fulfilled'; this.promise.$emit('success', obj); } Deferred.prototype.reject = function (obj) { this.state = 'failed'; this.promise.$emit('error', obj); } Deferred.prototype.progress = function (obj) { this.promise.$emit('progress', obj); }
-
-
Promise中的多异步操作(Promise.all)
-
支持序列化调用的Promise(链式调用)
-
异步并发控制
-
bagpipe的解决方案
-
async的解决方案
-
内存控制
-
回收机制
- 分代机制(新生代和老生代)
- Scavenge算法
-
缓存方法(队列先进先出)内存当缓存使用
-
简单的算法实现
// 简单的内存缓存的方法,用于优化内存(斐波那契数列可使用全递归) var LimitTableMap = function(limit) { this.limit = limit || 10; this.map = {}; this.keys = []; } var hasOwnProperty = Object.prototype.hasOwnProperty; LimitTableMap.prototype.set = function(key,value) { var map = this.map; var keys = this.keys; if(!hasOwnProperty.call(map,key)) { if(keys.length === this.limit) { var firstKey = keys.unshift(); delete map[firstKey]; } } map[key] = value; } LimitTableMap.prototype.get = function(key){ return this.map[key]; } module.exprot = LimitTableMap; -
模块机制实现
(function(exports,require,module,_filename,_dirname) { var local = '局部变量'; exports.get = function() { return local; }; }()); -
缓存使用方法
采用进程外部的缓存, 进程自身不存储状态。外部的缓存软件有着良好的缓存过期淘汰策略以及自身的内存管理,不影响Node进程的性能,解决两个主要问题
-
将缓存转移至外部,减少常驻内存的数量,让垃圾回收更高效。
-
进程之间可以共享缓存
使用方案: Redis,MemCached
-
-
理解BUffer
-
大Buffer文件的拼接
-
BUffer.concat 的实现
// 小BUffer对象复制为大Buffer对象 Buffer.concat = function(list, length) { if(!Array.isArray(list)) { throw newError('Usagr:Buffer.concat(list,[length])'); } if(list.length === 0) { return new Buffer(0); } else if(list.length === 1) { return list[0]; } if(typeof length !== 'number') { length = 0; for(var i = 0; i < list.length; i++) { var buf = list[i]; length += buf.length; } } var buffer = new Buffer(length); var pos = 0; for(var i = 0; i < list.length; i++) { var buf = list[i]; buf.copy(buffer,pos); pos += buf.length; } return buffer; }
-
网络编程
-
构建TCP服务(传输控制协议)
var net = require('net'); var server = net.createServer(function(socket){ socket.write('Echo server\n\r'); socket.pipe(socket); }) server.listen(137,'127.0.0.1'); -
构建UDP服务(用户数据包协议)
// 服务端编程 var dgram = require('dgram'); var server = dgram.createSocket('udp4'); server.on('message', function(msg,rinfo) { console.log('server got:' + msg + 'from' + rinfo.address + ':' + rion.port); }); server.on('listening', function() { var address = server.address(); console.log('server listening' + address.address + ':' + address.port); }); server.bind(41234); -
构建http服务
- 服务端和客户端编程
-
构建webSocket服务
var socket = new WebSocket('ws://127.0.0.1/updates'); socket.onopen = function() { setInterval(function(){ if(socket.bufferedAnount == 0) { socket.send(getupdateData()); } }, 50) } socket.onmessage = function(event) { // TODO: event.data } -
网络安全
-
TLS/SSL协议
// 公钥私钥校验 // CA协议,安全证书 -
构建https服务
//https 服务端 var https = require('https'); var fs = require('fs'); var options = { key: fs.readFileSync('./kyes/server.key), cart: fs.readFileSync('./keys/server.crt') } https.createServer(option, function(req,res){ res.writeHead(200); res.end('hello world\n); }).listen(8000); // https客户端 var option = { hostname:'localhost', port: 8000, path: '/', methods: 'GET', key: fs.readFileSync('./keys/client.key'), cart: fs.readFileSync('./keys/client.crt'), ca:[fs.readFileSync('./keys/ca.crt')] } option.agent = new https.Agent(options); var req = https.request(options, function(){ res.setEncoding('utf-8'); res.on('data',function(d) { console.log(d); }); })
-
构建Web应用
-
构建具体业务的需求(req对象)
-
请求方法的判断(req.method)
-
URL的路径解析 (req.url)
-
URL中查询字符串的解析
// query方式传输数据的获取 var url = require('url'); var query = url.parse(req.url, true).query; -
Cookie的解析(req.headers.cookie)
// cookie的使用过程 1. 服务器向客户端发送cookie 2. 浏览器将cookie保存 3. 之后每次浏览器都将cookie发向服务端 var parseCookie = function(cookie) { var cookies = {}; if(!cookie) { return cookies; } var list = cookie.split(';'); for(var i = 0; i < list.length; i++) { var pair = list[i].split('='); cookies[pair[0].trim()] = pair[1]; } return cookies; } Set-Cookie: name=value; Path=/; Expires=Sun, 23-Apr-23 09:01:35 GMT; Domain=.domain.com; //反向格式化cookie的内容 var serialize = function(name,value, opt) { var pairs = [name + '=' + encode(val)]; opt = opt || {}; if(opt.maxAge) {pairs.push('Max-Age=' + opt.maxAge)}; if(opt.domain) {pairs.push('Domain=' + opt.domain)}; if(opt.path) {pairs.push('Path=' + opt.path)}; if(opt.expires) {pairs.push('Expires=' + opt.expires.toUTCString())}; if(opt.httpOnly) {pairs.push(opt.httpOnly)}; if(opt.secure) {pairs.push(secure)}; return pairs.join('; '); } // 客户端可修改cookie -
Basic认证
-
表单数据的解析
-
任意根式文件的上传处理
-
-
session的使用
-
基于cookie来实现用户和数据的映射
// 生成session var sessions = {}; var key = 'session_id'; var EXPIRES = 20*60*1000; var genrate = function() { var session = {}; session.id = (new Date()).getTime() + Math.random(); session.cookie = { expire: (new Date()).getTime + EXPIRES; } sessions[session.id] = session; return session; } // 请求到来时检查cookie中的口令是否与服务端的数据,如果过期重新生成,响应给客户端 var writeHead = res.writeHead; res.writeHead = function() { var cookies = res.getHeader('Set-Cookie'); var session = serialize('Set-Cookie', req.session.id); cookies = Array.isArray('cookies') ? cookies.concat(session) : [cookies,session]; res.setHeader('Set-Cookie', cookies); return writeHead.apply(this, arguments); } -
通过查询字符串来实现浏览器和服务端数据的对应
// 判断url中携带的查询字符串 // 与以上不同点 var getURL = function(url,key,value) { var obj = url.parse(_url,true); obj.query[key] = value; return url.format(obj); } function(res,req) { var redirect = function() { res.setHeader('Location', url); res.writeHeader(302); res.end(); } var id = res.query[key]; if(!id) { var session = generate(); redirect(getURL(req.url,key,session.key)); } else { var session = session[id]; if(session) { if(session.cookie.expire > (new Date()).getTime()) { // 更新超时时间 session.cookie.expire = (new Date()).getTime() + EXPIRE; req.session = session; handle(req,res); } else { // 如果超时了,删除旧的数据,并重新生成 delete session[id]; var session = generate(); redirect(getURL(req.url,key,session.id)); } } else { // 如果session过期或者口令不对,重新生成session var session = generate(); redirect(getURL(req.url, key,session.id)); } } } // 缺点:危险性较大,伪造用户身份较容易
-
-
缓存的使用
-
添加Expires或Cache-Control 到报文头部
-
配置Etags
-
让Ajax可缓存
-
-
Basic认证
页面需要验证,会检查请求报文头中的Authorization字段的内容
var encode = function(username, password) { return new Buffer(username + ':' + password).toString(); } // 校验认证信息 function(req,res) { var auth = req.headers['Authorization'] || ''; var paths = auth.split(' '); var method = parts[0] || ''; // Basic var encoded = parts[1] || ''; var decoded = new Buffer(enocde, 'base64').toString('urf-8').split(':'); var user = decoded[0]; var password = decoded[1]; if(!check(user,password)) { res.setHeader('WWW-Authenticate', 'Basic realm='SecUre Area'; res.writeHead(401); res.end(); } else { handle(req, res); } } -
数据上传
-
数据上传类型
// Content-Type 类型 // 表单 application/x-WWW-form-Urlencoded var handle = function(req,res) { if(req.headers['content-type'] === 'application/x-www-form-Urlencoded') { req.body = queryString.parse(req.rawBody); } } // JSON 类型 application/json // xml格式 application/xml // 附件上传 mutipart/form-data(diveintonode.js) 基于流式处理解析报文,将接受到的文件写入临时文件库
-
-
路由解析
- 文件路径型
- MVC模式(手工映射和自然映射)
- RESTful
-
中间件
-
尾递归调用处理
//querystring、cookie、session中间件的封装 app.use = function(path) { if(typeof path === 'string') { handle = { path: pathRegexp(path), stack: Array.prototype.slice.call(arguments,1); } } else { handle = { path:'/', stack: Array.prototype.slice.call(argements,0); } } } // 持续匹配支持,后续中间件逻辑 var match = function(pathname,routes) { var stacks = []; for(var i = 0; i < routes.length; i++) { var route = routes[i]; var reg = route.path.regexp; var matched = reg.exec(pathname); if(matched) { stacks = stacks.concat[route.stack]; } } } // 修改持续分发的过程 -
异常处理
异常处理无法再异步执行捕获,next()方法传出。
var handle500 = function(err, req, res, stack) { // 选取异常中间件 stack = stack.filter(function(middleware){ return middleware.length === 4; }); var next = function(){ // 取出中间件执行 var middleware = stack.shift(); if(middleware){ //传递 异常对象 middleware(err, req,res,next); } } // 启动执行 next(); } -
中间件与性能
- 编写高效的中间件
- 合理利用路由,避免不必要的中间件执行(精确路由匹配规则)
-
页面渲染(实现一个render方法)
var cache = {}; var VIEW_FOLDER = '/path/to/wwwroot/views'; res.render = function(viewname,data) { if(!cache[viewname]) { var text; try { text = fs.readfileSync(path.join(VIEW_FOLDER,viewname),'utf-8'); } catch(e) { res.writeHead(500,{content-Type:'text/html'}); res.end('模板文件错误'); return; } cache[viewname] = compile[text]; } var compiled = cache[viewname]; res.writeHead(200,{'content-Type': 'text-html'}); var html = compiled(data); res.end(html); } // 子模板替换 include关键字 // 布局视图 layout继承 // 服务端骨架屏渲染 + 动态渲染数据
玩转进程
-
句柄传递
-
children_process的使用
-
cluster模块
var cluster = require('cluster'); var http = require('http'); var numCPUs = require('os').cpus().length; if(cluster.isMaster) { for(var i = 0; i < numCPUs; i++) { cluster.forks(); } cluster.on('exit', function(worker,code,singnal) { console.log('worker' + worker.process.pid + ' died'); }) } else { //workers can share any tcp connection http.createServer(function(req,res) { res.writeHead(200); res.end('hello world\n'); }).listen(8000); } // 进程判断标准,环境变量中是否有NODE_UNIQUE_ID cluster.isWorker = ('NODE_UNIQUE_ID' in process.env); cluster.isMaster = (cluster.isWorker === false);
测试
-
单元测试介绍
- 测试风格分类(TDD 测试驱动开发 、BDD行为驱动开发)
-
压力测试
-
测试数据与业务测试数据转换
// QPS换算公式 QPS = PV/10h
产品化
监控报警的实现
var nodemailer = require('nodemailer');
// 建立一个SMTP连接
var smtpTransport = nodemailer.createTransport('SMTP',{
service: 'Gmail',
auth: {
user: 'gmial.user@gmail.com',
pass: 'Userpass'
}
})
// 邮件选项
var mailOptions = {
form:'', //发件人邮件地址
to: '', //收件人地址
subject: '', //标题
text: 'Hello world', //纯文本内容
html: '' //html 内容
}
// 发送邮件
smtpTransport.sendMail(mailOptions, function(err, response){
if(err) {
console.log(err);
} else {
console.log('Message send: ' + response.message);
}
})
附录内容
安装node
// Linix 系统下安装Node
// 源代码安装
Node编码规范
-
空格与样式
- 缩进(使用两个空格,非tab) - 变量声明(上下文污染) - 空格(操作符前后加空格) - 单双引号使用(字符串使用单引号,JSON中使用双引号) - 大括号的位置(无需另起一行) - 逗号(前后空格) - 分号(表达式行尾添加分号) -
命名规范
- 变量命名 1. 变量和方法:使用小驼峰 2. 类命名:使用大驼峰,首字符大写 3. 常量命名:所有字母均大写,使用下划线分割 4. 文件命名:使用小写,下划线分割 5. 包命名:简短有意义 -
比较操作
- 使用全等代替等于 === 代替 == -
字面量
// 实例化变量时尽量使用{},[] 代替 new Object(), new Array(), // 不要使用string, bollean,number对象类型 -
作用域
1. 慎用with 2. 慎用eval -
数组与对象
1. 字面量格式 2. for in 循环 3. 不要把数组当对象使用 -
异步操作
1. 异步回调的第一个参数应该是错误提示 2. 执行传入的回调函数 -
类与模块
1. 类继承(Node推荐方式) function(options){ stream.Stream.call(this); } utils.inherits(Socket,stream.Stream); 2. 导出 module.exports = Class; -
注释说明(软件自带)