脚本宝典收集整理的这篇文章主要介绍了

带你了解JavaScript相关的模块机制

脚本宝典小编觉得挺不错的,现在分享给大家,也给大家做个参考,希望能帮助你少写一行代码,多一份安全和惬意。
<p><code></p> <h2 id="articleHeader0">前言</h2> <blockquote><p><a href="http://www.js-code.com/tag/java" title="java" target="_blank">java</a>有类文件,Python有<a href="http://www.js-code.com/tag/import" title="import" target="_blank">import</a>机制,Ruby有require等,而Javascript 通过<code>&lt;script&gt;</code>标签引入代码的机制显得杂乱无章,语言自身毫无组织能力,人们不得不用命名空间的等方式人为的组织代码,以求达到安全易用的目的<br />《深入浅出Nodejs》--朴灵</p></blockquote> <p>模块一直以来都是组织大型软件的必备的要素,就像建筑和砖,“砖”的组织规则更是需要最先明确的事情,一直以来JS在语言层面都没能给模块机制足够的重视,知道ES6的module的出现仿佛给出了最终解决的方案,但是毕竟ES6的module还没能得到良好的支持,其中所面临的复杂情况可想而知,因为业务场景的多样性导致似乎哪一种模块机制都感觉到了众口难调,虽然Node8已经对绝大部分的ES6语法提供了非常好的支持,但是要想使用ES6的模块机制还是必须要使用类似<a href="http://www.js-code.com/tag/babel" title="babel" target="_blank">babel</a>的转义工具才能做到并不是那么“无畏”的使用。本文从最简单的模块开始,然后主要从Node的模块规范和ES6的模块机制对模块进行梳理。</p> <h2 id="articleHeader1">“模块化”的基本实现</h2> <p>每次在注册成为某一个网站或者应用的用户时最让人心碎的的就是自己常用的用户名已经存在了,很紧张得换了几个还能接受的用户名发现自己的想法总是很受欢迎,于是即便放着《不将就》也无奈的选择了在自己的用户名后面加上了自己的生日数字...<br />这里也不太方便讨论如果加上了生日数字之后,表单校正还是提示你“该用户名已经存在!”的情况,剪网线就完事了。</p> <p><span class="img-wrap"><img data-src="/img/remote/1460000012999802" src="https://static.segmentfault.com/v-5cc2cd8e/global/img/squares.svg" alt="用户名已存在!" title="用户名已存在!" style="cursor: pointer;"></span></p> <p>我想表达的意思实际就是,<strong>全局环境下的变量的命名冲突</strong>,变量太多难免词穷情况很常见,所以这一定是模块化给我们带来的好处,有了模块你就可以继续用你喜欢的用户名,只不过你得介绍清楚,你是“村口第五家.Ray"</p> <p><span class="img-wrap"><img data-src="/img/remote/1460000012999803" src="https://static.segmentfault.com/v-5cc2cd8e/global/img/squares.svg" alt="一把梭" title="一把梭" style="cursor: pointer;"></span></p> <p>无需多言,上图表达了一切。良好的模块化,是<strong>代码复用与工程解耦</strong>的关键,"一把梭"确实爽,讲究一个我不管你里面怎么翻滚,你暴露给我干净的接口,我还你一个讲究的git star。</p> <p>如果一个包依赖另一个包,你一把梭的时候还要手动先把它依赖的那个包梭进来,过分之,那个它依赖的包有依赖好几个别的包,甚至有些情况中你甚至还要很在意你手动添加依赖的顺序,这种梭法,一旦项目复杂,光是对这些“梭法”的管理都让人心烦了,所以为了省心,模块机制也务必要面对<strong>解析依赖,管理依赖</strong>这个本身就很繁琐的任务。</p> <p>所以进入正题,针对前面提到的几点,看一看简单的模块实现。</p> <ul> <li>最简单的模块化可以理解成一个一个的<strong>封装函数</strong>,每一个封装的函数去完成特定的功能,调用函数的方式进行复用。但是存在着类似于a,b污染了全局变量的缺点</li> </ul> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text="const module1 = ()=>{<br /> // dosomething<br /> }<br /> const module2 = ()=>{<br /> // dosomething<br /> }" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="js"><span class="hljs-keyword"><a href="http://www.js-code.com/tag/const" title="浏览关于“const”的文章" target="_blank" class="tag_link">const</a></span> module1 = <span class="hljs-function"><span class="hljs-params">()</span>=&gt;</span>{ <span class="hljs-comment">// <a href="http://www.js-code.com/tag/do" title="浏览关于“do”的文章" target="_blank" class="tag_link">do</a>someth<a href="http://www.js-code.com/tag/in" title="浏览关于“in”的文章" target="_blank" class="tag_link">in</a>g</span> } <span class="hljs-keyword">const</span> module2 = <span class="hljs-function"><span class="hljs-params">()</span>=&gt;</span>{ <span class="hljs-comment">// dosomething</span> }</code></pre> <ul> <li><strong>使用对象封装</strong></li> </ul> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text="var module1 = new Object({     _count : 0,     m1 : function (){       //...     },     m2 : function (){       //...     }   });   // module1.m1   // module1.m2" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="js"><span class="hljs-keyword"><a href="http://www.js-code.com/tag/var" title="浏览关于“var”的文章" target="_blank" class="tag_link">var</a></span> module1 = <span class="hljs-keyword"><a href="http://www.js-code.com/tag/new" title="浏览关于“new”的文章" target="_blank" class="tag_link">new</a></span> <span class="hljs-built_in"><a href="http://www.js-code.com/tag/Object" title="浏览关于“Object”的文章" target="_blank" class="tag_link">Object</a></span>({     <span class="hljs-attr">_count</span> : <span class="hljs-number">0</span>,     <span class="hljs-attr">m1</span> : <span class="hljs-function"><span class="hljs-keyword"><a href="http://www.js-code.com/tag/function" title="浏览关于“function”的文章" target="_blank" class="tag_link">function</a></span> (<span class="hljs-params"></span>)</span>{       <span class="hljs-comment">//...</span>     },     <span class="hljs-attr">m2</span> : <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>)</span>{       <span class="hljs-comment">//...</span>     }   });   <span class="hljs-comment">// module1.m1</span>   <span class="hljs-comment">// module1.m2</span></code></pre> <p>缺点:往往存在不想让外部访问的变量(module1._count),这种方式就不能满足了(不考虑使用<a href="http://www.js-code.com/tag/Object" title="Object" target="_blank">Object</a>.def<a href="http://www.js-code.com/tag/in" title="in" target="_blank">in</a>e<a href="http://www.js-code.com/tag/prop" title="Prop" target="_blank">Prop</a>erty)</p> <ul> <li><strong>立即执行函数的方式</strong></li> </ul> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text="  var module1 = (function(){     var _count = 0;     var m1 = function(){       //...     };     var m2 = function(){       //...     };     return {       m1 : m1,       m2 : m2     };   })();" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="js">  <span class="hljs-keyword">var</span> module1 = (<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{     <span class="hljs-keyword">var</span> _count = <span class="hljs-number">0</span>;     <span class="hljs-keyword">var</span> m1 = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{       <span class="hljs-comment">//...</span>     };     <span class="hljs-keyword">var</span> m2 = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{       <span class="hljs-comment">//...</span>     };     <span class="hljs-keyword"><a href="http://www.js-code.com/tag/return" title="浏览关于“return”的文章" target="_blank" class="tag_link">return</a></span> {       <span class="hljs-attr">m1</span> : m1,       <span class="hljs-attr">m2</span> : m2     };   })();</code></pre> <p>通过自执行函数可以只返回想返回的东西。</p> <p>如果此模块内想继承使用类似于jquery等库则就需要显示的将库传入到自执行函数中了</p> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text="var module1 = (function ($, axios) {     //...   })(jQuery, axios);" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="js"><span class="hljs-keyword">var</span> module1 = (<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">$, ax<a href="http://www.js-code.com/tag/ios" title="浏览关于“ios”的文章" target="_blank" class="tag_link">ios</a></span>) </span>{     <span class="hljs-comment">//...</span>   })(<a href="http://www.js-code.com/tag/jquery" title="浏览关于“jQuery”的文章" target="_blank" class="tag_link">jQuery</a>, axios);</code></pre> <h2 id="articleHeader2">浏览器传统加载模块规则</h2> <p><strong>1.默认方法</strong></p> <p>通过<code>&lt;script&gt;</code>标签加载 JavaScript 脚本,默认是同步加载执行的,渲染引擎如果遇到<code>&lt;script&gt;</code>会停下来,知道脚本下载执行完成</p> <p><strong>2.异步方法</strong></p> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="<a href="http://www.js-code.com/tag/button" title="button" target="_blank">button</a>" <a href="http://www.js-code.com/tag/class" title="class" target="_blank">class</a>="copyCode code-tool" data-toggle="tooltip" data-placement="<a href="http://www.js-code.com/tag/top" title="top" target="_blank">top</a>" data-clipboard-<a href="http://www.js-code.com/tag/text" title="text" target="_blank">text</a>="<script src=&quot;/lib/test.js&quot; defer></script><br /> <script src=&quot;/lib/test.js&quot; async></script>" title="" data-original-title="复制"></span> </div> </p></div> <pre class="xml hljs"><code class="html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/lib/test.js"</span> <span class="hljs-attr">defer</span>&gt;</span><span class="undefined"></span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/lib/test.js"</span> <span class="hljs-attr">async</span>&gt;</span><span class="undefined"></span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></code></pre> <p><strong>defer 和 async属性</strong></p> <ol> <li>defer 会让该标签引用的脚本在<a href="http://www.js-code.com/tag/dom" title="DOM" target="_blank">DOM</a>完全解析之后,并且引用的其他脚本执行完成之后,才会执行;多个defer会按照在页面上出现的顺序依次执行</li> <li>async 类似于异步回调函数,加载完成或,渲染引擎就会立即停下来去执行该脚本,多个async脚本不能后保证执行的顺序</li> </ol> <h2 id="articleHeader3">CommonJs</h2> <p>Node 的模块系统就是参照着CommonJs规范所实现的</p> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text="const path = require('path') path.join(__dirname,path.sep) " title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="js"><span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path'</span>) path.join(__dir<a href="http://www.js-code.com/tag/name" title="浏览关于“name”的文章" target="_blank" class="tag_link">name</a>,path.sep) </code></pre> <p>path.jo<a href="http://www.js-code.com/tag/in" title="in" target="_blank">in</a> 必然是依赖于path模块加载完成才能使用的,对于服务器来说,因为所有的资源都存放在本地,所以各种模块各种模块加载进来之后再执行先关逻辑对于速度的要求来说并不会是那么明显问题。</p> <p><strong>特点</strong>:</p> <ol> <li>一个文件就是一个模块,拥有单独的作用域;</li> <li>普通方式定义的变量、函数、对象都属于该模块内;</li> <li>通过<strong>require</strong>来加载模块;</li> <li>通过<strong><a href="http://www.js-code.com/tag/export" title="export" target="_blank">export</a>s</strong>和<strong>modul.<a href="http://www.js-code.com/tag/export" title="export" target="_blank">export</a>s</strong>来暴露模块中的内容;</li> <li>模块加载的顺序,按照其在代码中出现的顺序。</li> <li>模块可以多次加载,<strong>但只会在第一次加载的时候运行一次</strong>,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果;模块的加载顺序,按照代码的出现顺序是同步加载的;</li> </ol> <p><strong>require</strong>(同步加载)基本功能:<strong>读取并执行</strong>一个JS文件,然后返回该模块的<a href="http://www.js-code.com/tag/export" title="export" target="_blank">export</a>s对象,如果没有发现指定模块会报错;</p> <p><strong><a href="http://www.js-code.com/tag/export" title="export" target="_blank">export</a>s</strong>:<a href="http://www.js-code.com/tag/node" title="node" target="_blank">node</a>为每个模块提供一个<a href="http://www.js-code.com/tag/export" title="浏览关于“export”的文章" target="_blank" class="tag_link">export</a>s变量,其指向module.exports,相当于在模块头部加了这句话:<a href="http://www.js-code.com/tag/var" title="var" target="_blank">var</a> exports = module.exports,在对外输出时,可以给exports对象添加方法(exports.xxx等同于module.exports.xxx),<strong>不能直接赋值</strong>(因为这样就切断了exports和module.exports的联系);</p> <p><strong>module</strong>变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,<strong>其实是加载该模块的module.exports属性</strong>。</p> <ul> <li> <p>module对象的属性:</p> <ul> <li> <strong>module.id</strong>模块的识别符,通常是带有绝对路径的模块文件名。</li> <li> <strong>module.file<a href="http://www.js-code.com/tag/name" title="name" target="_blank">name</a></strong> 模块的文件名,带有绝对路径。</li> <li> <strong>module.loaded</strong> 返回一个布尔值,表示模块是否已经完成加载。</li> <li> <strong>module.<a href="http://www.js-code.com/tag/parent" title="parent" target="_blank">parent</a></strong> 返回一个对象,表示调用该模块的模块。</li> <li> <strong>module.children</strong> 返回一个<a href="http://www.js-code.com/tag/%e6%95%b0%e7%bb%84" title="数组" target="_blank">数组</a>,表示该模块要用到的其他模块。</li> <li> <strong>module.exports</strong> 表示模块对外输出的值。</li> </ul> </li> </ul> <p>例子:</p> <ul> <li>注意在这种方式下module.exports被重新赋值了,所以之前使用exports导出的hello不再有效(模块头部<strong><a href="http://www.js-code.com/tag/var" title="var" target="_blank">var</a> exports = module.exports</strong>)</li> </ul> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text="exports.hello = function() { return 'hello'; }; module.exports = 'Hello world';/" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="js">exports.hello = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{ <span class="hljs-keyword">return</span> <span class="hljs-string">'hello'</span>; }; <span class="hljs-built_in">module</span>.exports = <span class="hljs-string">'Hello world'</span>;<span class="hljs-regexp">/</span></code></pre> <p>因此一旦<strong>module.exports被赋值了,表明这个模块具有单一出口了</strong></p> <h2 id="articleHeader4">AMD</h2> <p><strong>Asynchronous Module Definition异步加载某模块的规范。</strong>试想如果在浏览器中(资源不再本地)采用commonjs这种完全依赖于先加载再试用方法,那么如果一个模块特别大,网速特别慢的情况下就会出现页面卡顿的情况。便有了异步加载模块的AMD规范。require.js便是基于此规范</p> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text="require(['module1','module2'....], callback); reqire([jquery],function(jquery){ //do something }) //定义模块 define(id, [depends], callback); //id是模块名,可选的依赖别的模块的数组,callback是用于return出一个给别的模块用的函数" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="js"><span class="hljs-built_in">require</span>([<span class="hljs-string">'module1'</span>,<span class="hljs-string">'module2'</span>....], c<a href="http://www.js-code.com/tag/all" title="浏览关于“all”的文章" target="_blank" class="tag_link">all</a>back); reqire([jquery],<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">jquery</span>)</span>{ <span class="hljs-comment">//do something</span> }) <span class="hljs-comment">//定义模块</span> define(id, [depends], callback); <span class="hljs-comment">//id是模块名,可选的依赖别的模块的<a href="http://www.js-code.com/tag/%e6%95%b0%e7%bb%84" title="浏览关于“数组”的文章" target="_blank" class="tag_link">数组</a>,callback是用于return出一个给别的模块用的函数</span></code></pre> <p>熟悉的回调函数形式。</p> <h2 id="articleHeader5">Node的模块实现</h2> <p>Node 对于模块的实现以commonjs为基础的同时也增加了许多自身的特性</p> <ul> <li> <p><strong>Node模块的引入的三个步骤</strong></p> <ul> <li>路径分析</li> <li> <p>文件定位</p> <ul> <li>在<code>require</code>参数中如果不写后缀名,<a href="http://www.js-code.com/tag/node" title="node" target="_blank">node</a>会按照<code>.js</code>,<code>.<a href="http://www.js-code.com/tag/node" title="浏览关于“node”的文章" target="_blank" class="tag_link">node</a></code>,<code>.json</code>的顺序依次补足并<a href="http://www.js-code.com/tag/try" title="try" target="_blank">try</a></li> <li>此过程会调用<code>fs</code>模块同步阻塞式的判断文件是否存在,因此非js文件最后加上后缀</li> </ul> </li> <li> <p>编译执行</p> <ul> <li> <code>.js</code> 文件会被解析为 JavaScript 文本文件,<code>.json</code> 文件会被解析为 JSON 文本文件。 <code>.node</code> 文件会被解析为通过 dl<a href="http://www.js-code.com/tag/open" title="open" target="_blank">open</a> 加载的编译后的插件模块.</li> </ul> </li> </ul> </li> <li> <p><strong>Node的模块分类</strong></p> <ul> <li> <strong>核心模块</strong> Node本身提供的模块,比如<code>path</code>,<code>buffer</code>,<code>http</code>等,在Node编译过程中就加载进内存,因此会省掉文件定位和编译执行两个文件加载步骤</li> <li> <strong>文件模块</strong> 开发人员自己写的模块,会经历完整的模块引入步骤</li> </ul> </li> <li> <strong>Node也会优先从缓存中加载引入过的文件模块</strong>,在Node中第一次加载某一个模块的时候,Node就会缓存下该模块,之后再加载模块就会直接从缓存中取了。这个“潜规则”核心模块和文件模块都会有。</li> </ul> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text="require('./test.js').message='hello' console.log(require.cache); console.log(require('./test.js').message)//hello" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="js"><span class="hljs-built_in">require</span>(<span class="hljs-string">'./test.js'</span>).message=<span class="hljs-string">'hello'</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-built_in">require</span>.cache); <span class="hljs-built_in">console</span>.log(<span class="hljs-built_in">require</span>(<span class="hljs-string">'./test.js'</span>).message)<span class="hljs-comment">//hello</span></code></pre> <p>上述代码说明第二次加载依旧使用了第一次加载进来之后的模块并没有重新加载而是读取了缓存中的模块,因为重新加载的某块中并没有message。打印出来的<strong>require.cache</strong>包含了本模块的module信息和加载进来的模块信息。</p> <p><span class="img-wrap"><img data-src="/img/remote/1460000012999804" src="https://static.segmentfault.com/v-5cc2cd8e/global/img/squares.svg" alt="" title="" style="cursor: pointer;"></span></p> <p>那么如果你想要多次执行某一个模块,要么你手动像下面这样删除该模块的缓存记录之后再重新加载使用,要么应该在模块中暴露一个工厂函数,然后调用那个函数多次执行该模块,与vue-ssr的创建应用实例的工厂函数意思相近。</p> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text="require('./test.js').message='hello' delete require.cache['/absolute-path/test.js'] console.log(require('./test.js').message)//undifined" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="js"><span class="hljs-built_in">require</span>(<span class="hljs-string">'./test.js'</span>).message=<span class="hljs-string">'hello'</span> <span class="hljs-keyword"><a href="http://www.js-code.com/tag/delete" title="浏览关于“delete”的文章" target="_blank" class="tag_link">delete</a></span> <span class="hljs-built_in">require</span>.cache[<span class="hljs-string">'/absolute-path/test.js'</span>] <span class="hljs-built_in">console</span>.log(<span class="hljs-built_in">require</span>(<span class="hljs-string">'./test.js'</span>).message)<span class="hljs-comment">//und<a href="http://www.js-code.com/tag/if" title="浏览关于“if”的文章" target="_blank" class="tag_link">if</a>ined</span></code></pre> <p>可见当删除了相关模块的缓存,再一次加载时则不再有message了。</p> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="<a href="http://www.js-code.com/tag/button" title="button" target="_blank">button</a>" <a href="http://www.js-code.com/tag/class" title="class" target="_blank">class</a>="copyCode code-tool" data-toggle="tooltip" data-placement="<a href="http://www.js-code.com/tag/top" title="top" target="_blank">top</a>" data-clipboard-<a href="http://www.js-code.com/tag/text" title="text" target="_blank">text</a>="// <a href="http://www.js-code.com/tag/vue" title="Vue" target="_blank">Vue</a>-ssr工厂函数,目的是为每个请求创立一个新的应用实例<br /> <a href="http://www.js-code.com/tag/const" title="const" target="_blank">const</a> <a href="http://www.js-code.com/tag/vue" title="Vue" target="_blank">Vue</a> = require('vue')<br /> module.exports = <a href="http://www.js-code.com/tag/function" title="function" target="_blank">function</a> createApp (con<a href="http://www.js-code.com/tag/text" title="浏览关于“text”的文章" target="_blank" class="tag_link">text</a>) {<br /> <a href="http://www.js-code.com/tag/return" title="return" target="_blank">return</a> <a href="http://www.js-code.com/tag/new" title="new" target="_blank">new</a> <a href="http://www.js-code.com/tag/vue" title="浏览关于“Vue”的文章" target="_blank" class="tag_link">Vue</a>({<br /> data: {<br /> url: context.url<br /> },<br /> template: `</p> <div>访问的 URL 是: {{ url }}</div> <p>`<br /> })<br /> }" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="js"><span class="hljs-comment">// Vue-ssr工厂函数,目的是为每个请求创立一个新的应用实例</span> <span class="hljs-keyword">const</span> Vue = <span class="hljs-built_in">require</span>(<span class="hljs-string">'vue'</span>) <span class="hljs-built_in">module</span>.exports = <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createApp</span> (<span class="hljs-params">context</span>) </span>{ <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Vue({ <span class="hljs-attr">data</span>: { <span class="hljs-attr">url</span>: context.url }, <span class="hljs-attr">template</span>: <span class="hljs-string">`&lt;<a href="http://www.js-code.com/tag/div" title="浏览关于“div”的文章" target="_blank" class="tag_link">div</a>&gt;访问的 URL 是: {{ url }}&lt;/div&gt;`</span> }) }</code></pre> <ul> <li><strong>模块包装器</strong></li> </ul> <p>Node在加载模块之后,执行之前则会使用函数包装器将模块代码包装,从而实现<strong>将顶层变量(<code>var</code>,<code><a href="http://www.js-code.com/tag/let" title="let" target="_blank">let</a></code>,<code><a href="http://www.js-code.com/tag/const" title="const" target="_blank">const</a></code>)作用域限制在模块范围内</strong>,<strong>提供每一个特定在该模块的顶层全局变量</strong><code>module</code>,<code>exports</code>,<code>__dir<a href="http://www.js-code.com/tag/name" title="name" target="_blank">name</a></code>(所在文件夹的绝对路径),<code>__filename</code>(绝对路径加上文件名)</p> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text="(function(exports, require, module, __filename, __dirname) { // 模块的代码实际上在这里 });" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="js">(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">exports, require, module, __filename, __dirname</span>) </span>{ <span class="hljs-comment">// 模块的代码实际上在这里</span> });</code></pre> <p>关于模块的具体编译执行过程,这次就不深入讨论了,足够花心思在好好重新深入总结重写一篇了,顺便再次安利朴灵大大的《深入浅出nodejs》</p> <h2 id="articleHeader6">ES6中模块的解决方案</h2> <p>终于,ES6在语言层面上提供了JS一直都没有的模块功能,使得在继Commonjs之于服务端,AMD之于浏览器之外提供了一个通用的解决方案。</p> <p><strong>1.设计思想</strong></p> <p>尽量静态化(<strong>静态加载</strong>),使得编译时就能确定模块间的依赖关系以及输入输出的变量。</p> <p><strong>2.关键语法</strong></p> <ul> <li> <p><strong>export</strong></p> <ul> <li>export可以输出变量:<code>export var a = 1</code> </li> <li>输出函数:`export <a href="http://www.js-code.com/tag/function" title="function" target="_blank">function</a> sum(x, y) {</li> </ul> </li> </ul> <p><a href="http://www.js-code.com/tag/return" title="return" target="_blank">return</a> x + y;<br />};<br />`</p> <ul> <li>输出类:export class A{}</li> <li>结尾大括号写法:export {a , sum , A}</li> <li><strong>尤为注意的一点就是export所导出的接口一定要和模块内部的变量建立一一对应的关系</strong></li> </ul> <p>对于一个模块来说,它就是一个默认使用了严格模式的文件('use strict'),而别的文件要想使用该模块,就必须要求该模块内有export主动导出的内容</p> <p>例子:</p> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text="export 1 //直接导出一个数字是不可以的 var a= 2 export a //间接导出数字也是不可以的! export {a}//正确 export function(){} //错误 function sum(){} export sum //错误 export {sum}//正确" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="js"><span class="hljs-keyword">export</span> <span class="hljs-number">1</span> <span class="hljs-comment">//直接导出一个数字是不可以的</span> <span class="hljs-keyword">var</span> a= <span class="hljs-number">2</span> <span class="hljs-keyword">export</span> a <span class="hljs-comment">//间接导出数字也是不可以的!</span> <span class="hljs-keyword">export</span> {a}<span class="hljs-comment">//正确</span> <span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{} <span class="hljs-comment">//错误</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sum</span>(<span class="hljs-params"></span>)</span>{} <span class="hljs-keyword">export</span> sum <span class="hljs-comment">//错误</span> <span class="hljs-keyword">export</span> {sum}<span class="hljs-comment">//正确</span></code></pre> <p><strong>export个人最为重要的一点就是可以取到模块内的实时的值</strong></p> <p>例子:</p> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text="export var foo = 'bar'; setTimeout(() => foo = 'baz', 500);" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="js"><span class="hljs-keyword">export</span> <span class="hljs-keyword">var</span> foo = <span class="hljs-string">'bar'</span>; <a href="http://www.js-code.com/tag/setTimeout" title="浏览关于“setTimeout”的文章" target="_blank" class="tag_link">setTimeout</a>(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> foo = <span class="hljs-string">'baz'</span>, <span class="hljs-number">500</span>);</code></pre> <p>引用该模块的文件在定时器时间到的时候则会得到改变后的值</p> <ul> <li><strong>export <a href="http://www.js-code.com/tag/default" title="default" target="_blank">default</a></strong></li> </ul> <p>实质: <strong>导出一个叫做<a href="http://www.js-code.com/tag/default" title="default" target="_blank">default</a>(默认的)变量,本质是将后面的值,赋给<a href="http://www.js-code.com/tag/default" title="浏览关于“default”的文章" target="_blank" class="tag_link">default</a>变量</strong>,所以情况就和export 不同了</p> <p><strong>不同点:</strong></p> <ol> <li>export 导出的变量,在<a href="http://www.js-code.com/tag/import" title="import" target="_blank">import</a>的时候必须要知道变量名,否则无法加载,export default就允许随意取名直接加载,并且不用使用大括号;</li> <li>export default 后面不能跟变量声明语句</li> </ol> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text="// 第一组 export default function crc32() {} import crc32 from 'crc32'; // 输入 // 第二组 export function crc32() {}; import {crc32} from 'crc32'; // 输入 export var a = 1;// 正确 var a = 1; export default a;// 正确 export default var a = 1;// 错误" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="js"><span class="hljs-comment">// 第一组</span> <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">crc32</span>(<span class="hljs-params"></span>) </span>{} <span class="hljs-keyword">import</span> crc32 <span class="hljs-keyword">from</span> <span class="hljs-string">'crc32'</span>; <span class="hljs-comment">// 输入</span> <span class="hljs-comment">// 第二组</span> <span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">crc32</span>(<span class="hljs-params"></span>) </span>{}; <span class="hljs-keyword">import</span> {crc32} <span class="hljs-keyword">from</span> <span class="hljs-string">'crc32'</span>; <span class="hljs-comment">// 输入</span> <span class="hljs-keyword">export</span> <span class="hljs-keyword">var</span> a = <span class="hljs-number">1</span>;<span class="hljs-comment">// 正确</span> <span class="hljs-keyword">var</span> a = <span class="hljs-number">1</span>; <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> a;<span class="hljs-comment">// 正确</span> <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">var</span> a = <span class="hljs-number">1</span>;<span class="hljs-comment">// 错误</span></code></pre> <p>export default 每一个模块只允许有一个</p> <ul> <li><strong>import</strong></li> </ul> <p>与导出export对应,引用则是import</p> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text="export {a,b} || / import { a as A ,b as B} from './test.js';" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="js"><span class="hljs-keyword">export</span> {a,b} || / <span class="hljs-keyword">import</span> { a <span class="hljs-keyword">as</span> A ,b <span class="hljs-keyword">as</span> B} <span class="hljs-keyword">from</span> <span class="hljs-string">'./test.js'</span>;</code></pre> <p><strong>主要特点:</strong></p> <p>使用import加载具有提升的效果,即会提到文件头部进行:</p> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text="foo(); import { foo } from 'my_module';" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="js">foo(); <span class="hljs-keyword">import</span> { foo } <span class="hljs-keyword">from</span> <span class="hljs-string">'my_module'</span>;</code></pre> <p>该代码会正常执行。</p> <p>*加载默认加载全部导出的变量</p> <p><code>import * as A from './a.js'</code></p> <p><strong>import 加载进来的变量是不允许改变的。</strong></p> <h3 id="articleHeader7">浏览器对<a href="http://www.js-code.com/tag/es6%e6%a8%a1%e5%9d%97" title="ES6模块" target="_blank">ES6模块</a>的加载</h3> <p><strong>type='module',此时浏览器就会知道这是<a href="http://www.js-code.com/tag/es6%e6%a8%a1%e5%9d%97" title="ES6模块" target="_blank">ES6模块</a>,同时会自动给他加上前文提到的defer属性,即等到所有的渲染操作都执行完成之后,才会执行该模块</strong></p> <p><code>&lt;script type="module" src="./test.js"&gt;&lt;/script&gt;</code></p> <h3 id="articleHeader8">Node 对<a href="http://www.js-code.com/tag/es6%e6%a8%a1%e5%9d%97" title="浏览关于“ES6模块”的文章" target="_blank" class="tag_link">ES6模块</a>的加载</h3> <p>由于Node有自己的模块加载机制,所以在Node8.5以上版本将两种方式的加载分开来处理,对于加载ES6的模块,node要求其后缀名得是<code>.mjs</code>,然后还得加上<code>--experimental-modules</code>参数,然后两种机制还不能混用。确实还是很麻烦的,所以现在Node端想用import主流还是用<a href="http://www.js-code.com/tag/babel" title="babel" target="_blank">babel</a>转义。</p> <h2 id="articleHeader9">对比ES6 module和Node的commonjs</h2> <h3 id="articleHeader10">差异:</h3> <ul> <li><strong>静态加载VS运行时加载</strong></li> </ul> <p>首先看下面一段代码:</p> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text="if (x > 2) {<br /> import A from './a.js';<br /> }else{<br /> import B from './b.js';<br /> }" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="js"><span class="hljs-keyword">if</span> (x &gt; <span class="hljs-number">2</span>) { <span class="hljs-keyword">import</span> A <span class="hljs-keyword">from</span> <span class="hljs-string">'./a.js'</span>; }<span class="hljs-keyword"><a href="http://www.js-code.com/tag/else" title="浏览关于“else”的文章" target="_blank" class="tag_link">else</a></span>{ <span class="hljs-keyword">import</span> B <span class="hljs-keyword">from</span> <span class="hljs-string">'./b.js'</span>; }</code></pre> <p>这段代码会报错,因为JS引擎在处理import是在编译时期,此时不会去执行条件语句,因此这段代码会出现句法错误,相反,如果换成:</p> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text="if (x > 2) {<br /> const A =require('./a.js');<br /> }else{<br /> const B =require('./b.js');<br /> }" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="js"><span class="hljs-keyword">if</span> (x &gt; <span class="hljs-number">2</span>) { <span class="hljs-keyword">const</span> A =<span class="hljs-built_in">require</span>(<span class="hljs-string">'./a.js'</span>); }<span class="hljs-keyword">else</span>{ <span class="hljs-keyword">const</span> B =<span class="hljs-built_in">require</span>(<span class="hljs-string">'./b.js'</span>); }</code></pre> <p>commonjs是在运行时加载模块,因此上面代码就会成功运行</p> <p>由于动态加载功能的要求,才会有了import()函数的提案,这里就不过多赘述。</p> <ul> <li>值的引用VS值的拷贝</li> </ul> <p><strong>commonjs模块在加载之后会把原始类型的值缓存,之后该模块的内部变化则不会再影响到其输出的值</strong>:</p> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text="//test.js var counter = 3; function incCounter() { counter++; } module.exports = { counter: counter, incCounter: incCounter, }; ================================== //main.js var test = require('./test'); console.log(test.counter); // 3 test.incCounter(); console.log(test.counter); // 3" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="js"><span class="hljs-comment">//test.js</span> <span class="hljs-keyword">var</span> counter = <span class="hljs-number">3</span>; <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">incCounter</span>(<span class="hljs-params"></span>) </span>{ counter++; } <span class="hljs-built_in">module</span>.exports = { <span class="hljs-attr">counter</span>: counter, <span class="hljs-attr">incCounter</span>: incCounter, }; ================================== <span class="hljs-comment">//main.js</span> <span class="hljs-keyword">var</span> test = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./test'</span>); <span class="hljs-built_in">console</span>.log(test.counter); <span class="hljs-comment">// 3</span> test.incCounter(); <span class="hljs-built_in">console</span>.log(test.counter); <span class="hljs-comment">// 3</span></code></pre> <p>ES6的模块机制,在引擎静态分析阶段会把import当成是一种<strong>只读引用(地址是只读的<a href="http://www.js-code.com/tag/const" title="const" target="_blank">const</a>,因此不可以在引用该模块的文件里给他重新赋值)</strong>,等到代码实际运行时,才会根据引用去取值</p> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text="// test.js export let counter = 3; export function incCounter() { counter++; } // main.js import { counter, incCounter } from './test'; console.log(counter); // 3 incCounter(); console.log(counter); // 4" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="js"><span class="hljs-comment">// test.js</span> <span class="hljs-keyword">export</span> <span class="hljs-keyword"><a href="http://www.js-code.com/tag/let" title="浏览关于“let”的文章" target="_blank" class="tag_link">let</a></span> counter = <span class="hljs-number">3</span>; <span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">incCounter</span>(<span class="hljs-params"></span>) </span>{ counter++; } <span class="hljs-comment">// main.js</span> <span class="hljs-keyword">import</span> { counter, incCounter } <span class="hljs-keyword">from</span> <span class="hljs-string">'./test'</span>; <span class="hljs-built_in">console</span>.log(counter); <span class="hljs-comment">// 3</span> incCounter(); <span class="hljs-built_in">console</span>.log(counter); <span class="hljs-comment">// 4</span></code></pre> <h3 id="articleHeader11">循环加载问题</h3> <p>循环加载指的是,a文件依赖于b文件,而b文件又依赖于a文件</p> <ul> <li><strong>commonjs的循环加载问题</strong></li> </ul> <p>commonjs是在加载时执行的,他在require的时候就会全部跑一遍,因此他在遇到循环加载的情况就会只输出已经执行的部分,而之后的部分则不会输出,下面是一个例子:</p> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text="//parent文件 exports.flag = 1; let children = require('./children')//停下来,加载chilren console.log(`parent文件中chilren的flag =${children.flag}`); exports.flag = 2 console.log(`parent文件执行完毕了`); ========================================================= //test2文件 exports.flag = 1; let parent = require('./parent')//停下来,加载parent,此时parent只执行到了第一行,导出结果flag ==1 console.log(`children文件中parent的flag =${parent.flag}`); exports.flag = 2 console.log(`children文件执行完毕了`); " title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="js"><span class="hljs-comment">//<a href="http://www.js-code.com/tag/parent" title="浏览关于“parent”的文章" target="_blank" class="tag_link">parent</a>文件</span> exports.flag = <span class="hljs-number">1</span>; <span class="hljs-keyword">let</span> children = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./children'</span>)<span class="hljs-comment">//停下来,加载chilren</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`parent文件中chilren的flag =<span class="hljs-subst">${children.flag}</span>`</span>); exports.flag = <span class="hljs-number">2</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`parent文件执行完毕了`</span>); ========================================================= <span class="hljs-comment">//test2文件</span> exports.flag = <span class="hljs-number">1</span>; <span class="hljs-keyword">let</span> parent = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./parent'</span>)<span class="hljs-comment">//停下来,加载parent,此时parent只执行到了第一行,导出结果flag ==1</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`children文件中parent的flag =<span class="hljs-subst">${parent.flag}</span>`</span>); exports.flag = <span class="hljs-number">2</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`children文件执行完毕了`</span>); </code></pre> <p><code>node <a href="http://www.js-code.com/tag/parent" title="parent" target="_blank">parent</a></code>之后运行结果为</p> <p><span class="img-wrap"><img data-src="/img/remote/1460000012999805" src="https://static.segmentfault.com/v-5cc2cd8e/global/img/squares.svg" alt="Commonjs循环加载" title="Commonjs循环加载" style="cursor: pointer;"></span></p> <p>运行parent之后会在第一行导出flag=1,然后去<code>ruquire</code>children文件,此时parent进行等待,等待children文件执行结束,children开始执行到第二行的时候出现“循环加载”parent文件,此时系统自动去找parent文件的exports属性,而parent只执行了一行,但是好在它有exports了flag,所以children文件加再进来了那个flag并继续执行,第三行不会报错,最后在第四行children导出了flag=2,此时parent再接着执行到结束。</p> <ul> <li><strong>ES6中的循环加载问题</strong></li> </ul> <p>ES6和commonjs本质上不同!因为ES6是<strong>引用取值</strong>,即动态引用</p> <p>引用阮一峰老师<a href="http://es6.ruanyifeng.com/#docs/module" rel="nofollow noreferrer" target="_blank">ES6标准入门</a>的例子</p> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text="// a.mjs import {bar} from './b'; console.log('a.mjs'); console.log(bar); export let foo = 'foo'; // b.mjs import {foo} from './a'; console.log('b.mjs'); console.log(foo); export let bar = 'bar';" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="js"><span class="hljs-comment">// a.mjs</span> <span class="hljs-keyword">import</span> {bar} <span class="hljs-keyword">from</span> <span class="hljs-string">'./b'</span>; <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'a.mjs'</span>); <span class="hljs-built_in">console</span>.log(bar); <span class="hljs-keyword">export</span> <span class="hljs-keyword">let</span> foo = <span class="hljs-string">'foo'</span>; <span class="hljs-comment">// b.mjs</span> <span class="hljs-keyword">import</span> {foo} <span class="hljs-keyword">from</span> <span class="hljs-string">'./a'</span>; <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'b.mjs'</span>); <span class="hljs-built_in">console</span>.log(foo); <span class="hljs-keyword">export</span> <span class="hljs-keyword">let</span> bar = <span class="hljs-string">'bar'</span>;</code></pre> <p>执行后的结果:</p> <p><span class="img-wrap"><img data-src="/img/remote/1460000012999806" src="https://static.segmentfault.com/v-5cc2cd8e/global/img/squares.svg" alt="ES6循环加载出错" title="ES6循环加载出错" style="cursor: pointer;"></span></p> <p>执行的过程是当a文件防线import了b文件之后就会去执行b文件,到了b文件这边看到了他又引用了a文件,并不会又去执行a文件发生“张郎送李郎”的故事,而是倔强得认为foo这个接口已经存在了,于是就继续执行下去,直到在要引用<code>foo</code>的时候发现<code>foo</code>还没有定义,因为<a href="http://www.js-code.com/tag/let" title="let" target="_blank">let</a>定义变量会出现"暂时性死区",不可以还没定义就使用,其实如果改成var声明,有个变量提升作用就不会报错了。改成var声明foo<code>export <a href="http://www.js-code.com/tag/let" title="let" target="_blank">let</a> foo = 'foo';</code></p> <p><span class="img-wrap"><img data-src="/img/remote/1460000012999807" src="https://static.segmentfault.com/v-5cc2cd8e/global/img/squares.svg" alt="ES6循环加载换成var" title="ES6循环加载换成var" style="cursor: pointer;"></span></p> <p>虽然打印的foo是und<a href="http://www.js-code.com/tag/if" title="if" target="_blank">if</a>ined但是并没有影响程序执行,但最好的做法是,改成同样有提升作用的<a href="http://www.js-code.com/tag/function" title="function" target="_blank">function</a>来声明。最后去执行函数来获得值,最后得到了希望的结果</p> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text="// a.mjs import {bar} from './b'; console.log('a.mjs'); console.log(bar()); export function foo() { return 'foo' }; // b.mjs import {foo} from './a'; console.log('b.mjs'); console.log(foo()); export function bar() { return 'bar' };" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="js"><span class="hljs-comment">// a.mjs</span> <span class="hljs-keyword">import</span> {bar} <span class="hljs-keyword">from</span> <span class="hljs-string">'./b'</span>; <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'a.mjs'</span>); <span class="hljs-built_in">console</span>.log(bar()); <span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">foo</span>(<span class="hljs-params"></span>) </span>{ <span class="hljs-keyword">return</span> <span class="hljs-string">'foo'</span> }; <span class="hljs-comment">// b.mjs</span> <span class="hljs-keyword">import</span> {foo} <span class="hljs-keyword">from</span> <span class="hljs-string">'./a'</span>; <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'b.mjs'</span>); <span class="hljs-built_in">console</span>.log(foo()); <span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">bar</span>(<span class="hljs-params"></span>) </span>{ <span class="hljs-keyword">return</span> <span class="hljs-string">'bar'</span> };</code></pre> <p><span class="img-wrap"><img data-src="/img/remote/1460000012999808" src="https://static.segmentfault.com/v-5cc2cd8e/global/img/squares.svg" alt="ES6循环加载正确" title="ES6循环加载正确" style="cursor: pointer;"></span></p> <h2 id="articleHeader12">结束语</h2> <p>其实关于模块还有很多东西还没有梳理总结到,比如node模块的加载过程的细节,和编译过程,再比如如何自己写一个npm模块发布等等都是很值得去梳理总结的,这一次就先到这吧,总之,第一次在自己的SF正儿八经的写这么长的技术总结博客,组织内容上感觉比较凌乱,还有很多的不足。希望自己以后多多总结提高吧。最后当然还是要感谢开源,感谢提供了那么多优秀资料的前辈们。也欢迎来我的博客网站(<a href="https://isliulei.com)%E6%8C%87%E6%95%99" rel="nofollow noreferrer" target="_blank">https://isliulei.com)指教</a>。</p> <blockquote><p>参考文章:<br /><a href="http://es6.ruanyifeng.com/#docs/module" rel="nofollow noreferrer" target="_blank">ES6标准入门--阮一峰</a><br /><a href="https://nodejs.org/dist/latest-v8.x/docs/api/" rel="nofollow noreferrer" target="_blank">Nodejs v8.9.4 官方文档</a><br />《深入浅出Nodejs》---朴灵<br /><a href="http://javascript.ruanyifeng.com/nodejs/module.html" rel="nofollow noreferrer" target="_blank">Commonjs规范</a> </p></blockquote> <p></code></p>

总结

以上是脚本宝典为你收集整理的

带你了解JavaScript相关的模块机制

全部内容,希望文章能够帮你解决

带你了解JavaScript相关的模块机制

所遇到的程序开发问题,欢迎加入QQ群277859234一起讨论学习。如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典网站推荐给程序员好友。 本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。

80%的人都看过