美文网首页
02、NodeJS-基础

02、NodeJS-基础

作者: 王梓懿_1fbc | 来源:发表于2018-10-20 16:02 被阅读4次

    一、异步编程

    • 异步操作
      - Node 采用 Chrome V8 引擎处理 JavaScript 脚本, V8 最大特点就是单线程运行,一次只能运行一个任务
      - Node 大量采用异步操作(asynchronous operation),即任务不是马上执行,而是插在任务队列的尾部,等到前面的任务运行完后再执行
      - 提高代码的响应能力
    
    

    Node大量采用异步操作,即任务不是马上执行,而是直接插入任务队列的尾部,等前面任务执行完成后再只执行。异步执行,而不是单线程执行(一次只能执行一个任务),这大大提高代码的响应能力。
    【Node中,所有会发生阻塞的操作都是异步的。】

      - setTimeout()
      - ajax
      - 文件操作
      ...
    
    
    • 异步操作回调
      由于系统永远不知道用户什么时候会输入内容,所以代 码不能永远停在一个地方;
      Node 中的操作方式就是以异步回调的方式解决无状态 的问题;

    • 回调函数的设计

      - 回调函数一定作为参数的最后一个参数出现:
        function foo1(name, age, callback) { }
        function foo2(value, callback1, callback2) { }
    
      - 回调函数的第一个参数默认接收错误信息,第二个参数才是真正 的回调数据(便于外界获取调用的错误情况):
         foo1('李明', 19, function(error, data) { 
            if(error) throw error;
              console.log(data);
          });
    
    

    ***错误优先: ***因为之后的操作大多数都是异步的方式,无法通过 try catch 捕获异常; 所以在node中错误优先的回调函数,第一个参数为上一步的错误信息。

    • 异步回调的问题
      - 异步事件驱动的代码不容易阅读
      - 不容易调试
      - 不容易维护
    
    

    二、进程和线程

    • 什么是进程
      - 一个正在运行 的应用程序都称之为进程;
      - 每一个应用程序都至少有一个进程;
      - 进程是用来给应用程序提供一个运行的环境;
      - 进程是操作系统为应用程序分配资源的一个单位;
    
    
    • 什么是线程
      - 用来执行应用程序中的代码;
      - 在一个进程内部,可以有很多的线程;
      - 在一个线程内部,同时只可以干一件事;
      - 而且传统的开发方式大部分都是 I/O 阻塞的;
      - 所以需要多线程来更好的利用硬件资源;
      - 给人带来一种错觉:线程越多越好;
    
    

    多线程同时执行,真实情况并不是"同时",因为CPU只有一个;
    线程问题: 线程创建需要耗费资源,线程数量也不能无限添加,线程同步操作,线程间数据共享,CPU中线程间的切换有上下文的转换是需要耗时的....
    在node中,实现异步非阻塞操作,并不是使用多线程实现了(常规的异步非阻塞是通过多线程实现的)。*** Node.js在设计上也是比较大胆,它以单进程、单线程模式运行。事件驱动机制是Node.js通过内部单线程高效率地维护事件循环队列来实现的,没有多线程的资源占用和上下文切换,这意味着面对大规模的http请求,Node.js凭借事件驱动搞定一切。***

    三、事件驱动

    事件驱动是NodeJS中一大特性。事件驱动,就是通过监听事件的状态变化来作出相应的操作。例如文件存在,文件不存在,文件读取完毕,文件读取错误,触发对应的状态,之后通过回调函数进行处理。

    • 线程驱动和事件驱动

      • 线程驱动就是当收到一个请求的时候,将会为该请求开一个新的线程来处理请求。一般存在一个线程池,线程池中有空闲的线程,会从线程池中拿取线程来进行处理,如果线程池中没有空闲的线程,新来的请求将会进入队列排队,直到线程池中空闲线程;
      • 事件驱动就是当进来一个新的请求的时,请求将会被压入队列中,然后通过一个循环来检测队列中的事件状态变化,如果检测到有状态变化的事件,那么就执行该事件对应的处理代码,一般都是回调函数;

    在美国去看医生,需要填写大量表格,比如保险、个人信息之类,传统的基于线程的系统(thread-based system),接待员叫到你,你需要在前台填写完成这些表格,你站着填单,而接待员坐着看你填单。你让接待员没办法接待下一个客户,除非完成你的业务。想让这个系统能运行的快一些,只有多加几个接待员,人力成本需要增加不少。

    基于事件的系统(event-based system)中,当你到窗口发现需要填写一些额外的表格而不仅仅是挂个号,接待员把表格和笔给你,告诉你可以找个座位填写,填完了以后再回去找他。你回去坐着填表,而接待员开始接待下一个客户。你没有阻塞接待员的服务。
    你填完表格,返回队伍中,等接待员接待完现在的客户,你把表格递给他。如果有什么问题或者需要填写额外的表格,他给你一份新的,然后重复这个过程。
    这个系统已经非常高效了,几乎大部分医生都是这么做的。如果等待的人太多,可以加入额外的接待员进行服务,但是肯定要比基于线程模式的少得多。

    四、模块化结构

    • 模块与文件是一一对应关系,即加载一个模块,实际上
      就是加载对应的一个模块文件

    • 模块的分类

      - 文件模块
          就是我们自己写的功能模块文件
      - 核心模块
          Node 平台自带的一套基本的功能模块,也有人称之为 Node平台的 API
      - 第三方模块
          社区或第三方个人开发好的功能模块,可以直接拿回来用
    
    
    • 模块化开发的流程
      - new compute.js  创建模块(一个模块就一个文件)
      - module.exports = {}  导出成员
      - var comp = require('./compute.js')  载入模块
      - comp.add(1, 1)  使用模块
    
    

    五、定义模块

    • 模块内全局环境
      -  __dirname
          用于获取当前文件所在目录的完整路径
          在 REPL 环境无效
      -  __filename
          用来获取当前文件的完整路径
          在 REPL 环境同样无效
      - module 
          模块对象
      - exports
          映射到module.exports的别名
      - require()
          require.cache
          require.extensions
          require.main
          require.resolve()
    
    

    文件操作中必须使用绝对路径;

    • module 对象
      Node 内部提供一个 Module 构建函数。所有模块都是 Module 的实例;
      – module.id 模块的识别符,通常是带有绝对路径的模块文件名;
      - module.filename 模块定义的文件的绝对路径;
      – module.loaded 返回一个布尔值,表示模块是否已经完成加载;
      – module.parent 返回一个对象,表示调用该模块的模块;
      – module.children 返回一个数组,表示该模块要用到的其他模块;
      – module.exports 表示模块对外输出的值;
    
    
    • 模块的定义
      - 一个新的 JS 文件就是一个模块;
      - 一个合格的模块应该是有导出成员的,否则模块就失去了定义的价值;
      - 模块内部是一个独立(封闭)的作用域(模块与模块之间不会冲突);
      - 模块之间必须通过导出或导入的方式协同;
      - 导出方式
          exports.name = value;
          module.exports = { name: value };
      - module.exports是用于为模块导出成员的接口;
      - exports是指向module.exports的别名,相当于 在模块开始的时候执行
          var exports = module.exports;
      - 一旦为 module.exports 赋值,就会切断之前两者的相关性;
      - 最终模块的导出成员以 module.exports 为准;
    
    

    1、每个模块的内部都是私有空间,不会污染全局作用域;
    2、模块可以多次加载,但是只会在第一次加载时运行一次, 然后运行结果就被缓存了,以后再加载,就直接读取缓 存结果;
    3、模块加载的顺序,按照其在代码中出现的顺序;

    六、载入模块

    • require是什么
      require 的基本功能是,读入并执行一个JavaScript文件,然后返回该模块的exports对象;

    • require扩展名
      require 加载文件时可以省略扩展名;

      require('./module');
    
      // 1、此时文件按 JS 文件执行
      require('./module.js');
      // 2、此时文件按 JSON 文件解析
      require('./module.json');
      // 3、此时文件预编译好的 C++ 模块执行
      require('./module.node');
    
    
    • require加载文件规则
      - 通过 ./ 或 ../ 开头:则按照相对路径从当前文件所在文件夹开始寻找模块
          require('../file.js');   // 上级目录下找 file.js 文件
      - 通过 / 开头:则以系统根目录开始寻找模块
          require('/Users/zyz/Documents/file.js');  // 以绝对路径的方式找
      - 如果参数字符串不以“./“ 或 ”/“ 开头,则表示加载 的是一个默认提供的核心模块(位于 Node 的系统安 装目录中)
          require('fs');   // 加载核心模块中的文件系统模块
      - 或者从当前目录向上搜索 node_modules 目录中的文件
          require('my_module'); // 各级 node_modules文件夹中搜索 my_module.js 文件;
    
    

    require('my_module'); 开始是在核心模块中查找,这个其实不是核心模块;接着各级 node_modules文件夹中搜索

    • 模块的缓存
      • 第一次加载某个模块时,Node会缓存该模块。以后再 加载该模块,就直接从缓存取出该模块的 module.exports 属性(不会再次执行该模块)
      • 模块的缓存可以通过require.cache拿到,同样也可以删除
        //  模块的缓存的删除
        Object.keys(require.cache).forEach( (key) => {
            delete require.cache[key];
        } );
    
    
    • 如果需要多次执行模块中的代码,一般可以让模块暴露行为(函数)【避免每次都要清理缓存的问题】
      // 缓存的问题 -- 方式1
      module.exports = () => {
          return {time: new Date()};
      }
      // 缓存的问题 -- 方式2
        function fn(){
            return {time: new Date()};
         }
        module.exports = {fn};
    
    

    作者:西门奄
    链接:https://www.jianshu.com/u/77035eb804c3
    來源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

    相关文章

      网友评论

          本文标题:02、NodeJS-基础

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