• 读书笔记: 深入浅出node.js



    >> 深入浅出node.js

    node.js是c++编写的js运行环境

    浏览器: 渲染引擎 + js引擎

    后端的js运行环境

    node.js用google v8引擎,同时提供很多系统级的API(文件操作 网络编程...)

    node.js采用事件驱动 异步编程,为网络服务而设计

    浏览器端的js有各种安全限制

    node.js提供的多数API都是基于事件的,异步的风格。

    node.js的优点:充分利用系统资源,执行代码不会被阻塞以等待某个操作的完成;这个设计非常适合后端的网络服务编程。通过事件注册,回调,可以提高资源的利用率,改善性能。

    为方便服务器开发,node.js的网络模块有很多:
    HTTP, HTTPS, DNS, NET, UDP, TLS等,有助于快速构建web服务器。

    node.js的特点:事件驱动,异步编程,单进程,单线程。
    node.js内部通过单线程高效率地维护事件循环队列,没有太多的资源占用和上下文切换。

    单核性能出色的node.js如何利用多核CPU呢?建议运行多个node.js进程,用某种通信协议协调各项任务。

    javascript的匿名函数和闭包特性非常适合事件驱动,异步编程。

    node.js最擅长的事情是与其他服务通信。因为node.js是基于事件的无阻塞的,所以非常适合处理并发请求;缺点:node.js是相对新的一个开源项目,所以不太稳定,一直的变,而且缺少足够多的第三方库支持。

    知名项目托管网站GitHub也尝试了node应用(NodeLoad). 使用node.js的项目还有MyFox

    Node库socket.io

    >> node.js的安装和配置
    window版已经编译好的node.js安装包 nodejs.msi,安装后在Node命令行环境输入 node -v,有版本号输出,则说明安装好了。

    NPM(Node Package Manager)NodeJs的包管理器。通过NPM我们可以安装Nodejs的第三方库。

    Unix/Linux安装NPM:
    curl http://npmjs.org/install.sh | sudo sh
    获取shell脚本,交给sh命令以sudo权限执行。
    安装完之后 输入 npm 回车,如输出帮助信息 则表明安装成功。

    用NPM安装第三方库:如underscore
    npm install underscore
    由于一些特殊的网络环境用npm安装第三方库时,容易卡死;可以通过一个镜像的npm资源库来安装,如:
    npm --registry "http://npm.hacknodejs.com/" install underscore

    设置为默认npm资源库:
    npm config set registry "http://npm.hacknodejs.com" , 设置之后就可以直接 npm install underscore

    window下安装NPM:
    下载较新的nodejs.msi(v0.10...)安装包并安装后,npm默认也安装了。

    命令行下输入 npm回车即可查看npm的帮助信息。

    npm install underscore

    >> node.js的模块机制
    CommonJs规范:构建javascript在包括服务器,桌面,命令行的生态系统。
    node.js实现了用require方法引入其他模块,同时npm也基于CommonJs规范定义了包规范,实现了依赖管理和包的自动安装。

    > 模块的定义和使用
    circle.js:
    var PI = Math.PI;
    exports.area = function(r){
    return PI*r*r
    }

    exports.length = function(r){
    return 2*PI*r
    }

    app.js:
    var circle = require('./circle.js');
    console.log('the area of circle of radius 4 is ' + circle.area(4) );

    > 模块的载入策略
    node.js的模块分为2类:原生(核心)模块 和 文件模块
    原生模块在node.js源代码编译时就编译进了二进制执行文件,加载速度最快;文件模块是动态加载的,相对慢。

    node.js对require后的原生模块和文件模块都有缓存,第二次require时,从缓存获得,不会有重复的开销。

    node.js加载文件模块的工作是有原生模块module来实现和完成的。
    文件模块按后缀又分为3类:
    .js 通过fs模块同步读取js文件并执行
    .node c/c++编写的addon, 通过dlopen方法加载
    .json 读取文件 用JSON.parse方法解析。

    node.js对app.js这类文件模块的加载过程:
    1. 对文件内容进行头尾包装,变为模块定义的形式。如包装后变为:
    (function(exports, require, module, __filename, __dirname){
    var circle = require('./circle.js');
    console.log('the area of circle of radius 4 is ' + circle.area(4) );
    })
    包装后用vm模块的runInThisContext方法执行这个function对象,并传入对应的实参。

    require('./circle.js'); //在这里circle.js模块经历 载入--编译--缓存的过程,最后返回module对象exports。

    > require方法的文件查找策略
    node.js有原生模块和3种文件模块
    先在文件模块缓存区查找--若没有-->看看是否原生模块(是,则到原生模块缓存区查找,没找到则加载之并缓存)--若非原生模块--则查找文件模块----根据后缀 载入文件模块并缓存----最后返回exports

    require(..)方法的参数:
    1. http, fs, path等原生模块
    2. ./mod或../mod,相对路径的文件模块
    3. /pathtomodule/mod 绝对路径的文件模块
    4. mod 非原生模块的文件模块

    module path这个概念:
    每一个被加载的文件模块,在创建模块对象时,这个模块便会有一个paths属性,其值是根据当前路径计算得到的路径数组。如:
    modulepaths.js:
    console.log(module.paths);
    把modulepaths.js放到 f:panlm目录下,在命令行切换到该目录,执行 node modulepaths.js,输出:
    [ 'F:\pan\lm\node_modules',
    'F:\pan\node_modules',
    'F:\node_modules' ]
    查找路径生成规则很明显,此外还有一个全局的module path: 当前node执行文件的相对目录 和 环境变量中设置的HOME ,NODE_PATH

    简而言之,若require绝对路径的文件,查找时不会遍历每一个node_modules目录,速度最快。

    对module path数组的每条路径都执行某个查找过程。

    >> 包结构
    一个符合CommonJs规范的包结构应该如下:~~node_modules
    1. 一个package.json文件应该存在于包的顶级目录下。
    2. 二进制文件应包含在bin目录下
    3. javascript代码也应该在bin目录下
    4. 文档应该在doc目录下
    5. 单元测试应该在test目录下

    module paths路径数组中,在每条路径的查找过程中,在尝试添加扩展名也没找到目标文件时,会尝试将当期路径当做包来加载,从package.json中获取信息。读取package.json的main字段。

    package.json的字段:
    1. name 包名 需要在npm上是唯一的,不能包含空格。
    2. description 包简介
    3. version 版本号 x.y.z 用于版本控制的场景
    4. keywords 关键字数组,用于npm分类搜素
    5. maintainers 包维护者的数组 数组元素是形如 {name:..,email:.., web:..}的对象
    6. contributors 包贡献者的数组,第一个元素是包作者,形如{name:..,email:..}
    7. bugs 一个可以提交bug的url地址
    8. license 包所使用的许可证数组 数组元素如{type:...,url:..}
    9. repositories 托管源代码的地址数组
    10. dependencies 当前包需要的依赖 这个属性很重要,NPM会通过它自动加载依赖包。

    包符合CommonJs规范后,就可以发布,输入命令
    npm publish <folder>
    (需要先注册一个npm账号 : npm adduser)

    若用户要使用npm上的包,可以执行:
    npm install <package>

    本地方式安装包:
    从github手动下载包,在命令行下转到包的目录下 执行:
    npm install <package.json>


    --------------------------------------------------------
    node.js中的js模块文件和script标签加载的js文件的区别:node.js的模块文件中声明的变量是在闭包内的,不会污染全局环境,需要被外部调用的接口都挂在exports上。

    >> node.js的事件机制
    node.js的特点:Evented I/O for V8 javascript (基于V8引擎的事件驱动I/O)

    Event模块(events.EventEmitter)是一个简单的事件监听器模式的实现。具有方法:
    addListener/on, once, removeListener, removeAllListeners, emit...

    node.js的事件与前端Dom树上的事件不同, 不存在冒泡和逐层捕获等行为,自然也没有 preventDefault(), stopPropagation(), stopImmediatePropagation()等处理事件传递的方法

    事件侦听器模式也是一种事件钩子(hook)机制,利用事件钩子导出内部数据和状态给外部调用者。


    var options = {
    host: 'www.google.com',
    port: 80,
    path: '/upload',
    method: 'POST'
    };

    var req = http.request(options, function(res){
    console.log('STATUS: ' + res.statusCode);
    console.log('HEADERS: ' + JSON.stringfy(res.headers));

    res.setEncoding('utf8');
    res.on('data', function(chunk){
    console.log('BODY:' + chunk);
    });

    });

    req.on('error', function(e){
    console.log('problem with request: ' + e.message);
    });

    //write data to request body
    req.write('data ');
    req.write('data2 ');
    req.end();

    注:如对于一个事件添加超过10个监听器,会得到一条警告,因为设计者认为侦听器太多,可能导致内存泄漏。调用这个语句可以取消10个侦听器的限制: emitter.setMaxListener(0);

    若运行时的错误触发了error事件,会交给error侦听器处理,若没有设置error侦听器,则抛出异常,若异常没有被捕获,将会引起退出。

    > 如何继承event.EventEmitter类,如node.js中流对象继承EventEmitter类:
    //类式继承
    function Stream(){
    event.EventEmitter.call(this);
    }
    util.inherits(Stream, event.EventEmitter);

    //util.inherits应该是如下这样实现原型链的
    util.inherits = function(subClass, superClass){
    function F(){}
    F.prototype = superClass.prototype;
    subClass.prototype = new F;
    subClass.prototype.constructor = subClass;
    }

    > 多事件之间的协作
    在大应用中,数据源和web服务器分离是必然的。好处有:相同数据源开发各种丰富的客户端程序,从多个数据源拉取数据,渲染到客户端。
    node.js擅长同时并行发起对多个数据源的请求。
    并行请求:
    api.getUser('username', function(profile){
    // got the profile
    });

    api.getTimeline('username', function(timeline){
    // got the timeline
    });

    api.getSkin('username', function(skin){
    // got the skin
    });

    这里存在一个问题:请求可以并行发出,但是如何控制回调函数的执行顺序?

    若改为这样:
    api.getUser('username', function(profile){
    api.getTimeline('username', function(timeline){
    api.getSkin('username', function(skin){
    // todo
    });
    });
    });

    这将导致请求不能并行发出。

    node.js没有原生支持多事件之间协调的方法,需要借助第三方库。如:

    var proxy = new EventProxy();
    //all方法作用:侦听完事件后,执行回调,并将侦听接收到的参数传入回调中
    proxy.all('profile', 'timeline', 'skin', function(profile, timeline, skin){
    //todo
    });

    api.getUser('username', function(profile){
    proxy.emit('profile', profile); //触发事件profile,并传入实参profile
    });

    api.getTimeline('username', function(timeline){
    proxy.emit('timeline', timeline);
    });

    api.getSkin('username', function(skin){
    proxy.emit('skin', skin);
    });

    解决多事件协作的另一种方案:Jscex(代码可以用同步的思维去写,异步方式执行)
    如:
    var data = $await(Task.whenAll({
    profile: api.getUser('username'),
    timeline: api.getTimeline('username'),
    skin: api.getSkin('username')
    }));

    //使用:data.profile, data.timeline, data.skin
    // todo

    > 利用事件队列解决雪崩问题
    雪崩问题:指在缓存失效的情景下,大量的并发访问同时涌入数据库查询,数据库无法承受,进而导致网站整体很慢。

    看看加一个状态锁的解决方案:
    var status = 'ready';
    var select = function(callback){
    if(status ==='ready'){
    status = 'pending'; //上锁
    db.select('SQL', function(results){
    callback(result);
    status = 'ready'; //解锁
    });
    }
    }

    连续多次调用select方法时,锁定期间有的select会不被处理;所以应该用事件队列的方式来解决。

    var proxy = new EventProxy();
    var status = 'ready';
    var select = function(callback){
    proxy.once('selected', callback);//将所有请求的回调都压入事件队列中
    if(status === 'ready'){
    status = 'pending';
    db.select('SQL', function(result){
    proxy.emit('selected', result);
    status = 'ready';
    });
    }
    }

    >> node.js的异步I/O实现
    同步:程序中的后续任务都需要等待I/O的完成,等待的过程中无法充分利用CPU。
    实现I/O并行,充分利用CPU的方式有2种:多线程但进程, 单线程多进程

    getFile('file_path'); //耗时m
    getFileFromNet('url'); //耗时n
    同步I/O的话,需要m+n, 异步I/O的话,需要max(m,n)
    异步I/O在分布式的环境中很重要,能明显改善性能。

    > 异步I/O与轮询技术
    进行非阻塞的I/O操作时,要读取到完整数据,应用程序要多次轮询,才能确保数据读取完成,然后进行下一步。轮询技术的缺点是应用程序主动多次调用,占用较多CPU时间片。

    >> node.js的异步I/O模型
    fs.open = function(path, flags, mode, callback){
    callback = arguments[arguments.length - 1];
    if( (typeof callback) !== 'function' ){
    callback = noop;
    }

    mode = modeNum(mode, 438); /* 438 = 0666 */
    binding.open(pathModule._makeLong(path), stringToFlags(flags), mode, callback);
    }

    >> string buffer

    var fs = require('fs');
    var rs = fs.createReadStream('testdata.md');
    var data = '';
    rs.on('data', function(trunk){
    data += trunk; //trunk 是一个buffer对象
    });

    rs.on('end', function(){
    console.log(data);
    });

    npm install bufferhelper;
    bufferconcate.js:
    var http = require('http');
    var BufferHelper = require('bufferhelper');
    http.createServer(function(req, res){
    var bufferHelper = new BufferHelper();
    req.on('data', function(chunk){
    bufferHelper.concat(chunk);
    });

    req.on('end', function(){
    var html = bufferHelper.toBuffer().toString();
    res.writeHead(200);
    res.end(html);
    });
    });

    >> connect模块(node.js web框架)
    中间件的流式处理

    var app = connect();

    //middleware
    app.use(connect.staticCache());
    app.use(connect.static(__diranme + '/public') );
    app.use(connect.cookieParser());
    app.use(connect.session());
    ...
    app.use(function(req, res, next){
    //中间件
    });

    app.listen(3001);

    connect用use方法注册中间件到中间件队列中。

  • 相关阅读:
    [代码质量] Git统计本次提交新增代码行数,建议每个评审commit新增行数小于400行
    [Web 安全] WASC 和 OWASP两个web安全方面组织机构介绍
    [web 前端] Npm package.json与package-lock.json文件的作用
    Mac IDEA 插件 lombok
    IDEA 导入新的项目步骤
    IDEA for Mac 快捷键
    Flink --- hello world
    LogisticRegression回归算法 Sklearn 参数详解
    keep going
    在 macOS 上快速新建 txt 文本文件
  • 原文地址:https://www.cnblogs.com/stephenykk/p/3832171.html
Copyright © 2020-2023  润新知