阅读CommonJS的模块加载
聊一会儿,CommonJS
我相信每个人都知道英语单词“Common”的意思。我记得有一句话叫“常识”,意思是常识。那么CommonJS是不是和常识差不多,大家都懂?显然不是,这个常识根本不是常识。一开始我以为commonJS是一个开源的JS库,使用起来非常方便。里面有一些常用的前端方法。然而,我错得太离谱了,CommonJS不仅是一个库,还是一个看不见的东西。这只是一个规格!就像学校的规章制度一样,用来规范JS编程,绑定前端。就像Promise一样,它是一个规范。虽然有许多开源库来实现这些规范,但是这个规范可以通过我们的JS能力来实现。
CommonJs规范
那么CommonJS规范了什么呢?为了解释这个规范,我们应该从JS的特性开始。JS是文字脚本语言,也就是边编译边运行,所以没有模块概念。因此,CommonJS是完善JS在这方面不足的标准。
CommonJS定义了两个主要概念:
require函数用于导入模块,export变量并导出模块。但是浏览器不支持这两个关键词,所以我觉得这就是浏览器不支持CommonJS的原因。如果我们在浏览器中使用CommonJs,我们需要一些编译库,比如browserify,帮助我们将CommonJs编译成浏览器支持的语法,也就是实现require和exports。
那么CommonJS可以用来做什么呢?虽然CommonJS已经不能直接在浏览器中使用,但是nodejs可以基于CommonJS规范来实现。在nodejs中,我们可以直接使用关键字require和export来导入和导出模块。
Nodejs中CommomJS模块的实现
需要
导入,代码很简单,让{count,addcount}=require('。/utils ')。那么在进口过程中发生了什么?首先,它必须解析路径。系统为我们解析出一条绝对路径。我们写的相对路径是给我们的,绝对路径是给系统的。毕竟绝对路径又热又长,看着很费力,尤其是我们的项目在N个文件夹下的时候。所以首先需要解决的是路径。我们可以写得很简洁,只需要写相对路径和文件名,甚至连后缀都可以省略,让要求帮助我们匹配和查找。也就是说,require的第一步是分析获取模块内容的路径:
如果是核心模块,比如fs,会直接返回模块
如果它有一个路径,如/,/,等等。拼接一个绝对路径,然后在读取文件之前读取cache require.cache。如果没有后缀,会自动加后缀,然后逐个识别。
解析。js转换成JavaScript文本文件。将json转换为JSON对象。节点转换为二进制插件模块。该模块在第一次加载后将被缓存在require.cache中,因此在多次加载require后将获得相同的对象。
在执行模块代码时,模块将以以下模式打包,这样范围就可以在模块的范围内。
(函数(导出,要求,模块,_ _ filename,_ _ dirname){//模块的代码其实在这里});关于nodejs给出的官方解释,可以参考一下
组件
谈完require做了什么,require触发的模块做了什么?我们来看看用法。首先,编写一个简单的导出模块。写完模块后,只需添加要导出到module.exports的参数。
让count=0函数addcount () {count}模块。exports={count,addcount}然后在根据require执行代码时添加它,那么我们的代码实际上是这样的:
(函数(exports,require,module,__filename,_ _ dirname){ let count=0 function addCount(){ count } module . exports={ count,addCount } });当需要时,模块发生了什么,我们可以在vscode处中断:
根据这个断点,我们可以整理出:
当用黄色圈起来时,它是必需的,这就是我们所说的方法
用红色圈出模块的工作内容
模块。_ compilemodule。解脱.jsmodule。loadtrymoduleloadmodule。_ loadmodule。用蓝色圈起来的runmain就是nodejs做的,也就是NativeModule,用来执行模块对象。
我们都知道在JS中,调用函数的时候是栈,也就是说触发require函数的时候,图中的运行时是从下往上运行的。也就是说,蓝色的盒子先运行。我把他的部分代码剥离出来研究了一下。
NativeModule本机代码密钥代码,用于封装模块。
nativemodule . wrap=function(script){ return nativemodule . wrapper[0]script nativemodule . wrapper[1];};NativeModule.wrapper=['(函数(导出,要求,模块,__filename,__dirname) { ',' \ n });'];在NativeModule触发Module.runMain之后,我们的模块加载开始了。让我们从下往上解读。
模块。_load是创建一个新的模块对象,然后把这个新对象放到模块缓存中。
var模块=新模块(文件名,父模块);模块。_cache[filename]=模块;TryMouduleLoad,然后新创建的模块对象开始解析导入的模块内容
module.load(文件名);新创建的模块对象继承了模块。加载这个方法是解析文件类型,然后在不同的类别中执行它
模块扩展.js js做了两件事,读取文件,然后准备编译
模块。_compile终于到了编译的阶段,那么JS是如何运行文本的呢?js有三种方法可以将文本转换成可执行对象:
Eval方法eval('console.log('aaa ')')
新函数()模板引擎
让str=' console.log (a)'新函数(' AAA ',str)节点执行字符串,我们使用高级vm
让vm=require ('vm ')让a=' console . log(' a ')' VM . runintiscontext(a)这里,Module是以VM的方式编译的,先封装,再执行,最后返回require,这样就可以得到执行结果。
var包装器=Module.wrap(内容);var compiled wrapper=VM . runiniscontext(wrapper,{ filename: filename,lineOffset: 0,displayerrors 3360 true });因为所有模块都是封装后执行的,也就是说,对于这个导入的模块,我们只能根据外部接口module.exports来访问内容。
综上
看了这些代码的人真的很晕。实际上主要的过程是在require之后解析路径,然后触发Module类,然后Module的_load方法是在当前模块中创建一个新模块的缓存,以保证下次执行require的时候可以直接返回而不需要再次执行。然后,这个新模块的加载方法通过虚拟机执行代码加载并返回需要的对象。
因为这是编译运行后分配的缓存的方式,如果export的值是参数,不是函数,那么当前参数的变化不会引起export的变化,因为分配给export的参数是静态的,不会引起二次运行。
CommonJs模块和ES6模块的区别
使用场景
由于关键字的限制,CommonJS多用于服务器端。至于ES6的模块加载,这个特性已经得到浏览器的支持,所以ES6可以作为浏览器使用。如果遇到不支持ES6语法的浏览器,可以选择将其翻译成ES5。
语法差异
ES6也是一个JavaScript规范,不同于CommonJs模块。显然,首先代码不同,ES6的导入导出非常直观。
CommonJS ES6支持关键字参数、require、module、exports、_ _ filename、_ _ dirnameimport、export import const path=require(' path ')从' path' export module导入路径. exports=APP;导出默认APP导入的对象可以随意修改,但导入次数可以随意要求,除了第一次,都是从模块缓存中获取,导入到表头* *注意!说重点!Nodejs是CommonJS的儿子,所以有些ES6特性是不支持的,比如模块的ES6关键字导入导出。如果大家都在nodejs环境下运行,就等一个大的红色错误~ * *
负载差异
除了语法上的不同,他们所指的模块的性质也不同。虽然都是模块,但这些模块的结构却大不相同。
在ES6中,如果要在浏览器中进行测试,可以使用以下代码:
//utils . jsconst x=1;导出默认xscript类型='模块'从导入x '。/utils . js ';console . log(x);导出默认的x/script必须首先给脚本一个type='module ',表示它是一个ES6模块,默认情况下这个标签是异步加载的,也就是说它会在所有页面加载完毕后执行,否则没有这个标签代码就无法运行。然后就可以直接写导入导出了。
ES6模块导入的几个问题:
同一模块只能导入一次。例如,如果已经导入了x,则不能从utils导入x。不同的模块可以引入相同的模块。该模块仅在第一次导入时执行。引入的模块是一个值参考,并且是动态的。更改后,其他相关值也会发生变化。引入的对象不能随意切断链接。比如我不能修改我介绍的count的值,因为这是导入的,你只能在count所在的模块中修改。但是,如果count是一个对象,则可以更改该对象的属性,例如count.one=1,但不能更改count={one:1}。你可以看到这个例子。我写了一个小测试来改变对象的值。您会发现utils.js中count的初始值应该是0,但是count的值在运行addcount后会动态变化,所以count的值改为2。
let count=0 function addCount(){count=count 2 }导出{ count,addCount}脚本类型='module '导入{ count,addCount} from '。/utils . js ';//count=4//无法修改,将报告错误addCount(). console . log(count);/script是commonJS的模块引用,其特点是:
如前一节所述,模块导出的固定值是一个固定值,不会因为后面的修改而改变。除非静态值没有被导出,否则它会变成一个函数,并且每次调用都是动态进行的,然后每次值都是最新的。导入的对象可以随意修改,这只是导入模块的副本。
如果想深入学习,可以参考夏阮老师的ES6词条——Module的加载实现。
CommonJS模块摘要
CommonJS模块只能在支持该规范的环境中运行。nodejs是基于CommonJS规范开发的,因此可以完美运行CommonJS模块。nodejs不支持ES6模块规范,所以nodejs服务器开发一般用CommonJS规范编写。
CommonJS模块用require导入,用module导出.需要注意的是,如果导出的对象是一个静态值,这个值不是常量,以后可能会改变,请使用函数动态获取,否则无法获取修改后的值。导入的参数可以随意更改,所以使用时要小心。
以上就是边肖介绍的CommonJS模块加载的详细讲解和集成,希望对大家有所帮助。如果你有任何问题,请给我留言,边肖会及时回复你。非常感谢您对我们网站的支持!
版权声明:阅读CommonJS的模块加载是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。