美文网首页
Node.js学习札记

Node.js学习札记

作者: Chaos_YM | 来源:发表于2019-05-20 14:04 被阅读0次

    一,commonJS

    W3C:BOM,DOM
    浏览器端JS:W3C + ECMAscript
    Node.js:commonJS + ECMAscript
    commonJS: FS,TCP,stream,Buffer。。。。。

    二, 模块系统

    语法:require();module.export;
    实现步骤
    1,路径分析;
    2,文件定位(require带上扩展名会加快速度,不带扩展名会按照.js/.json/node次序补足扩展名,一一尝试);
    3,编译执行。(语言本身的核心模块可忽略1,2)(定位文件后会新建模块对象进行编译,就是把模块的信息制作成一个对象而已。并进行缓存)
    性能:缓存编译后的对象,优先缓存加载

    几种模块:
    1.核心模块:http,fs,path(优先级仅次于缓存)
    2.相对路径和绝对路径模块
    3.文件模块

    三,异步I/O

    操作系统底层:阻塞I/O与非阻塞I/O(非阻塞通过轮询监控状态)
    非阻塞I/O的主要轮询技术(从旧到新):
    1.read:最原始,低性能,一直轮询检控I/O状态
    2.select:改进read,轮询文件描述符的时间状态;限制:采用1024长度的数组储存状态,最多1024个文件操作符
    3.poll:改进select,采用链表储存状态
    4.epoll:通过事件唤醒,目前效率最高


    现实中的异步I/O:
    线程池通过主线程和其他IO线程,模拟异步I/O


    Node中的异步I/O
    事件循环,观察者,请求对象,IO线程池四者共同构成了Node异步IO

    Node高效的原因正是因为 基于事件驱动的非阻塞I/O模型
    javascript是单线程的,但是Node的I/O是可以并行多线程的


    Node中与IO无关的异步API
    首先计时器并不是精确的,通过插入定时器观察者的红黑树,每次tick执行检查是否过时,过时则形成事件;
    setTimeout()
    setInterval()
    process.nextTick() 相比setTimeout轻量,因为不用动用红黑树 每次tick全部执行完毕
    setImmediate() 如果有多个,每次tick执行一个
    process.nextTick()优先级高于setImmediate() ,因为观察者优先级不同

    四,异步编程

    难点:
    1.异常处理:
    try/catch 处理异步函数,只会捕获立即返回的第一阶段,callback等无法捕获。
    Node规定:异常会作为回调函数的第一个实参传回,为空则没有异常
    2.函数嵌套的回调地狱
    3.阻塞代码的实现
    4.多线程编程
    5.异步转同步

    解决方案:

    事件发布/订阅模式

    没有DOM中的事件冒泡和捕获,没有preventDefault(),stopPropagation(),stopImmediatePropagation()等;
    有addListener/on(),once(),removeListener(),removeAllListeners(),emit()等事件监听模式的方法;
    (�发布订阅模式有点像vue的总线机制)
    可以一对多,多对一,即一个/多个emit(),多个/一个on();
    注意
    一个事件超过10个侦听器Node会发出一条警告 ,调用emitter.setMaxListerners(0)可以关闭,主要为了防止内存泄露和cpu占用过多;
    1.利用事件队列解决雪崩问题

    当访问量巨大,同一条SQL语句同时进行大量查询,会影响整体性能,解决:
    1,加状态锁(单单加状态锁,只有第一个请求有效,后续就完全无效了)
    2,通过once()讲请求引入事件队列,这样执行一次SQL后,所有请求的监听器会被移除,但是回调会被压入事件队列,。查询之后数据被所有回调共同使用,提高性能。

    2.多异步之间的协作方案
    当存在多个事件对应一个侦听器的时候,可能出现并行问题,但是我们希望是串行的。
    原生实现或使用EventProxy(非Node原生)

    Promise/Deferred

    即get().success().fail()链式写法模式代替回调
    promise.then()用于外部暴露给开发者,defer用于内部维护:未完成,已完成,失败三种状态,并触发事件(内部原理还是事件发布订阅模式)

    流程控制(略)

    尾触发和next,async,step,wind

    异步并发控制

    并发量过大,底层达上限
    解决:bagpipe,async(方式都类似 控制上限)


    五,v8内存控制

    1,垃圾回收机制

    尽管物理内存可能很大,但是v8只能使用部分内存(64位约为1.4G,32位约为0.7G),原因是内存过大垃圾回收机制耗时过长 严重影响前后端体验
    process.memoryUsage()查看内存
    打开内存限制:启动时增加
    node --max-old-space-size=1700 test.js // 单位位MB,老生代内存空间
    node --max-new-space-size=1024 test.js // 单位位KB,新生代内存空间
    两者想加,为总内存


    新生代又二分为两个semispace,From和To,进行垃圾回收时,检测From中的存活对象,复制到To,原From被释放,然后From和To的角色兑换;多次之后仍然存在的对象,移动到老生代内存(晋升);


    垃圾回收时,应用程序会暂停,即"全停顿";(老生代清理全停顿事件过长,有增量标记,延迟清理,增量式整理等方式优化时间)
    查看垃圾回收日志:(启动Node时增加 --trance_gc参数)
    node --trance_gc


    2,高效使用内存

    无法立即收回的内存有闭包和全局变量,故小心使用。
    查看内存,堆外内存,内存泄露,大内存应用


    六,理解buffer对象

    1.结构
    内建模块使用C++提高性能,核心模块使用JS。内存占用上,buffer属于堆外内存。且buffer被放在了全局对象上,无需require可以直接引入
    2.buffer对象
    类数组,每个元素为16进制的两位数。中文字占3个元素,英文和半角标点占一个元素,、;
    可以访问length属性,可以通过下标访问元素,和赋值元素;

    var buf = new Buffer(100);
    console.log(buf.length)     // 100
    console.log(buf[10])     // 0~255的随机数
    // 赋值操作(超出0~255,或者赋值小数,会处理后返回)
    buf[10] = 100;
    console.log(buf[10])     // 100
    buf[20] = -100;
    console.log(buf[20])     // 156 小于0,逐次加256,直到符合
    buf[21] = 300;
    console.log(buf[21])     // 44 大于0,逐次减256,直到符合
    buf[22] = 3.1415;
    console.log(buf[22])     // 3  小数会舍弃小数部分
    

    3.buffer内存分配
    小块内存可以手动操作申请和,大内存无需操作,C++自动处理
    4.Buffer转换
    1),字符串转buffer:
    new Buffer(str, [encoding(编码方式)]);
    encoding无参数默认utf-8,
    可以多次写入,不同编码类型的字符串转码值:buf.write(str, [offset], [length], [encoding])
    2),buffer转字符串:
    buf.toString([encoding], [start], [end])
    如果这个buffer由多段不同编码写入,需要分部分转码
    3),不支持类型:
    Buffer.isEncoding(xx),判断是否支持buffer转码,返回布尔值;
    对于不支持的格式,可以引入其他模块辅助处理。
    4),乱码:

    var fs = require('fs);
    var rs = fs.createReadStream('test.md', {highWaterMark: 11});
    var data = '';
    rs.on("data", function (chunk) {
      data += chunk; 
      // 等价于 data = data.toString() + chunk.toString();
    });
    rs.on("end", function () {
      console.log(data);
    });
    // 改进
    readable.setEncoding('utf-8')  // 可以正常输出中文
    

    这样读取文件,英文不会有问题,中文会产生乱码
    原因:文件可读流的每次读取的buffer长度限制为11
    改进:上面setEncoding,使文件流传递的使编码后的字符串,限制(只支持utf-8, base64, ucs-2/utf-16le这三种)(不能从根本上解决问题)
    正确拼接buffer:
    5.buffer与性能
    将字符串转化为buffer后传输,速度可提升一倍
    highWaterMark值越大,读取文件速度越快


    六,网络编程

    1.TCP服务
    2.HTTP服务
    3.websocket服务
    4.网络服务与安全
    SSL协议,Node相关模块:crypto,tls,https


    七,构建web应用

    1.基础功能
    请求方法,url解析,url查询字符串解析(query),cookie解析,表单数据,文件上传处理……等等

    var app = function (req, res) {
       res.writeHead(200, { 'Content-Type': 'text/plain' });
       res.end('hello world\n');
    }
    http.createServer(app).listen(1337);
    
    // url解析
    req.method === 'POST' || 'GET' || 'PUT' || 'DELETE';
    // url解析
    var url = require('url');
    url.parse(req.url)
    
    // url查询字符串解析(query)   ?foo=bar&baz=val
    var url = require('url');
    var querystring = require('querystring');
    var query = url.parse(req.url, true).query || querystring.parse(url.parse(req.url).query);
    // 结果  如果query的键出现多次,结果会是一个数组  ?foo=1&foo=2
    query = { foo: 'bar', baz: 'val' };      { foo: [1, 2] }
    
    // cookie解析  req.headers.cookie  格式key=value;key2=val2;
    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;
    }
    // 挂载到req对象上
    function (req, res) {
      req.cookies = parseCookie(req.headers.cookie);
    }
    
    // 返回给用户的cookie在 Set-Cookie中
    // name=value是必选,其余是可选;
    // path表示这个cookie影响的路径,当前访问路径不匹配时,就不会发送cookie
    Set-Cookie: name=value; path=/; .....
    Expires和Max-age    // cookie过期时间,不设置则关闭浏览器失去cookie    Expires是UTC时间,缺点客户端时间与服务器不同,就发生过期偏差,max-age是毫秒
    HttpOnly  // 告知浏览器不允许通过document.cookie更改cookie值
    Secure   // 为true只对https有效,表示cookie只能在https链接中传递,http链接不会携带cookie
    res.setHeader('Set-Cookie', 'xxx');  // xxx可以通过拼接max-age=abc,path=/形成的数组然后join(';')组成
    

    出于性能:应减少cookie数量与大小,为静态资源换个域名使之不必携带cookie
    安全性:前后端都可以修改cookie,易篡改,判别账户信息,用Session(只存在服务端)


    session数据与用户一一对应的方式:
    1),基于cookie实现映射
    将口令放在cookie中,一旦修改即丢失映射,然后实现cookie与session的映射即可(
    2),通过查询字符串实现对应
    session安全:
    防止cookie等客户端口令被盗窃或者伪造:方法一私钥加密,
    XSS漏洞(跨站脚本攻击):用户输入未转译,输入了JS代码


    basic认证(用户账号密码登陆)
    在https中或可用


    2.数据上传
    除了报头中的数据http会解析,携带的数据需要自己解析,通过data事件

    // 以数据流接收
    var buffers = [];
    req.on('data', function (chunk) {
      buffers.push(chunk);
    });
    // 传输完成后转码
    req.on('end', function () {
      req.rawBody = Buffer.concat(buffers).toString();
    });
    
    // 判断数据上传的方式,先定义一个判断请求头的函数
    var mime = function (req) {
      var str = req.headers['content-type'] || '';
      return str.split(';')[0];
    }
    // 表单数据   报文体数据与query相同,可同样方式解析
    var handle = function (req, res) {
      if (mime(req) === 'application/x-www-form-urlencoded') {
        req.body = querystring.parse(req.rawBody);
      }
      todo(req, res);
    }
    // Json文件
    var handle = function (req, res) {
      if (mime(req) === 'application/json') {
        try {
          req.body = JSON.parse(req.rawBody);
        } catch (e) {
          res.writeHead(400);
          res.end('Invalid JSON');
          return;
        }
      }
      todo(req, res);
    }
    // XML文件
    var xml2js = require('xml2js');
    var handle = function (req, res) {
      if (mime(req) === 'application/xml') {
        // 略
      }
    }
    

    附件上传,CSRF(跨站请求伪造)


    3.路由解析
    路由映射 => MVC => RESTful

    4.中间件(洋葱圈)
    将从http请求到业务逻辑中间的细节囊括 => 一系列优化 => 中间件+路由
    1>异常处理;2>中间件性能(高效与合理路由)

    5.页面渲染

    相关文章

      网友评论

          本文标题:Node.js学习札记

          本文链接:https://www.haomeiwen.com/subject/rztkzqtx.html