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

JavaScript 模块演化简史

脚本宝典小编觉得挺不错的,现在分享给大家,也给大家做个参考,希望能帮助你少写一行代码,多一份安全和惬意。
<p><code></p> <blockquote> <p><a href="https://zhuanlan.zhihu.com/p/26231889" rel="nofollow noreferrer" target="_blank">JavaScript 模块演化简史</a> 从属于笔者的 <a href="https://github.com/wxyyxc1992/Web-Development-And-Engineering-Practices" rel="nofollow noreferrer" target="_blank">Web 开发基础与工程实践</a>。本文主要总结自 <a href="https://parg.co/bhn" rel="nofollow noreferrer" target="_blank">The Evolution of JavaScript Modularity</a>、<a href="https://blog.hospodarets.com/native-ecmascript-modules-the-first-overview" rel="nofollow noreferrer" target="_blank">Native ECMAScript modules - the first overview</a>、<a href="https://blog.hospodarets.com/native-ecmascript-modules-new-features" rel="nofollow noreferrer" target="_blank">Native ECMAScript modules: the new features and differences from Webpack modules</a> 等 <a href="https://parg.co/b45" rel="nofollow noreferrer" target="_blank">JavaScript 语法学习资料索引</a> 中注明的文章,更多深度思考参阅 <a href="https://zhuanlan.zhihu.com/p/24575395" rel="nofollow noreferrer" target="_blank">2016-我的前端之路:工具化与工程化</a>。</p> </blockquote> <h1 id="articleHeader0">JavaScript 模块化</h1> <p>当年 Brendan Eich 草创 JavaScript 之际,他应该无法想象 JavaScript 在未来二十年内发挥的巨大作用;同样作为广为诟病的过于随意的语言,缺乏强有力的模块化解决方案一直是 JavaScript 的缺陷之一。早期的 JavaScript 往往作为嵌入到 <a href="http://www.js-code.com/tag/html" title="HTML" target="_blank">HTML</a> 页面中的用于控制动画与简单的用户交互的脚本语言,我们习惯于将其直接嵌入到 <code>script</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="<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>="<!--html--><br /> <script type=&quot;application/javascript&quot;> // module1 code // module2 code </script>" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs xml"><code><span class="hljs-comment">&lt;!--html--&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"application/<a href="http://www.js-code.com/tag/java" title="浏览关于“java”的文章" target="_blank" class="tag_link">java</a>script"</span>&gt;</span><span class="actionscript"> <span class="hljs-comment">// module1 code</span> <span class="hljs-comment">// module2 code</span> </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></code></pre> <p>不过随着单页应用与富客户端的流行,不断增长的代码库也急需合理的代码分割与依赖管理的解决方案,这也就是我们在软件工程领域所熟悉的模块化(Modularity)。所谓模块化主要是解决代码分割、作用域隔离、模块之间的依赖管理以及发布到生产环境时的自动化打包与处理等多个方面。二十年间流行过的 JavaScript 模块化解决方案包括但不限于直接声明依赖(Directly Def<a href="http://www.js-code.com/tag/in" title="in" target="_blank">in</a>ed Dependences)、命名空间(Namespace Pattern)、模块模式(Module Pattern)、依赖分离定义(Detached Dependency Def<a href="http://www.js-code.com/tag/in" title="in" target="_blank">in</a>itions)、沙盒(Sandbox)、依赖注入(Dependency Injection)、CommonJS、AMD、UMD、标签化模块(Labeled Modules)、YModules、ES 2015 Modules。<br />在早期的 Web 开发中,所有的嵌入到网页内的 JavaScript 对象都会使用全局的 <code>w<a href="http://www.js-code.com/tag/in" title="浏览关于“in”的文章" target="_blank" class="tag_link">in</a><a href="http://www.js-code.com/tag/do" title="do" target="_blank">do</a>w</code> 对象来存放未使用 <code><a href="http://www.js-code.com/tag/var" title="var" target="_blank">var</a></code> 定义的变量。大概在上世纪末,JavaScript 多用于解决简单的任务,这也就意味着我们只需编写少量的 JavaScript 代码;不过随着代码库的线性增长,我们首先会碰到的就是所谓命名冲突(Name Collisions)困境:</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="// file greeting.js var helloInLang = { en: 'Hello world!', es: '¡Hola mundo!', ru: 'Привет мир!' }; function writeHello(lang) { document.write(helloInLang[lang]); } // file hello.js function writeHello() { document.write('The script is broken'); }" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs javascript"><code><span class="hljs-comment">// file greeting.js</span> <span class="hljs-keyword"><a href="http://www.js-code.com/tag/var" title="浏览关于“var”的文章" target="_blank" class="tag_link">var</a></span> helloInLang = { <span class="hljs-attr">en</span>: <span class="hljs-string">'Hello world!'</span>, <span class="hljs-attr">es</span>: <span class="hljs-string">'¡Hola mun<a href="http://www.js-code.com/tag/do" title="浏览关于“do”的文章" target="_blank" class="tag_link">do</a>!'</span>, <span class="hljs-attr">ru</span>: <span class="hljs-string">'Привет мир!'</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-title">writeHello</span>(<span class="hljs-params">lang</span>) </span>{ <span class="hljs-built_in"><a href="http://www.js-code.com/tag/document" title="浏览关于“document”的文章" target="_blank" class="tag_link">document</a></span>.write(helloInLang[lang]); } <span class="hljs-comment">// file hello.js</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">writeHello</span>(<span class="hljs-params"></span>) </span>{ <span class="hljs-built_in">document</span>.write(<span class="hljs-string">'The script is broken'</span>); }</code></pre> <p>当我们在页面内同时引入这两个 JavaScript 脚本文件时,显而易见两个文件中定义的 <code>writeHello</code> 函数起了冲突,最后调用的函数取决于我们引入的先后顺序。此外在大型应用中,我们不可能将所有的代码写入到单个 JavaScript 文件中;我们也不可能手动地在 <a href="http://www.js-code.com/tag/html" title="HTML" target="_blank">HTML</a> 文件中引入全部的脚本文件,特别是此时还存在着模块间依赖的问题,相信很多开发者都会遇到 <code><a href="http://www.js-code.com/tag/jquery" title="jQuery" target="_blank">jQuery</a></code> 尚未定义这样的问题。不过物极必反,过度碎片化的模块同样会带来性能的损耗与包体尺寸的增大,这包括了模块加载、模块解析、因为 Webpack 等打包工具包裹模块时封装的过多 IIFE 函数导致的 JavaScript 引擎优化失败等。譬如我们的源码如下:</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="// index.js var total = 0 total += require('./module_0') total += require('./module_1') total += require('./module_2') // etc. console.log(total) // module_0.js module.exports = 0 // module_1.js module.exports = 1" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs typescript"><code><span class="hljs-comment">// index.js</span> <span class="hljs-keyword">var</span> total = <span class="hljs-number">0</span> total += <span class="hljs-built_in">require</span>(<span class="hljs-string">'./module_0'</span>) total += <span class="hljs-built_in">require</span>(<span class="hljs-string">'./module_1'</span>) total += <span class="hljs-built_in">require</span>(<span class="hljs-string">'./module_2'</span>) <span class="hljs-comment">// etc.</span> <span class="hljs-built_in">console</span>.log(total) <span class="hljs-comment">// module_0.js</span> <span class="hljs-built_in">module</span>.<a href="http://www.js-code.com/tag/export" title="浏览关于“export”的文章" target="_blank" class="tag_link">export</a>s = <span class="hljs-number">0</span> <span class="hljs-comment">// module_1.js</span> <span class="hljs-built_in">module</span>.exports = <span class="hljs-number">1</span></code></pre> <p>经过 Browser<a href="http://www.js-code.com/tag/if" title="if" target="_blank">if</a>y 打包之后的代码变成了如下式样:</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/function" title="function" target="_blank">function</a> e(t,n,r){<a href="http://www.js-code.com/tag/function" title="function" target="_blank">function</a> s(o,u){<a href="http://www.js-code.com/tag/if" title="if" target="_blank">if</a>(!n[o]){<a href="http://www.js-code.com/tag/if" title="浏览关于“if”的文章" target="_blank" class="tag_link">if</a>(!t[o]){<a href="http://www.js-code.com/tag/var" title="var" target="_blank">var</a> a=<a href="http://www.js-code.com/tag/typeof" title="typeof" target="_blank">typeof</a> require==&quot;<a href="http://www.js-code.com/tag/function" title="function" target="_blank">function</a>&quot;&amp;&amp;require;if(!u&amp;&amp;a)<a href="http://www.js-code.com/tag/return" title="return" target="_blank">return</a> a(o,!0);if(i)<a href="http://www.js-code.com/tag/return" title="return" target="_blank">return</a> i(o,!0);var f=<a href="http://www.js-code.com/tag/new" title="new" target="_blank">new</a> Error(&quot;Cannot find module '&quot;+o+&quot;'&quot;);<a href="http://www.js-code.com/tag/throw" title="throw" target="_blank">throw</a> f.code=&quot;MODULE_NOT_FOUND&quot;,f}var l=n[o]={<a href="http://www.js-code.com/tag/export" title="export" target="_blank">export</a>s:{}};t[o][0].c<a href="http://www.js-code.com/tag/all" title="all" target="_blank">all</a>(l.<a href="http://www.js-code.com/tag/export" title="export" target="_blank">export</a>s,<a href="http://www.js-code.com/tag/function" title="function" target="_blank">function</a>(e){var n=t[o][1][e];<a href="http://www.js-code.com/tag/return" title="return" target="_blank">return</a> s(n?n:e)},l,l.<a href="http://www.js-code.com/tag/export" title="export" target="_blank">export</a>s,e,t,n,r)}<a href="http://www.js-code.com/tag/return" title="return" target="_blank">return</a> n[o].<a href="http://www.js-code.com/tag/export" title="export" target="_blank">export</a>s}var i=<a href="http://www.js-code.com/tag/typeof" title="typeof" target="_blank">typeof</a> require==&quot;function&quot;&amp;&amp;require;<a href="http://www.js-code.com/tag/for" title="for" target="_blank">for</a>(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ module.exports = 0 },{}],2:[function(require,module,exports){ module.exports = 1 },{}],3:[function(require,module,exports){ module.exports = 10 },{}],4:[function(require,module,exports){ module.exports = 100 // etc." title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs typescript"><code>(<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">e</span>(<span class="hljs-params">t,n,r</span>)</span>{<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">s</span>(<span class="hljs-params">o,u</span>)</span>{<span class="hljs-keyword">if</span>(!n[o]){<span class="hljs-keyword">if</span>(!t[o]){<span class="hljs-keyword">var</span> a=<span class="hljs-keyword"><a href="http://www.js-code.com/tag/typeof" title="浏览关于“typeof”的文章" target="_blank" class="tag_link">typeof</a></span> <span class="hljs-built_in">require</span>==<span class="hljs-string">"function"</span>&amp;&amp;<span class="hljs-built_in">require</span>;<span class="hljs-keyword">if</span>(!u&amp;&amp;a)<span class="hljs-keyword"><a href="http://www.js-code.com/tag/return" title="浏览关于“return”的文章" target="_blank" class="tag_link">return</a></span> a(o,!<span class="hljs-number">0</span>);<span class="hljs-keyword">if</span>(i)<span class="hljs-keyword">return</span> i(o,!<span class="hljs-number">0</span>);<span class="hljs-keyword">var</span> f=<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">Error</span>(<span class="hljs-string">"Cannot find module '"</span>+o+<span class="hljs-string">"'"</span>);<span class="hljs-keyword"><a href="http://www.js-code.com/tag/throw" title="浏览关于“throw”的文章" target="_blank" class="tag_link">throw</a></span> f.code=<span class="hljs-string">"MODULE_NOT_FOUND"</span>,f}<span class="hljs-keyword">var</span> l=n[o]={exports:{}};t[o][<span class="hljs-number">0</span>].c<a href="http://www.js-code.com/tag/all" title="浏览关于“all”的文章" target="_blank" class="tag_link">all</a>(l.exports,<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">e</span>)</span>{<span class="hljs-keyword">var</span> n=t[o][<span class="hljs-number">1</span>][e];<span class="hljs-keyword">return</span> s(n?n:e)},l,l.exports,e,t,n,r)}<span class="hljs-keyword">return</span> n[o].exports}<span class="hljs-keyword">var</span> i=<span class="hljs-keyword">typeof</span> <span class="hljs-built_in">require</span>==<span class="hljs-string">"function"</span>&amp;&amp;<span class="hljs-built_in">require</span>;<span class="hljs-keyword"><a href="http://www.js-code.com/tag/for" title="浏览关于“for”的文章" target="_blank" class="tag_link">for</a></span>(<span class="hljs-keyword">var</span> o=<span class="hljs-number">0</span>;o&lt;r.<a href="http://www.js-code.com/tag/length" title="浏览关于“length”的文章" target="_blank" class="tag_link">length</a>;o++)s(r[o]);<span class="hljs-keyword">return</span> s})({<span class="hljs-number">1</span>:[<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"><span class="hljs-built_in">require</span>,<span class="hljs-built_in">module</span>,exports</span>)</span>{ <span class="hljs-built_in">module</span>.exports = <span class="hljs-number">0</span> },{}],<span class="hljs-number">2</span>:[<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"><span class="hljs-built_in">require</span>,<span class="hljs-built_in">module</span>,exports</span>)</span>{ <span class="hljs-built_in">module</span>.exports = <span class="hljs-number">1</span> },{}],<span class="hljs-number">3</span>:[<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"><span class="hljs-built_in">require</span>,<span class="hljs-built_in">module</span>,exports</span>)</span>{ <span class="hljs-built_in">module</span>.exports = <span class="hljs-number">10</span> },{}],<span class="hljs-number">4</span>:[<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"><span class="hljs-built_in">require</span>,<span class="hljs-built_in">module</span>,exports</span>)</span>{ <span class="hljs-built_in">module</span>.exports = <span class="hljs-number">100</span> <span class="hljs-comment">// etc.</span></code></pre> <p>我们分别测试 100、1000 与 5000 模块,可以发现随着模块数目的增长最后的包体大小并非线性增长:<br /><span class="img-wrap"><img data-src="/img/remote/1460000008979687?w=970&amp;h=587" src="https://static.segmentfault.com/v-5cc2cd8e/global/img/squares.svg" alt="" title="" style="cursor: pointer;"></span></p> <h1 id="articleHeader1">命名空间模式</h1> <p>命名空间模式始于 2002 年,顾名思义我们可以使用特殊的约定命名。譬如我们可以为某个模块内的变量统一添加 <code>myApp_</code> 前缀,譬如 <code>myApp_address</code>,<code>myApp_validateUser()</code> 等等。同样,我们也可以将函数赋值给模块内的变量或者对象的属性,从而可以使得可以像 <code><a href="http://www.js-code.com/tag/document" title="document" target="_blank">document</a>.write()</code> 这样在子命名空间下定义函数而避免冲突。首个采样该设计模式的界面库当属 Bin<a href="http://www.js-code.com/tag/do" title="do" target="_blank">do</a>ws,其是 Erik Arvidsson 创建于 2002 年。他没有简单地为自定义函数或者对象添加命名前缀,而是将所有的 Bindows 当中的数据与逻辑代码封装于某个全局对象内,从而避免所谓的全局作用域污染。命名空间模式的设计思想如下所示:</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="// file app.js var app = {}; // file greeting.js app.helloInLang = { en: 'Hello world!', es: '¡Hola mundo!', ru: 'Привет мир!' }; // file hello.js app.writeHello = function (lang) { document.write(app.helloInLang[lang]); };" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs javascript"><code><span class="hljs-comment">// file app.js</span> <span class="hljs-keyword">var</span> app = {}; <span class="hljs-comment">// file greeting.js</span> app.helloInLang = { <span class="hljs-attr">en</span>: <span class="hljs-string">'Hello world!'</span>, <span class="hljs-attr">es</span>: <span class="hljs-string">'¡Hola mundo!'</span>, <span class="hljs-attr">ru</span>: <span class="hljs-string">'Привет мир!'</span> }; <span class="hljs-comment">// file hello.js</span> app.writeHello = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">lang</span>) </span>{ <span class="hljs-built_in">document</span>.write(app.helloInLang[lang]); };</code></pre> <p>我们可以发现自定义代码中的所有数据对象与函数都归属于全局对象 <code>app</code>,不过显而易见这种方式对于大型多人协同项目的可维护性还是较差,并且没有解决模块间依赖管理的问题。另外有时候我们需要处理一些自动执行的 Pollyfill 性质的代码,就需要将模块包裹在自调用的函数中,譬如在某个大型应用中,我们的代码可能会切分为如下几个模块:</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="// polyfill-vendor.js (function(){ // polyfills-vendor code }()); // module1.js function module1(params){ // module1 code return module1; } // module3.js function module3(params){ this.a = params.a; } module3.prototype.getA = function(){ return this.a; }; // app.js var APP = {}; if(isModule1Needed){ APP.module1 = module1({param1:1}); } APP.module3 = new module3({a: 42});" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs actionscript"><code><span class="hljs-comment">// polyfill-vendor.js</span> (<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span></span>{ <span class="hljs-comment">// polyfills-vendor code</span> }()); <span class="hljs-comment">// module1.js</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">module1</span><span class="hljs-params">(params)</span></span>{ <span class="hljs-comment">// module1 code</span> <span class="hljs-keyword">return</span> module1; } <span class="hljs-comment">// module3.js</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">module3</span><span class="hljs-params">(params)</span></span>{ <span class="hljs-keyword"><a href="http://www.js-code.com/tag/this" title="浏览关于“this”的文章" target="_blank" class="tag_link">this</a></span>.a = params.a; } module3.<a href="http://www.js-code.com/tag/prototype" title="浏览关于“prototype”的文章" target="_blank" class="tag_link">prototype</a>.getA = <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-keyword">this</span>.a; }; <span class="hljs-comment">// app.js</span> <span class="hljs-keyword">var</span> APP = {}; <span class="hljs-keyword">if</span>(isModule1Needed){ APP.module1 = module1({param1:<span class="hljs-number">1</span>}); } APP.module3 = <span class="hljs-keyword">new</span> module3({a: <span class="hljs-number">42</span>});</code></pre> <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="<a href="http://www.js-code.com/tag/button" title="浏览关于“button”的文章" target="_blank" class="tag_link">button</a>" class="copyCode code-tool" data-toggle="tooltip" data-placement="<a href="http://www.js-code.com/tag/top" title="浏览关于“top”的文章" target="_blank" class="tag_link">top</a>" data-clipboard-<a href="http://www.js-code.com/tag/text" title="浏览关于“text”的文章" target="_blank" class="tag_link">text</a>="<!--html--><br /> <script type=&quot;application/javascript&quot; src=&quot;PATH/polyfill-vendor.js&quot; ></script><br /> <script type=&quot;application/javascript&quot; src=&quot;PATH/module1.js&quot; ></script><br /> <script type=&quot;application/javascript&quot; src=&quot;PATH/module2.js&quot; ></script><br /> <script type=&quot;application/javascript&quot; src=&quot;PATH/app.js&quot; ></script>" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs xml"><code><span class="hljs-comment">&lt;!--html--&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"application/javascript"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"PATH/polyfill-vendor.js"</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">type</span>=<span class="hljs-string">"application/javascript"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"PATH/module1.js"</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">type</span>=<span class="hljs-string">"application/javascript"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"PATH/module2.js"</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">type</span>=<span class="hljs-string">"application/javascript"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"PATH/app.js"</span> &gt;</span><span class="undefined"></span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></code></pre> <p>不过这种方式对于模块间通信也是个麻烦。命名空间模式算是如今 JavaScript 领域最为著名的模式之一,而在 Bindows 之后 Dojo(2005),YUI(2005) 这些优秀的界面框架也是承袭了这种思想。</p> <h1 id="articleHeader2">依赖注入</h1> <p>Martin Fowler 于 2004 年提出了依赖注入(<a href="https://martinfowler.com/articles/injection.html" rel="nofollow noreferrer" target="_blank">Dependency Injection</a>)的概念,其主要用于 Java 中的组件内通信;以 Spring 为代表的一系列支持依赖注入与控制反转的框架将这种设计模式发扬光大,并且成为了 Java 服务端开发的标准模式之一。依赖注入的核心思想在于某个模块不需要手动地初始化某个依赖对象,而只需要声明该依赖并由外部框架自动实例化该对象实现并且传递到模块内。而五年之后的 2009 年 <a href="https://github.com/mhevery" rel="nofollow noreferrer" target="_blank">Misko Hevery</a> 开始设计新的 JavaScript 框架,并且使用了依赖注入作为其组件间通信的核心机制。这个框架就是引领一时风骚,甚至于说是现代 Web 开发先驱之一的 <a href="http://www.js-code.com/tag/angular" title="Angular" target="_blank">Angular</a>。<a href="http://www.js-code.com/tag/angular" title="Angular" target="_blank">Angular</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="// file greeting.js angular.module('greeter', []) .value('greeting', { helloInLang: { en: 'Hello world!', es: '¡Hola mundo!', ru: 'Привет мир!' }, sayHello: function(lang) { return this.helloInLang[lang]; } }); // file app.js angular.module('app', ['greeter']) .controller('GreetingController', ['$scope', 'greeting', function($scope, greeting) { $scope.phrase = greeting.sayHello('en'); }]);" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs php"><code><span class="hljs-comment">// file greeting.js</span> angular.module(<span class="hljs-string">'greeter'</span>, []) .value(<span class="hljs-string">'greeting'</span>, { helloInLang: { en: <span class="hljs-string">'Hello world!'</span>, es: <span class="hljs-string">'¡Hola mundo!'</span>, ru: <span class="hljs-string">'Привет мир!'</span> }, sayHello: <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(lang)</span> </span>{ <span class="hljs-keyword">return</span> this.helloInLang[lang]; } }); <span class="hljs-comment">// file app.js</span> angular.module(<span class="hljs-string">'app'</span>, [<span class="hljs-string">'greeter'</span>]) .controller(<span class="hljs-string">'GreetingController'</span>, [<span class="hljs-string">'$scope'</span>, <span class="hljs-string">'greeting'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">($scope, greeting)</span> </span>{ $scope.phrase = greeting.sayHello(<span class="hljs-string">'en'</span>); }]);</code></pre> <p>之后在 <a href="https://github.com/angular/angular" rel="nofollow noreferrer" target="_blank">Angular 2</a> 与 <a href="https://github.com/2gis/slot" rel="nofollow noreferrer" target="_blank">Slot</a> 之中依赖注入仍是核心机制之一,这也是 <a href="http://www.js-code.com/tag/angular" title="浏览关于“Angular”的文章" target="_blank" class="tag_link">Angular</a> 一系的更多的被视为大而全的框架而不是小而美的库的原因之一。</p> <h1 id="articleHeader3">CommonJS</h1> <p>在 Node.js 横空出世之前,就已经有很多将运行于客户端浏览器中的 JavaScript 迁移运行到服务端的<a href="https://en.wikipedia.org/wiki/Comparison_of_server-side_JavaScript_solutions" rel="nofollow noreferrer" target="_blank">框架</a>;不过由于缺乏合适的规范,也没有提供统一的与操作系统及运行环境交互的接口,这些框架并未流行开来。2009 年时 Mozilla 的雇员 <a href="https://github.com/dangoor" rel="nofollow noreferrer" target="_blank">Kevin Dangoor</a> 发表了<a href="http://www.blueskyonmars.com/2009/01/29/what-server-side-javascript-needs/" rel="nofollow noreferrer" target="_blank">博客</a>讨论服务端 JavaScript 代码面临的困境,号召所有有志于规范服务端 JavaScript 接口的志同道合的开发者协同讨论,群策群力,最终形成了 ServerJS 规范;一年之后 ServerJS 重命名为 CommonJS。后来 CommonJS 内的模块规范成为了 Node.js 的标准实现规范,其基本语法为 <code>var commonjs = require("./commonjs");</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="// file greeting.js var helloInLang = { en: 'Hello world!', es: '¡Hola mundo!', ru: 'Привет мир!' }; var sayHello = function (lang) { return helloInLang[lang]; } module.exports.sayHello = sayHello; // file hello.js var sayHello = require('./lib/greeting').sayHello; var phrase = sayHello('en'); console.log(phrase);" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs javascript"><code><span class="hljs-comment">// file greeting.js</span> <span class="hljs-keyword">var</span> helloInLang = { <span class="hljs-attr">en</span>: <span class="hljs-string">'Hello world!'</span>, <span class="hljs-attr">es</span>: <span class="hljs-string">'¡Hola mundo!'</span>, <span class="hljs-attr">ru</span>: <span class="hljs-string">'Привет мир!'</span> }; <span class="hljs-keyword">var</span> sayHello = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">lang</span>) </span>{ <span class="hljs-keyword">return</span> helloInLang[lang]; } <span class="hljs-built_in">module</span>.exports.sayHello = sayHello; <span class="hljs-comment">// file hello.js</span> <span class="hljs-keyword">var</span> sayHello = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./lib/greeting'</span>).sayHello; <span class="hljs-keyword">var</span> phrase = sayHello(<span class="hljs-string">'en'</span>); <span class="hljs-built_in">console</span>.log(phrase);</code></pre> <p>该模块实现方案主要包含 <code>require</code> 与 <code>module</code> 这两个关键字,其允许某个模块对外暴露部分接口并且由其他模块导入使用。在 Node.js 中我们通过内建辅助函数来使用 CommonJS 的导入导出功能,而在其他 JavaScript 引擎中我们可以将其包裹为如下形式:</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) { // ... // Your code injects here! // ... });" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs typescript"><code>(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">exports, <span class="hljs-built_in">require</span>, <span class="hljs-built_in">module</span>, __file<a href="http://www.js-code.com/tag/name" title="浏览关于“name”的文章" target="_blank" class="tag_link">name</a>, __dirname</span>) </span>{ <span class="hljs-comment">// ...</span> <span class="hljs-comment">// Your code injects here!</span> <span class="hljs-comment">// ...</span> });</code></pre> <p>CommonJS 规范本身只是定义了不同环境下支持模块交互性的最小化原则,其具备极大的可扩展性。Node.js 中就对 <code>require</code> 函数添加了 <code>main</code> 属性,该属性在执行模块所属文件时指向 <code>module</code> 对象。Babel 在实现 ES2015 Modules 的转义时也扩展了 <code>require</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="export default something;" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs objectivec"><code style="word-break: break-word; white-space: initial;"><span class="hljs-keyword">export</span> <span class="hljs-keyword"><a href="http://www.js-code.com/tag/default" title="浏览关于“default”的文章" target="_blank" class="tag_link">default</a></span> something;</code></pre> <p>Babel 将此类型的导出转化为了 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="export.default = something;" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs coffeescript"><code style="word-break: break-word; white-space: initial;"><span class="hljs-keyword">export</span>.<span class="hljs-keyword">default</span> = something;</code></pre> <p>Webpack 打包工具也使用了很多扩展,譬如 <code>require.ensure</code>、<code>require.cache</code>、<code>require.context</code> 等等。CommonJS 算是目前最流行的模块格式,我们不仅可以在 Node.js 中使用,还可以通过 <a href="http://browserify.org/" rel="nofollow noreferrer" target="_blank">Browserify</a> 与 <a href="https://webpack.js.org/" rel="nofollow noreferrer" target="_blank">Webpack</a> 这样的打包工具将代码打包到客户端运行。另外我们需要注意的是,Node.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="// obj.js module.exports = { num:1 } // primitive.js module.exports = 1; // modifier.js var number = require('./primitive'); var obj = require('./obj'); number = 2; obj.num = 2; console.log(number); console.log(obj); // main.js console.log(require('./primitive')); console.log(require('./obj')); require('./modifier.js') console.log(require('./primitive')); console.log(require('./obj')); // 执行结果 1 { num: 1 } 2 { num: 2 } 1 { num: 2 }" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs typescript"><code><span class="hljs-comment">// obj.js</span> <span class="hljs-built_in">module</span>.exports = { num:<span class="hljs-number">1</span> } <span class="hljs-comment">// primitive.js</span> <span class="hljs-built_in">module</span>.exports = <span class="hljs-number">1</span>; <span class="hljs-comment">// modifier.js</span> <span class="hljs-keyword">var</span> <span class="hljs-built_in">number</span> = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./primitive'</span>); <span class="hljs-keyword">var</span> obj = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./obj'</span>); <span class="hljs-built_in">number</span> = <span class="hljs-number">2</span>; obj.num = <span class="hljs-number">2</span>; <span class="hljs-built_in">console</span>.log(<span class="hljs-built_in">number</span>); <span class="hljs-built_in">console</span>.log(obj); <span class="hljs-comment">// main.js</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-built_in">require</span>(<span class="hljs-string">'./primitive'</span>)); <span class="hljs-built_in">console</span>.log(<span class="hljs-built_in">require</span>(<span class="hljs-string">'./obj'</span>)); <span class="hljs-built_in">require</span>(<span class="hljs-string">'./modifier.js'</span>) <span class="hljs-built_in">console</span>.log(<span class="hljs-built_in">require</span>(<span class="hljs-string">'./primitive'</span>)); <span class="hljs-built_in">console</span>.log(<span class="hljs-built_in">require</span>(<span class="hljs-string">'./obj'</span>)); <span class="hljs-comment">// 执行结果</span> <span class="hljs-number">1</span> { num: <span class="hljs-number">1</span> } <span class="hljs-number">2</span> { num: <span class="hljs-number">2</span> } <span class="hljs-number">1</span> { num: <span class="hljs-number">2</span> }</code></pre> <h1 id="articleHeader4">AMD</h1> <p>就在 CommonJS 规范火热讨论的同时,很多开发者也关注于如何实现模块的异步加载。Web 应用的性能优化一直是前端工程实践中不可避免的问题,而模块的异步加载以及预加载等机制能有效地优化 Web 应用的加载速度。Mozilla 的另一位雇员 <a href="https://github.com/jrburke" rel="nofollow noreferrer" target="_blank">James Burke</a> 是<a href="https://groups.google.com/forum/#!msg/commonjs/nbpX739RQ5o/SdpVQDtx88AJ" rel="nofollow noreferrer" target="_blank">讨论组</a>的活跃成员,他在 Dojo 1.7 版本中引入了异步模块机制,并且在 2009 年开发了 require.js 框架。James 的核心思想在于不应该以同步方式加载模块,而应该充分利用浏览器的并发加载能力;James 按照其设计理念开发出的模块工具就是 AMD(Asynchronous Module Definition),其基本形式如下:</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="define([&quot;amd-module&quot;, &quot;../file&quot;], function(amdModule, file) { require([&quot;big-module/big/file&quot;], function(big) { var stuff = require(&quot;../my/stuff&quot;); }); });" title="" data-original-title="复制"></span> </div> </p></div> <pre class="javascript hljs"><code class="javascript">define([<span class="hljs-string">"amd-module"</span>, <span class="hljs-string">"../file"</span>], <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">amdModule, file</span>) </span>{ <span class="hljs-built_in">require</span>([<span class="hljs-string">"big-module/big/file"</span>], <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">big</span>) </span>{ <span class="hljs-keyword">var</span> stuff = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../my/stuff"</span>); }); });</code></pre> <p>而将我们上述使用的例子改写为 AMD 模式应当如下所示:</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="// file lib/greeting.js define(function() { var helloInLang = { en: 'Hello world!', es: '¡Hola mundo!', ru: 'Привет мир!' }; return { sayHello: function (lang) { return helloInLang[lang]; } }; }); // file hello.js define(['./lib/greeting'], function(greeting) { var phrase = greeting.sayHello('en'); document.write(phrase); });" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs javascript"><code><span class="hljs-comment">// file lib/greeting.js</span> define(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{ <span class="hljs-keyword">var</span> helloInLang = { <span class="hljs-attr">en</span>: <span class="hljs-string">'Hello world!'</span>, <span class="hljs-attr">es</span>: <span class="hljs-string">'¡Hola mundo!'</span>, <span class="hljs-attr">ru</span>: <span class="hljs-string">'Привет мир!'</span> }; <span class="hljs-keyword">return</span> { <span class="hljs-attr">sayHello</span>: <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">lang</span>) </span>{ <span class="hljs-keyword">return</span> helloInLang[lang]; } }; }); <span class="hljs-comment">// file hello.js</span> define([<span class="hljs-string">'./lib/greeting'</span>], <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">greeting</span>) </span>{ <span class="hljs-keyword">var</span> phrase = greeting.sayHello(<span class="hljs-string">'en'</span>); <span class="hljs-built_in">document</span>.write(phrase); });</code></pre> <p>hello.js 作为整个应用的入口模块,我们使用 <code>define</code> 关键字声明了该模块以及外部依赖;当我们执行该模块代码时,也就是执行 <code>define</code> 函数的第二个参数中定义的函数功能,其会在框架将所有的其他依赖模块加载完毕后被执行。这种延迟代码执行的技术也就保证了依赖的并发加载。从我个人而言,AMD 及其相关技术对于前端开发的工程化进步有着非常积极的意义,不过随着以 <code>npm</code> 为主导的依赖管理机制的统一,越来越多的开发者放弃了使用 AMD 模式。</p> <h1 id="articleHeader5">UMD</h1> <p>AMD 与 CommonJS 虽然师出同源,但还是分道扬镳,关注于代码异步加载与最小化入口模块的开发者将目光投注于 AMD;而随着 Node.js 以及 Browserify 的流行,越来越多的开发者也接受了 CommonJS 规范。令人扼腕叹息的是,符合 AMD 规范的模块并不能直接运行于实践了 CommonJS 模块规范的环境中,符合 CommonJS 规范的模块也不能由 AMD 进行异步加载,整个 JavaScript 生态圈貌似分崩离析。2011 年中,UMD,也就是 Universal Module Definition 规范正是为了弥合这种不一致性应运而出,其允许在环境中同时使用 AMD 与 CommonJS 规范。<a href="https://github.com/kriskowal/q" rel="nofollow noreferrer" target="_blank">Q</a> 算是 UMD 的首个规范实现,其能同时运行于浏览器环境(以脚本标签形式嵌入)与服务端的 Node.js 或者 Narwhal(CommonJS 模块)环境中;稍后,James 也为 Q 添加了对于 AMD 的支持。我们将上述例子中的 greeting.js 改写为同时支持 CommonJS 与 AMD 规范的模块:</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(define) { define(function () { var helloInLang = { en: 'Hello world!', es: '¡Hola mundo!', ru: 'Привет мир!' }; return { sayHello: function (lang) { return helloInLang[lang]; } }; }); }( typeof module === 'object' &amp;&amp; module.exports &amp;&amp; typeof define !== 'function' ? function (factory) { module.exports = factory(); } : define ));" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs javascript"><code>(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">define</span>) </span>{ define(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{ <span class="hljs-keyword">var</span> helloInLang = { <span class="hljs-attr">en</span>: <span class="hljs-string">'Hello world!'</span>, <span class="hljs-attr">es</span>: <span class="hljs-string">'¡Hola mundo!'</span>, <span class="hljs-attr">ru</span>: <span class="hljs-string">'Привет мир!'</span> }; <span class="hljs-keyword">return</span> { <span class="hljs-attr">sayHello</span>: <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">lang</span>) </span>{ <span class="hljs-keyword">return</span> helloInLang[lang]; } }; }); }( <span class="hljs-keyword">typeof</span> <span class="hljs-built_in">module</span> === <span class="hljs-string">'object'</span> &amp;&amp; <span class="hljs-built_in">module</span>.exports &amp;&amp; <span class="hljs-keyword">typeof</span> define !== <span class="hljs-string">'function'</span> ? <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">factory</span>) </span>{ <span class="hljs-built_in">module</span>.exports = factory(); } : define ));</code></pre> <p>该模式的核心思想在于所谓的 IIFE(Immediately Invoked Function Expression),该函数会根据环境来判断需要的参数类别,譬如在 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="function (factory) { module.exports = factory(); } " title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs javascript"><code><span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">factory</span>) </span>{ <span class="hljs-built_in">module</span>.exports = factory(); } </code></pre> <p>而如果是在 AMD 模块规范下,函数的参数就变成了 <code>define</code>。正是因为这种运行时的灵活性是我们能够将同一份代码运行于不同的环境中。</p> <h1 id="articleHeader6">ES2015 Modules</h1> <p>JavaScript 模块规范领域群雄逐鹿,各领风骚,作为 <a href="http://www.js-code.com/tag/ecmascript" title="ECMAScript" target="_blank">ECMAScript</a> 标准的起草者 TC39 委员会自然也不能置身事外。ES2015 Modules 规范始于 2010 年,主要由 <a href="https://github.com/dherman" rel="nofollow noreferrer" target="_blank">Dave Herman</a> 主导;随后的五年中 David 还参与了 asm.js,emscription,servo,等多个重大的开源项目,也使得 ES2015 Modules 的设计能够从多方面进行考虑与权衡。而最后的模块化规范定义于 2015 年正式发布,也就是被命名为 ES2015 Modules。我们上述的例子改写为 ES2015 Modules 规范如下所示:</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="// file lib/greeting.js const helloInLang = { en: 'Hello world!', es: '¡Hola mundo!', ru: 'Привет мир!' }; export const greeting = { sayHello: function (lang) { return helloInLang[lang]; } }; // file hello.js import { greeting } from &quot;./lib/greeting&quot;; const phrase = greeting.sayHello(&quot;en&quot;); document.write(phrase);" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs javascript"><code><span class="hljs-comment">// file lib/greeting.js</span> <span class="hljs-keyword"><a href="http://www.js-code.com/tag/const" title="浏览关于“const”的文章" target="_blank" class="tag_link">const</a></span> helloInLang = { <span class="hljs-attr">en</span>: <span class="hljs-string">'Hello world!'</span>, <span class="hljs-attr">es</span>: <span class="hljs-string">'¡Hola mundo!'</span>, <span class="hljs-attr">ru</span>: <span class="hljs-string">'Привет мир!'</span> }; <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> greeting = { <span class="hljs-attr">sayHello</span>: <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">lang</span>) </span>{ <span class="hljs-keyword">return</span> helloInLang[lang]; } }; <span class="hljs-comment">// file hello.js</span> <span class="hljs-keyword">import</span> { greeting } <span class="hljs-keyword">from</span> <span class="hljs-string">"./lib/greeting"</span>; <span class="hljs-keyword">const</span> phrase = greeting.sayHello(<span class="hljs-string">"en"</span>); <span class="hljs-built_in">document</span>.write(phrase);</code></pre> <p>ES2015 Modules 中主要的关键字就是 <code><a href="http://www.js-code.com/tag/import" title="import" target="_blank">import</a></code> 与 <code>export</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="// default exports export default 42; export default {}; export default []; export default foo; export default function () {} export default class {} export default function foo () {} export default class foo {} // variables exports export var foo = 1; export var foo = function () {}; export var bar; // lazy initialization export let foo = 2; export let bar; // lazy initialization export const foo = 3; export function foo () {} export class foo {} // named exports export {foo}; export {foo, bar}; export {foo as bar}; export {foo as default}; export {foo as default, bar}; // exports from export * from &quot;foo&quot;; export {foo} from &quot;foo&quot;; export {foo, bar} from &quot;foo&quot;; export {foo as bar} from &quot;foo&quot;; export {foo as default} from &quot;foo&quot;; export {foo as default, bar} from &quot;foo&quot;; export {default} from &quot;foo&quot;; export {default as foo} from &quot;foo&quot;;" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs javascript"><code><span class="hljs-comment">// default exports</span> <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-number">42</span>; <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {}; <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> []; <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> foo; <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-params"></span>) </span>{} <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-class"><span class="hljs-keyword">class</span> </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">foo</span> (<span class="hljs-params"></span>) </span>{} <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">foo</span> </span>{} <span class="hljs-comment">// variables exports</span> <span class="hljs-keyword">export</span> <span class="hljs-keyword">var</span> foo = <span class="hljs-number">1</span>; <span class="hljs-keyword">export</span> <span class="hljs-keyword">var</span> foo = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{}; <span class="hljs-keyword">export</span> <span class="hljs-keyword">var</span> bar; <span class="hljs-comment">// lazy initialization</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> foo = <span class="hljs-number">2</span>; <span class="hljs-keyword">export</span> <span class="hljs-keyword">let</span> bar; <span class="hljs-comment">// lazy initialization</span> <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> foo = <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">foo</span> (<span class="hljs-params"></span>) </span>{} <span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">foo</span> </span>{} <span class="hljs-comment">// named exports</span> <span class="hljs-keyword">export</span> {foo}; <span class="hljs-keyword">export</span> {foo, bar}; <span class="hljs-keyword">export</span> {foo <span class="hljs-keyword">as</span> bar}; <span class="hljs-keyword">export</span> {foo <span class="hljs-keyword">as</span> <span class="hljs-keyword">default</span>}; <span class="hljs-keyword">export</span> {foo <span class="hljs-keyword">as</span> <span class="hljs-keyword">default</span>, bar}; <span class="hljs-comment">// exports from</span> <span class="hljs-keyword">export</span> * <span class="hljs-keyword">from</span> <span class="hljs-string">"foo"</span>; <span class="hljs-keyword">export</span> {foo} <span class="hljs-keyword">from</span> <span class="hljs-string">"foo"</span>; <span class="hljs-keyword">export</span> {foo, bar} <span class="hljs-keyword">from</span> <span class="hljs-string">"foo"</span>; <span class="hljs-keyword">export</span> {foo <span class="hljs-keyword">as</span> bar} <span class="hljs-keyword">from</span> <span class="hljs-string">"foo"</span>; <span class="hljs-keyword">export</span> {foo <span class="hljs-keyword">as</span> <span class="hljs-keyword">default</span>} <span class="hljs-keyword">from</span> <span class="hljs-string">"foo"</span>; <span class="hljs-keyword">export</span> {foo <span class="hljs-keyword">as</span> <span class="hljs-keyword">default</span>, bar} <span class="hljs-keyword">from</span> <span class="hljs-string">"foo"</span>; <span class="hljs-keyword">export</span> {<span class="hljs-keyword">default</span>} <span class="hljs-keyword">from</span> <span class="hljs-string">"foo"</span>; <span class="hljs-keyword">export</span> {<span class="hljs-keyword">default</span> <span class="hljs-keyword">as</span> foo} <span class="hljs-keyword">from</span> <span class="hljs-string">"foo"</span>;</code></pre> <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="// default imports import foo from &quot;foo&quot;; import {default as foo} from &quot;foo&quot;; // named imports import {bar} from &quot;foo&quot;; import {bar, baz} from &quot;foo&quot;; import {bar as baz} from &quot;foo&quot;; import {bar as baz, xyz} from &quot;foo&quot;; // glob imports import * as foo from &quot;foo&quot;; // mixing imports import foo, {baz as xyz} from &quot;foo&quot;; import * as bar, {baz as xyz} from &quot;foo&quot;; import foo, * as bar, {baz as xyz} from &quot;foo&quot;;" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs typescript"><code><span class="hljs-comment">// default imports</span> <span class="hljs-keyword">import</span> foo <span class="hljs-keyword">from</span> <span class="hljs-string">"foo"</span>; <span class="hljs-keyword">import</span> {<span class="hljs-keyword">default</span> <span class="hljs-keyword">as</span> foo} <span class="hljs-keyword">from</span> <span class="hljs-string">"foo"</span>; <span class="hljs-comment">// named imports</span> <span class="hljs-keyword">import</span> {bar} <span class="hljs-keyword">from</span> <span class="hljs-string">"foo"</span>; <span class="hljs-keyword">import</span> {bar, baz} <span class="hljs-keyword">from</span> <span class="hljs-string">"foo"</span>; <span class="hljs-keyword">import</span> {bar <span class="hljs-keyword">as</span> baz} <span class="hljs-keyword">from</span> <span class="hljs-string">"foo"</span>; <span class="hljs-keyword">import</span> {bar <span class="hljs-keyword">as</span> baz, xyz} <span class="hljs-keyword">from</span> <span class="hljs-string">"foo"</span>; <span class="hljs-comment">// glob imports</span> <span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> foo <span class="hljs-keyword">from</span> <span class="hljs-string">"foo"</span>; <span class="hljs-comment">// mixing imports</span> <span class="hljs-keyword">import</span> foo, {baz <span class="hljs-keyword">as</span> xyz} <span class="hljs-keyword">from</span> <span class="hljs-string">"foo"</span>; <span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> bar, {baz <span class="hljs-keyword">as</span> xyz} <span class="hljs-keyword">from</span> <span class="hljs-string">"foo"</span>; <span class="hljs-keyword">import</span> foo, * <span class="hljs-keyword">as</span> bar, {baz <span class="hljs-keyword">as</span> xyz} <span class="hljs-keyword">from</span> <span class="hljs-string">"foo"</span>;</code></pre> <p>ES2015 Modules 作为 JavaScript 官方标准,日渐成为了开发者的主流选择。虽然我们目前还不能直接保证在所有环境(特别是旧版本浏览器)中使用该规范,但是通过 Babel 等转化工具能帮我们自动处理向下兼容。此外 ES2015 Modules 还是有些许被诟病的地方,譬如导入语句只能作为模块顶层的语句出现,不能出现在 <code>function</code> 里面或是 <code>if</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="if(Math.random()>0.5){<br /> import './module1.js'; // SyntaxError: Unexpected keyword 'import'<br /> }<br /> const import2 = (import './main2.js'); // SyntaxError<br /> try{<br /> import './module3.js'; // SyntaxError: Unexpected keyword 'import'<br /> }catch(err){<br /> console.error(err);<br /> }<br /> const moduleNumber = 4;</p> <p>import module4 from `module${moduleNumber}`; // SyntaxError: Unexpected token" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs typescript"><code><span class="hljs-keyword">if</span>(<span class="hljs-built_in"><a href="http://www.js-code.com/tag/Math" title="浏览关于“Math”的文章" target="_blank" class="tag_link">Math</a></span>.random()&gt;<span class="hljs-number">0.5</span>){ <span class="hljs-keyword">import</span> <span class="hljs-string">'./module1.js'</span>; <span class="hljs-comment">// SyntaxError: Unexpected keyword 'import'</span> } <span class="hljs-keyword">const</span> import2 = (<span class="hljs-keyword">import</span> <span class="hljs-string">'./main2.js'</span>); <span class="hljs-comment">// SyntaxError</span> <span class="hljs-keyword"><a href="http://www.js-code.com/tag/try" title="浏览关于“try”的文章" target="_blank" class="tag_link">try</a></span>{ <span class="hljs-keyword">import</span> <span class="hljs-string">'./module3.js'</span>; <span class="hljs-comment">// SyntaxError: Unexpected keyword 'import'</span> }<span class="hljs-keyword"><a href="http://www.js-code.com/tag/catch" title="浏览关于“catch”的文章" target="_blank" class="tag_link">catch</a></span>(err){ <span class="hljs-built_in">console</span>.error(err); } <span class="hljs-keyword">const</span> module<a href="http://www.js-code.com/tag/Number" title="浏览关于“Number”的文章" target="_blank" class="tag_link">Number</a> = <span class="hljs-number">4</span>; <span class="hljs-keyword">import</span> module4 <span class="hljs-keyword">from</span> <span class="hljs-string">`module<span class="hljs-subst">${moduleNumber}</span>`</span>; <span class="hljs-comment">// SyntaxError: Unexpected token</span></code></pre> <p>并且 <a href="http://www.js-code.com/tag/import" title="import" target="_blank">import</a> 语句会被提升到文件顶部执行,也就是说在模块初始化的时候所有的 <code>import</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="import './module1.js'; alert('code1'); import module2 from './module2.js'; alert('code2'); import module3 from './module3.js'; // 执行结果 module1 module2 module3 code1 code2" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs typescript"><code><span class="hljs-keyword">import</span> <span class="hljs-string">'./module1.js'</span>; <a href="http://www.js-code.com/tag/alert" title="浏览关于“alert”的文章" target="_blank" class="tag_link">alert</a>(<span class="hljs-string">'code1'</span>); <span class="hljs-keyword">import</span> module2 <span class="hljs-keyword">from</span> <span class="hljs-string">'./module2.js'</span>; alert(<span class="hljs-string">'code2'</span>); <span class="hljs-keyword">import</span> module3 <span class="hljs-keyword">from</span> <span class="hljs-string">'./module3.js'</span>; <span class="hljs-comment">// 执行结果</span> module1 module2 module3 code1 code2</code></pre> <p>并且 <code>import</code> 的模块名只能是字符串常量,导入的值也是不可变对象;比如说你不能 <code>import { a } from './a'</code> 然后给 a 赋值个其他什么东西。这些设计虽然使得灵活性不如 CommonJS 的 require,但却保证了 ES6 Modules 的依赖关系是确定(Deterministic)的,和运行时的状态无关,从而也就保证了 ES6 Modules 是可以进行可靠的静态分析的。对于主要在服务端运行的 Node 来说,所有的代码都在本地,按需动态 require 即可,但对于要下发到客户端的 Web 代码而言,要做到高效的按需使用,不能等到代码执行了才知道模块的依赖,必须要从模块的静态分析入手。这是 ES6 Modules 在设计时的一个重要考量,也是为什么没有直接采用 CommonJS。此外我们还需要关注下的是 ES2015 Modules 在浏览器内的原生支持情况,尽管我们可以通过 Webpack 等打包工具将应用打包为单个包文件。目前主流浏览器中默认支持 ES2015 Modules 只有 Safari,而 Firefox 在 54 版本之后允许用户手动启用该特性。以 Firefox 为例,如果我们在浏览器中使用 ES2015 Modules,我们需要声明入口模块:</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="<script type=&quot;module&quot; scr=&quot;PATH/file.js&quot;></script>" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs xml"><code style="word-break: break-word; white-space: initial;"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span> <span class="hljs-attr">scr</span>=<span class="hljs-string">"PATH/file.js"</span>&gt;</span><span class="undefined"></span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></code></pre> <p>这里的 <code>module</code> 关键字就告诉浏览器该脚本中包含了对于其他脚本的导入语句,需要进行预先处理;不过问题来了,那么 JavaScript 解释器又该如何判断某个文件是否为模块。社区也经过很多轮的讨论,我们可以来看下简单的例子:</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="<!--index.html--><br /> <!DOCTYPE html><br /> <html><br /> <head><br /> <script type=&quot;module&quot; src=&quot;main.js&quot;></script><br /> </head><br /> <body><br /> </body><br /> </html>" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs xml"><code><span class="hljs-comment">&lt;!--index.html--&gt;</span> <span class="hljs-meta">&lt;!DOCTYPE html&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"main.js"</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">head</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></code></pre> <p>main.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="// main.js import utils from &quot;./utils.js&quot;; utils.alert(` JavaScript modules work in this browser: https://blog.whatwg.org/js-modules `);" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs groovy"><code><span class="hljs-comment">// main.js</span> <span class="hljs-keyword">import</span> utils from <span class="hljs-string">"./utils.js"</span>; utils.alert(` JavaScript modules work <span class="hljs-keyword">in</span> <span class="hljs-keyword">this</span> <span class="hljs-string">browser:</span> <span class="hljs-symbol"> https:</span><span class="hljs-comment">//blog.whatwg.org/js-modules</span> `);</code></pre> <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="// utils.js export default { alert: (msg)=>{<br /> alert(msg);<br /> }<br /> };" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs typescript"><code><span class="hljs-comment">// utils.js</span> <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> { alert: <span class="hljs-function">(<span class="hljs-params">msg</span>)=&gt;</span>{ alert(msg); } };</code></pre> <p>我们可以发现,在 <code>import</code> 语句中我们提供了 <code>.js</code> 扩展名,这也是区别于打包工具的重要特性之一,往往打包工具中并不需要我们提供扩展名。此外,在浏览器中进行模块的动态加载,也要求待加载文件具有正确的 MIME 类型。我们常用的正确的模块地址譬如:</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="https://example.com/apples.js http:example.compears.mjs (becomes http://example.com/pears.mjs as step 1 parses with no base URL) //example.com/bananas ./strawberries.js.cgi ../lychees /limes.jsx data:text/javascript,export default ‘grapes’; blob:https://whatwg.org/d0360e2f-caee-469f-9a2f-87d5b0456f6f" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs groovy"><code><span class="hljs-string">https:</span><span class="hljs-comment">//example.com/apples.js</span> <span class="hljs-string">http:</span>example.compears.mjs (becomes <span class="hljs-string">http:</span><span class="hljs-comment">//example.com/pears.mjs as step 1 parses with no base URL)</span> <span class="hljs-comment">//example.com/bananas</span> ./strawberries.js.cgi ../lychees /limes.jsx <span class="hljs-string">data:</span>text/javascript,export <span class="hljs-keyword">default</span> ‘grapes’; <span class="hljs-string">blob:</span><span class="hljs-string">https:</span><span class="hljs-comment">//whatwg.org/d0360e2f-caee-469f-9a2f-87d5b0456f6f</span></code></pre> <p>不过笔者觉得有个不错的特性在于浏览器中支持 <a href="http://www.js-code.com/tag/cors" title="CORS" target="_blank">CORS</a> 协议,跨域加载其他域中的脚本。在浏览器中加载进来的模块与直接加载的脚本的作用域也是不一致的,并且不需要 <code>use strict</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="var x = 1; alert(x === window.x);//false alert(this === undefined);// true" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs typescript"><code><span class="hljs-keyword">var</span> x = <span class="hljs-number">1</span>; alert(x === <span class="hljs-built_in"><a href="http://www.js-code.com/tag/window" title="浏览关于“window”的文章" target="_blank" class="tag_link">window</a></span>.x);<span class="hljs-comment">//<a href="http://www.js-code.com/tag/false" title="浏览关于“false”的文章" target="_blank" class="tag_link">false</a></span> alert(<span class="hljs-keyword">this</span> === <span class="hljs-literal"><a href="http://www.js-code.com/tag/undefined" title="浏览关于“undefined”的文章" target="_blank" class="tag_link">undefined</a></span>);<span class="hljs-comment">// <a href="http://www.js-code.com/tag/true" title="浏览关于“true”的文章" target="_blank" class="tag_link">true</a></span></code></pre> <p>浏览器对于模块的加载默认是异步延迟进行的,即模块脚本的加载并不会阻塞浏览器的解析行为,而是并发加载并在页面加载完毕后进行解析,也就是所有的模块脚本具有 <code>defer</code> 属性。我们也可以为脚本添加 <code>async</code> 属性,即指明该脚本会在加载完毕后立刻执行。这一点与传统的非模块脚本相比很大不同,传统的脚本会阻塞浏览器解析直到抓取完毕,在抓取之后也会立刻进行执行操作。整个加载流程如下所示:<br /><span class="img-wrap"><img data-src="/img/remote/1460000008979688?w=2880&amp;h=650" src="https://static.segmentfault.com/v-5cc2cd8e/global/img/squares.svg" alt="" title="" style="cursor: pointer;"></span></p> <p></code></p>

总结

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

JavaScript 模块演化简史

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

JavaScript 模块演化简史

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

80%的人都看过