深入浅出Nodejs 读书笔记

389 阅读9分钟

深入浅出nodejs笔记

模块机制

  1. 发布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> 
    
  2. 兼容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

  1. 事件循环、观察者、请求对象 、I/O线程池这四者共同构成了Node异步I/O模

  2. 非 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延迟执行2 
      
      

      process.nextTick()中的回调函数执行的优先级要高于setImmediate()。 这里的原因在于事件循环对观察者的检查是有先后顺序的,process.nextTick()属于hide 观察者,setImmediate() 属于check观察者。在每一个轮询检查中,idle观察者先于I/O观察者,I/O观察者先于check观察者。

      process.nextTick() 在每轮循环中会将数组中的回调函数全部执行完,而setImmediate() 在每轮循环中执行链表中的一个回调函数。

异步编程

  1. 异步编程的难点

    编写异步方法的原则:

    • 必须执行调用者传入的回调函数
    • 正确传递回异常供调用者判断
  2. 异步编程的解决方案

    • 事件发布订阅/订阅模式

    • 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);
      }
      
      
  3. Promise中的多异步操作(Promise.all)

  4. 支持序列化调用的Promise(链式调用)

  5. 异步并发控制

    • bagpipe的解决方案

    • async的解决方案

内存控制

  1. 回收机制

    • 分代机制(新生代和老生代)
    • Scavenge算法
  2. 缓存方法(队列先进先出)内存当缓存使用

    • 简单的算法实现

      // 简单的内存缓存的方法,用于优化内存(斐波那契数列可使用全递归)
      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进程的性能,解决两个主要问题

      1. 将缓存转移至外部,减少常驻内存的数量,让垃圾回收更高效。

      2. 进程之间可以共享缓存

        使用方案: Redis,MemCached

理解BUffer

  1. 大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; 
      }
      

网络编程

  1. 构建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');
    
  2. 构建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);
    
    
    
  3. 构建http服务

    • 服务端和客户端编程
  4. 构建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
    }
    
    
  5. 网络安全

    • 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应用

  1. 构建具体业务的需求(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认证

    • 表单数据的解析

    • 任意根式文件的上传处理

  2. session的使用

    • 基于cookie来实现用户和数据的映射

      // 生成session
      var sessions = {};
      var key = 'session_id';
      var EXPIRES = 20*60*1000var 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));
              }
          }
              
      }
      
      // 缺点:危险性较大,伪造用户身份较容易
      
      
      
  3. 缓存的使用

    • 添加Expires或Cache-Control 到报文头部

    • 配置Etags

    • 让Ajax可缓存

  4. 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);
        }
    }
    
    
  5. 数据上传

    • 数据上传类型

      // 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) 基于流式处理解析报文,将接受到的文件写入临时文件库
      
      
      
  6. 路由解析

    • 文件路径型
    • MVC模式(手工映射和自然映射)
    • RESTful
  7. 中间件

  • 尾递归调用处理

    //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();
    }
    
    
  • 中间件与性能

    1. 编写高效的中间件
    2. 合理利用路由,避免不必要的中间件执行(精确路由匹配规则)
  1. 页面渲染(实现一个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继承
    // 服务端骨架屏渲染 + 动态渲染数据
    
    
    

玩转进程

  1. 句柄传递

  2. children_process的使用

  3. 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);
    
    

测试

  • 单元测试介绍

    1. 测试风格分类(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编码规范

  1. 空格与样式

    - 缩进(使用两个空格,非tab)
    - 变量声明(上下文污染)
    - 空格(操作符前后加空格)
    - 单双引号使用(字符串使用单引号,JSON中使用双引号)
    - 大括号的位置(无需另起一行)
    - 逗号(前后空格)
    - 分号(表达式行尾添加分号)
    
    
  2. 命名规范

    - 变量命名
        1. 变量和方法:使用小驼峰
        2. 类命名:使用大驼峰,首字符大写
        3. 常量命名:所有字母均大写,使用下划线分割
        4. 文件命名:使用小写,下划线分割
        5. 包命名:简短有意义
    
    
  3. 比较操作

    - 使用全等代替等于 === 代替 ==
    
    
  4. 字面量

    // 实例化变量时尽量使用{},[] 代替 new Object(), new Array(),
    // 不要使用string, bollean,number对象类型
    
    
  5. 作用域

    1. 慎用with
    2. 慎用eval
    
    
  6. 数组与对象

    1. 字面量格式
    2. for in 循环
    3. 不要把数组当对象使用
    
    
  7. 异步操作

    1. 异步回调的第一个参数应该是错误提示
    2. 执行传入的回调函数
    
    
  8. 类与模块

    1. 类继承(Node推荐方式)
    function(options){
     	stream.Stream.call(this);
    }
    utils.inherits(Socket,stream.Stream);
    2. 导出
    module.exports = Class;
    
    
  9. 注释说明(软件自带)

搭建局域NPM仓库