一、异步编程
- 异步操作
- 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
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
网友评论