node.js 3 模块化开发和原理解析
Posted lin_fightin
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了node.js 3 模块化开发和原理解析相关的知识,希望对你有一定的参考价值。
模块化
require细节
require是一个函数,require(’./)返回导出的对象,
查找规则
require的查找路劲
require(x)
1 x是一个核心模块,比如path,http,则直接返回核心模块。
2 x是以./ …/ /开头的吗?是的话就当作一个文件去对应的目录下查找,
有后缀名的时候,直接按照后缀名查找,没有的话,先 直接找x,找不到就根据x.js, x.json, x.node这个顺序来找。
那还找不到就将x作为一个目录,根据x/index.js x/index.json x/index.node来查找
再没找到直接报错。
3 没有路径,而且不是核心模块,那么会在每个文件的node_module去找,先从最里层的目录的node_module找,没有就往上一层的node_module去找,以此类推,都没找到再报错。
模块的加载过程
只要被require一次,里面的代码都会被执行一次。并且先执行require的代码。因为这是同步的,也就是只有加载完rqeuire的代码,才能运行下面的代码。并且执行的代码会缓存,也就是说我们就算再require,也不会再运行了,会直接从缓存取。
已经加载过的module的loaded属性会为true
那循环引用呢?
如a加载b.b加载c,c加载a
因为a引入了b,所以先执行b的代码,又因为b引入了c所以先执行c的代码。
node的代码执行顺序是通过数据结构图,会根据一行一行去找,并且因为a的loaded属性为true了,已经加载过了,所以不会再加载,所以到了c文件就不会再加载a的代码导致循环了。
Node是根据深度优先的算法来引入的。
Node源码大概看看模块化
定义了一个Module类,每个文件其实都会new一下,而且构造函数里面也有exports,就是module.exports
实现exports = module.exports操作。this指向实例。
require函数
返回_load的运行结果
而commonjs也是有缺点的,commonjs加载模块是同步的,也就是只有等对应的模块加载完才能继续加载下面的内容,而在浏览器上,我们是要从服务器将文件下载下来,如果同步的话表示后续的js代码都无法正常运行,可能导致白屏出现,而且现在webpakc可以实现将Commonjs代码转换成浏览器运行的代码了。
AMD CMD
AMD的实现主要是require.js。
CMD
SeaJS是cmd的主要实现,有兴趣可以去github看看。
ES MODULE
import export是关键字,不是函数,而且esmodule默认使用严格模式。
import函数
我们都知道这样是不行的,improt导入不能放在逻辑代码中的,为什么呢?因为js引擎在解析js代码的时候,我们都知道会通过parsing(解析)-ast树-字节码->二进制-执行 而Import的依赖关系是在解析时候parsing完成的,而上面的falg只有在运行的时候才知道是true或false,所以js在解析的时候不知道这个依赖关系成不成立,所以会报一个语法错误。为什么commonjs的require不会呢?require本质是一个函数,也是在运行阶段才完成的,但是浏览器默认是没有require,所以我们可以使用Import函数
commonjs的加载过程
common加载js文件是运行时加载并且同步
ES module加载过程
与commonjs加载js文件不同,esmodule在解析时就加载了,确立了彼此的依赖关系,并且是异步加载的。
可以看到是普通文件先执行,证明他是异步的,不会阻塞后面代码的执行。相当于给script加上了async属性。
并且export的导出本质上是,在export的时候,是在parsing的阶段就确定了,这时候创建了一个内存空间,叫做模块环境纪律,这里面做了一件事情,bingdings(实时绑定),当exports里面的值改变,就实时反映到模块环境纪律取,而里面也声明了一些变量,这里的变量赋值为exports的值。
比如
let a= 1
export { a }
//环境记录(内存空间做的事情)
const a = a(bingings做的类似的事情)实时响应,当源文件的a改变,这里的a也改变,相当于引用源文件的a
//导入的文件
import { a } from ‘./xx.js’
这里在引入的时候,本质上是引入了环境记录里面的值,所以当之后的源文件改变a后,由于模块环境记录是实时绑定的,所以会更新模块记录里面的值,那么引入的文件中的a也会跟着改变。而且我们不能在引入的文件对导出的a改变因为环境纪录里面的a是const定义的,不能改变,只能改变原文件的a值。但是如果是对象,对对象的操作在引入的文件还是可以的,这个跟const定义的对象可以改变其中的属性的含义一样。
commonjs与esmodule的区别
commonjs加载js文件是在代码运行阶段完成的并且是同步的,也就是require会阻塞后面代码的运行。
而require加载js文件是在parsing阶段加载的,并且是异步的,也就是不会阻塞后面代码的执行。
其次则是
commonjs导出的方式有export = {},module.exports={},这两块在一开始源码便会执行export = module.export,让其两个指向同一个对象,所以这也是为什么如果给module.export赋予新的对象的时候,导出并不会注意export的内容了,因为他们两个指向的都不是同个地址了,而common本质是以module.export指向的地址为准的,而require(’./xx’)本质上是一个函数,其返回值就是Module.export指向的对象。也是在代码运行阶段执行的。可以说commonjs的导入的对值的拷贝
而
esmodule的导出有export{},export default xxx,看上去export{}是导出了一个对象,但其实根本不是,首先esmodule的导入导出是在parsing阶段执行的,而不是运行阶段,并且export {} 的时候,本质上是创建了一个环境记录,简单的理解就是他是一个实时绑定export里面的值,然后再创建新的值,比如export {a} 在环境记录里就绑定他,然后const a = a,环境记录的值都是const的,当源文件a改变时,环境记录就会实时更新,并且跟新a值,const a = a(最新的a),这也是跟commonjs不同的地址,他们导出的不是对象,可以说是值的引用。而import {} from '.xx/本质是引用了环境记录里面的值,而不是源文件的,所以当你import {a} from '.xx/后,如果a是普通类型,这时候你不能修改,因为是const,只能在源文件直接修改,这时候才会通过实时绑定去修改环境记录里面的a的值,当是对象这种引用类型时,就可以操作里面的子属性。
所以esmodule的导入可以说是值得引用,而commonjs的导入是对值的拷贝
再者import也可以当做函数执行,import(),这时候就是函数,在代码运行阶段才执行的,而返回的也是一个promise对象,本质上也是export {}这个对象,可以通过await then这些去获取,是异步的。
(文中部分图片来自coderwhy老师)
以上是关于node.js 3 模块化开发和原理解析的主要内容,如果未能解决你的问题,请参考以下文章