隨著 Web 技術(shù)的蓬勃發(fā)展和依賴的基礎(chǔ)設(shè)施日益完善,前端領(lǐng)域逐漸從瀏覽器擴(kuò)展至服務(wù)端(Node.js),桌面端(PC、Android、iOS),乃至于物聯(lián)網(wǎng)設(shè)備(IoT),其中 JavaScript 承載著這些應(yīng)用程序的核心部分,隨著其規(guī)模化和復(fù)雜度的成倍增長,其軟件工程體系也隨之建立起來(協(xié)同開發(fā)、單元測試、需求和缺陷管理等),模塊化編程的需求日益迫切。
JavaScript 對模塊化編程的支持尚未形成規(guī)范,難以堪此重任;一時(shí)間,江湖俠士挺身而出,一路披荊斬棘,從刀耕火種過渡到面向未來的模塊化方案;
更多關(guān)于我的文章和項(xiàng)目,歡迎關(guān)注 @x-cold
模塊化編程就是通過組合一些__相對獨(dú)立可復(fù)用的模塊__來進(jìn)行功能的實(shí)現(xiàn),其最核心的兩部分是__定義模塊__和__引入模塊__;
盡管 JavaScript 語言層面并未提供模塊化的解決方案,但利用其可__面向?qū)ο骭_的語言特性,外加__設(shè)計(jì)模式__加持,能夠?qū)崿F(xiàn)一些簡單的模塊化的架構(gòu);經(jīng)典的一個(gè)案例是利用單例模式模式去實(shí)現(xiàn)模塊化,可以對模塊進(jìn)行較好的封裝,只暴露部分信息給需要使用模塊的地方;
// Define a module var moduleA=(function ($, doc) { var methodA=function() {}; var dataA={}; return { methodA: methodA, dataA: dataA }; })(jQuery, document); // Use a module var result=moduleA.mehodA();
直觀來看,通過立即執(zhí)行函數(shù)(IIFE)來聲明依賴以及導(dǎo)出數(shù)據(jù),這與當(dāng)下的模塊化方案并無巨大的差異,可本質(zhì)上卻有千差萬別,無法滿足的一些重要的特性;
題外話:由于年代久遠(yuǎn),這兩種模塊化方案逐漸淡出歷史舞臺(tái),具體特性不再細(xì)聊;
為了解決”刀耕火種”時(shí)代存留的需求,AMD 和 CMD 模塊化規(guī)范問世,解決了在瀏覽器端的異步模塊化編程的需求,__其最核心的原理是通過動(dòng)態(tài)加載 script 和事件監(jiān)聽的方式來異步加載模塊;__
AMD 和 CMD 最具代表的兩個(gè)作品分別對應(yīng) require.js 和 sea.js;其主要區(qū)別在于依賴聲明和依賴加載的時(shí)機(jī),其中 require.js 默認(rèn)在聲明時(shí)執(zhí)行, sea.js 推崇懶加載和按需使用;另外值得一提的是,CMD 規(guī)范的寫法和 CommonJS 極為相近,只需稍作修改,就能在 CommonJS 中使用。參考下面的 Case 更有助于理解;
// AMD define(['./a','./b'], function (moduleA, moduleB) { // 依賴前置 moduleA.mehodA(); console.log(moduleB.dataB); // 導(dǎo)出數(shù)據(jù) return {}; }); // CMD define(function (requie, exports, module) { // 依賴就近 var moduleA=require('./a'); moduleA.mehodA(); // 按需加載 if (needModuleB) { var moduleB=requie('./b'); moduleB.methodB(); } // 導(dǎo)出數(shù)據(jù) exports={}; });
2009 年 ry 發(fā)布 Node.js 的第一個(gè)版本,CommonJS 作為其中最核心的特性之一,適用于服務(wù)端下的場景;歷年來的考察和時(shí)間的洗禮,以及前端工程化對其的充分支持,CommonJS 被廣泛運(yùn)用于 Node.js 和瀏覽器;
// Core Module const cp=require('child_process'); // Npm Module const axios=require('axios'); // Custom Module const foo=require('./foo'); module.exports={ axios }; exports.foo=foo;
1、模塊定義
默認(rèn)任意 .node .js .json 文件都是符合規(guī)范的模塊;
2、引入模塊
首先從緩存(require.cache)優(yōu)先讀取模塊,如果未命中緩存,則進(jìn)行路徑分析,然后按照不同類型的模塊處理:
其中在編譯的過程中,Node對獲取的JavaScript文件內(nèi)容進(jìn)行了頭尾包裝,結(jié)果如下:
(function (exports, require, module, __filename, __dirname) { var circle=require('./circle.js'); console.log('The area of a circle of radius 4 is ' + circle.area(4)); });
ES Module 是語言層面的模塊化方案,由 ES 2015 提出,其規(guī)范與 CommonJS 比之 ,導(dǎo)出的值都可以看成是一個(gè)具備多個(gè)屬性或者方法的對象,可以實(shí)現(xiàn)互相兼容;但寫法上 ES Module 更簡潔,與 Python 接近;
import fs from 'fs'; import color from 'color'; import service, { getArticles } from '../service'; export default service; export const getArticles=getArticles;
主要差異在于:
// a.js export let a=1; export function caculate() { a++; }; // b.js import { a, caculate } from 'a.js'; console.log(a); // 1 caculate(); console.log(a); // 2 a=2; // Syntax Error: "a" is read-only
通過一層自執(zhí)行函數(shù)來兼容各種模塊化規(guī)范的寫法,兼容 AMD / CMD / CommonJS 等模塊化規(guī)范,貼上代碼勝過千言萬語,需要特別注意的是 ES Module 由于會(huì)對靜態(tài)代碼進(jìn)行分析,故這種運(yùn)行時(shí)的方案無法使用,此時(shí)通過 CommonJS 進(jìn)行兼容;
(function (global, factory) { if (typeof exports==='object') { module.exports=factory(); } else if (typeof define==='function' && define.amd) { define(factory); } else { this.eventUtil=factory(); } })(this, function (exports) { ? // Define Module Object.defineProperty(exports, "__esModule", { value: true }); exports.default=42; });
為了在瀏覽器環(huán)境中運(yùn)行模塊化的代碼,需要借助一些模塊化打包的工具進(jìn)行打包( 以 webpack 為例),定義了項(xiàng)目入口之后,會(huì)先快速地進(jìn)行依賴的分析,然后將所有依賴的模塊轉(zhuǎn)換成瀏覽器兼容的對應(yīng)模塊化規(guī)范的實(shí)現(xiàn);
從上面的介紹中,我們已經(jīng)對其規(guī)范和實(shí)現(xiàn)有了一定的了解;在瀏覽器中,要實(shí)現(xiàn) CommonJS 規(guī)范,只需要實(shí)現(xiàn) module / exports / require / global 這幾個(gè)屬性,由于瀏覽器中是無法訪問文件系統(tǒng)的,因此 require 過程中的文件定位需要改造為加載對應(yīng)的 JS 片段(webpack 采用的方式為通過函數(shù)傳參實(shí)現(xiàn)依賴的引入)。具體實(shí)現(xiàn)可以參考:tiny-browser-require。
webpack 打包出來的代碼快照如下,注意看注釋中的時(shí)序;
(function (modules) { // The module cache var installedModules={}; // The require function function __webpack_require__(moduleId) {} return __webpack_require__(0); // ---> 0 }) ({ 0: function (module, exports, __webpack_require__) { // Define module A var moduleB=__webpack_require__(1); // ---> 1 }, 1: function (module, exports, __webpack_require__) { // Define module B exports={}; // ---> 2 } });
實(shí)際上,ES Module 的處理同 CommonJS 相差無幾,只是在定義模塊和引入模塊時(shí)會(huì)去處理 __esModule 標(biāo)識,從而兼容其在語法上的差異。
1、瀏覽器環(huán)境下,網(wǎng)絡(luò)資源受到較大的限制,因此打包出來的文件如果體積巨大,對頁面性能的損耗極大,因此需要對構(gòu)建的目標(biāo)文件進(jìn)行拆分,同時(shí)模塊也需要支持動(dòng)態(tài)加載;
webpack 提供了兩個(gè)方法 require.ensure() 和 import() (推薦使用)進(jìn)行模塊的動(dòng)態(tài)加載,至于其中的原理,跟上面提及的 AMD & CMD 所見略同,import() 執(zhí)行后返回一個(gè) Promise 對象,其中所做的工作無非也是動(dòng)態(tài)新增 script 標(biāo)簽,然后通過 onload / onerror 事件進(jìn)一步處理。
2、由于 require 函數(shù)是完全自定義的,我們可以在模塊化中實(shí)現(xiàn)更多的特性,比如通過修改 require.resolve 或 Module._extensions 擴(kuò)展支持的文件類型,使得 css / .jsx / .vue / 圖片等文件也能為模塊化所使用;
作者:x-cold
本文主要理理js模塊化相關(guān)知識。
涉及到內(nèi)聯(lián)腳本、外聯(lián)腳本、動(dòng)態(tài)腳本、阻塞、defer、async、CommonJS、AMD、CMD、UMD、ES Module。順帶探究下Vite。
假設(shè)你是一個(gè)前端新手,現(xiàn)在入門,那么我們創(chuàng)建一個(gè)html頁面,需要新建一個(gè)index.html文件:
<!DOCTYPE html>
<html>
<head>
<title>test</title>
</head>
<body>
<p id="content">hello world</p>
</body>
</html>
如果需要在頁面中執(zhí)行javascript代碼,我們就需要在 HTML 頁面中插入 <script> 標(biāo)簽。
有2種插入方式:
1、放在<head>中
2、放在<body>中
比如,點(diǎn)擊hello world之后,在hello world后面加3個(gè)感嘆號的功能,我們在head中加入script標(biāo)簽,并給hello world綁定點(diǎn)擊事件:
<!DOCTYPE html>
<html>
<head>
<title>test</title>
<script>
function myFunction() {
document.getElementById('content').innerHTML='hello world!!!'
}
</script>
</head>
<body>
<p id="content" onclick="myFunction()">hello world</p>
</body>
</html>
如果加在body中,一般放在body的最后面:
<!DOCTYPE html>
<html>
<head>
<title>test</title>
</head>
<body>
<p id="content" onclick="myFunction()">hello world</p>
<script>
function myFunction() {
document.getElementById('content').innerHTML='hello world!!!'
}
</script>
</body>
</html>
簡單的邏輯我們可以用這2種方式寫,這種方式叫做內(nèi)聯(lián)腳本。
當(dāng)邏輯復(fù)雜時(shí),我們可以把上面的script標(biāo)簽中的代碼抽取出來,比如在html的同級目錄創(chuàng)建一個(gè)js文件夾,里面新建一個(gè)a.js的文件。
a.js中寫上面script標(biāo)簽中的代碼:
function myFunction() {
document.getElementById('content').innerHTML='hello world!!!'
}
上面的script標(biāo)簽則可以改成:
<script src="./js/a.js"></script>
上面的2種寫法,瀏覽器在加載html時(shí),遇到script標(biāo)簽,會(huì)停止解析html。
內(nèi)聯(lián)腳本會(huì)立刻執(zhí)行;外聯(lián)腳本會(huì)先下載再立刻執(zhí)行。
等腳本執(zhí)行完畢才會(huì)繼續(xù)解析html。
(html解析到哪里,頁面就能顯示到哪里,用戶也能看到哪里)
比如下面的代碼:
<p>...content before script...</p>
<script src="./js/a.js"></script>
<p>...content after script...</p>
解析到第一個(gè)p標(biāo)簽,我們能看到...content before script...顯示在了頁面中,然后瀏覽器遇到script標(biāo)簽,會(huì)停止解析html,而去下載a.js并執(zhí)行,執(zhí)行完a.js才會(huì)繼續(xù)解析html,然后頁面中才會(huì)出現(xiàn)...content after script...。
我們可以通過Chrome的Developer Tools分析一下index.html加載的時(shí)間線:
這會(huì)導(dǎo)致2個(gè)問題:
1、腳本無法訪問它下面的dom;
2、如果頁面頂部有個(gè)笨重的腳本,在它執(zhí)行完之前,用戶都看不到完整的頁面。
對于問題2,我們可以把腳本放在頁面底部,這樣它可以訪問到上面的dom,且不會(huì)阻塞頁面的顯示:
<body>
...all content is above the script...
<script src="./js/a.js"></script>
</body>
但這不是最好的辦法,我們接著往下看。
我們給script標(biāo)簽加defer屬性,就像下面這樣:
<p>...content before script...</p>
<script defer src="./js/a.js"></script>
<p>...content after script...</p>
defer 特性告訴瀏覽器不要等待腳本。于是,瀏覽器將繼續(xù)解析html,腳本會(huì)并行下載,然后等 DOM 構(gòu)建完成后,腳本才會(huì)執(zhí)行。
這樣script標(biāo)簽不再阻塞html的解析。
這時(shí)再看時(shí)間線:
需要注意的是,具有 defer 特性的腳本保持其相對順序。
比如:
<script defer src="./js/a.js"></script>
<script defer src="./js/b.js"></script>
上面的2個(gè)腳本會(huì)并行下載,但是不論哪個(gè)先下載完成,都是先執(zhí)行a.js,a.js執(zhí)行完才會(huì)執(zhí)行b.js。
這時(shí),如果b.js依賴a.js,這種寫法將很有用。
另外需要注意的是,defer 特性僅適用于外聯(lián)腳本,即如果 script標(biāo)簽沒有 src屬性,則會(huì)忽略 defer 特性。
我們可以給script標(biāo)簽加async屬性,就像下面這樣:
<script async src="./js/a.js"></script>
這會(huì)告訴瀏覽器,該腳本完全獨(dú)立。
獨(dú)立的意思是,DOM 和其他腳本不會(huì)等待它,它也不會(huì)等待其它東西。async 腳本就是一個(gè)會(huì)在加載完成時(shí)立即執(zhí)行的完全獨(dú)立的腳本。
這時(shí)再看時(shí)間線:
可以看到,雖然下載a.js不阻塞html的解析,但是執(zhí)行a.js會(huì)阻塞。
還需要注意多個(gè)async時(shí)的執(zhí)行順序,比如下面這段代碼:
<p>...content before script...</p>
<script async src="./js/a.js"></script>
<script async src="./js/b.js"></script>
<p>...content after script...</p>
兩個(gè)p標(biāo)簽的內(nèi)容會(huì)立刻顯示出來,a.js和b.js則并行下載,且下載成功后立刻執(zhí)行,所以多個(gè)async時(shí)的執(zhí)行順序是誰先下載成功誰先執(zhí)行。
一些比較獨(dú)立的腳本,比如性能監(jiān)控,就很適合用這種方式加載。
另外,和defer一樣,async 特性也僅適用于外聯(lián)腳本。
我們可以動(dòng)態(tài)地創(chuàng)建一個(gè)script標(biāo)簽并append到文檔中。
let script=document.createElement('script')
script.src='/js/a.js'
document.body.append(script)
append后腳本就會(huì)立刻開始加載,表現(xiàn)默認(rèn)和加了async屬性一致。
我們可以顯示的設(shè)置script.async=false來改變這個(gè)默認(rèn)行為,那么這時(shí)表現(xiàn)就和加了defer屬性一致。
上面的這些寫法,當(dāng)script標(biāo)簽變多時(shí),容易導(dǎo)致全局作用域污染,還要維護(hù)書寫順序,要解決這個(gè)問題,需要一種將 JavaScript 程序拆分為可按需導(dǎo)入的單獨(dú)模塊的機(jī)制,即js模塊化,我們接著往下看。
很長一段時(shí)間 JavaScript 沒有模塊化的概念,直到 Node.js 的誕生,把 JavaScript 帶到服務(wù)端,這時(shí),CommonJS誕生了。
CommonJS定義了三個(gè)全局變量:
require,exports,module
require 讀入并執(zhí)行一個(gè) js 文件,然后返回其 exports 對象;
exports 對外暴露模塊的接口,可以是任何類型,指向 module.exports;
module 是當(dāng)前模塊,exports 是 module 上的一個(gè)屬性。
Node.js 使用了CommonJS規(guī)范。
比如:
// a.js
let name='Lily'
export.name=name
// b.js
let a=require('a.js')
console.log(a.name) // Lily
由于CommonJS不適合瀏覽器端,于是出現(xiàn)了AMD和CMD規(guī)范。
AMD(Asynchronous Module Definition) 是 RequireJS 在推廣過程中對模塊定義的規(guī)范化產(chǎn)出。
基本思想是,通過 define 方法,將代碼定義為模塊。當(dāng)這個(gè)模塊被 require 時(shí),開始加載依賴的模塊,當(dāng)所有依賴的模塊加載完成后,開始執(zhí)行回調(diào)函數(shù),返回該模塊導(dǎo)出的值。
使用時(shí),需要先引入require.js:
<script src="require.js"></script>
<script src="a.js"></script>
然后可以這樣寫:
// a.js
define(function() {
let name='Lily'
return {
name
}
})
// b.js
define(['a.js'], function(a) {
let name='Bob'
console.log(a.name) // Lily
return {
name
}
})
CMD(Common Module Definition) 是 Sea.js 在推廣過程中對模塊定義的規(guī)范化產(chǎn)出。
使用時(shí),需要先引入sea.js:
<script src="sea.js"></script>
<script src="a.js"></script>
然后可以這樣寫:
// a.js
define(function(require, exports, module) {
var name='Lily'
exports.name=name
})
// b.js
define(function(require, exports, module) {
var name='Bob'
var a=require('a.js')
console.log(a.name) // 'Lily'
exports.name=name
})
UMD (Universal Module Definition) 目的是提供一個(gè)前后端跨平臺(tái)的解決方案(兼容全局變量、AMD、CMD和CommonJS)。
實(shí)現(xiàn)很簡單,判斷不同的環(huán)境,然后以不同的方式導(dǎo)出模塊:
(function (root, factory) {
if (typeof define==='function' && (define.amd || define.cmd)) {
// AMD、CMD
define([], factory);
} else if (typeof module !=='undefined' && typeof exports==='object') {
// Node、CommonJS
module.exports=factory();
} else {
// 瀏覽器全局變量
root.moduleName=factory();
}
}(this, function () {
// 只需要返回一個(gè)值作為模塊的export
// 這里我們返回了一個(gè)空對象
// 你也可以返回一個(gè)函數(shù)
return {};
}));
AMD 和 CMD 是社區(qū)的開發(fā)者們制定的模塊加載方案,并不是語言層面的標(biāo)準(zhǔn)。從 ES6 開始,在語言標(biāo)準(zhǔn)的層面上,實(shí)現(xiàn)了模塊化功能,而且實(shí)現(xiàn)得相當(dāng)簡單,完全可以取代上文的規(guī)范,成為瀏覽器和服務(wù)器通用的模塊解決方案。
ES6 的模塊自動(dòng)采用嚴(yán)格模式。模塊功能主要由兩個(gè)命令構(gòu)成:export和import。
export命令用于規(guī)定模塊的對外接口;
import命令用于輸入其他模塊提供的功能。
比如上面的代碼,我們可以這樣寫:
// a.js
const name='Lily'
export {
name
}
// 等價(jià)于
export const name='Lily'
// b.js
import { name } from 'a.js'
console.log(name) // Lily
// b.js
import * as a from 'a.js'
console.log(a.name) // Lily
此外,還可以用export default默認(rèn)導(dǎo)出的寫法:
// a.js
const name='Lily'
export default {
name
}
// b.js
import a from 'a.js'
console.log(a.name) // Lily
如果只想運(yùn)行a.js,可以只import:
// b.js
import 'a.js'
我們可以給script標(biāo)簽加type=module讓瀏覽器以 ES Module 的方式加載腳本:
<script type="module" src="./js/b.js"></script>
這時(shí),script標(biāo)簽會(huì)默認(rèn)有defer屬性(也可以設(shè)置成async),支持內(nèi)聯(lián)和外聯(lián)腳本。
這時(shí)我們運(yùn)行打開index.html,會(huì)發(fā)現(xiàn)瀏覽器報(bào)錯(cuò)了:
這是因?yàn)?type=module 的 script 標(biāo)簽加強(qiáng)了安全策略,瀏覽器加載不同域的腳本資源時(shí),如果服務(wù)器未返回有效的 Allow-Origin 相關(guān) CORS 頭,會(huì)禁止加載改腳本。而這里啟動(dòng)的index.html是一個(gè)本地文件(地址是file://路徑),將會(huì)遇到 CORS 錯(cuò)誤,需要通過一個(gè)服務(wù)器來啟動(dòng) HTML 文件。
在瀏覽器支持 ES Module 之前,我們用工具實(shí)現(xiàn)JavaScript模塊化的開發(fā),比如webpack、Rollup 和 Parcel 。但是當(dāng)項(xiàng)目越來越大后,本地?zé)岣略絹碓铰?Vite 旨在利用ESM解決上述問題。
Vite使用簡單,可以去官網(wǎng)(https://cn.vitejs.dev/)看看。
老的規(guī)范了解即可,未來是ES Module的,用Vite可以極大的提升開發(fā)時(shí)的體驗(yàn),生產(chǎn)環(huán)境用Rollup打包。
ommonJS、AMD、 CMD 都是 JavaScript 模塊化的規(guī)范。其中 CommonJS 側(cè)重于服務(wù)器端 JavaScript 模塊化規(guī)范,著名的 NodeJS 就是這種規(guī)范的實(shí)現(xiàn);AMD(異步模塊定義)和 CMD(通用模塊定義)偏向于瀏覽器端 JavaScript 模塊化規(guī)范:RequireJS 遵循 AMD,SeaJS 則遵循 CMD。
遵循這些規(guī)范,便可在需要時(shí)加載某些模塊,使得大量龐雜的代碼得以良好的組織和管理。模塊化還使得我們在管理和使用代碼不那么混亂,更方便與他人合作。
CommonJS 規(guī)范是出現(xiàn)的比較早,是一個(gè)偏向于服務(wù)器端的規(guī)范,NodeJS 便采用該規(guī)范。根據(jù) CommonJS 規(guī)范,一個(gè)單獨(dú)的文件就是一個(gè)模塊。加載模塊使用 require 方法,您可以通過 require('something') 來加載模塊。該方法讀取一個(gè)文件并執(zhí)行,最后返回文件內(nèi)部 exports 的對象。因此,在 NodeJS 中,定義一個(gè)模塊就是寫一個(gè)新的 JavaScript 文件,最后要將文件的內(nèi)容 exports 出來。
不過,這種方法更適合服務(wù)端,因?yàn)樵诜?wù)器端通過讀取本地磁盤加載模塊,同步加載速度很快。如果在客戶端,由于網(wǎng)絡(luò)等不穩(wěn)定因素,同步加載模塊代碼有可能會(huì)阻塞,會(huì)影響 require 后代碼的執(zhí)行。有沒有異步加載模塊的規(guī)范呢?
CommonJS 解決了 JavaScript 模塊化的問題,并可以用在瀏覽器中。但 CommonJS 是同步加載模塊,即用到模塊時(shí)即時(shí)加載,現(xiàn)加載現(xiàn)用,這種同步機(jī)制導(dǎo)致在瀏覽器端會(huì)出現(xiàn)各種問題:性能、可用性、調(diào)試和跨域等。
鑒于瀏覽器的特殊情況,又出現(xiàn)了一個(gè)規(guī)范,這個(gè)規(guī)范呢可以實(shí)現(xiàn)異步加載依賴模塊,并且會(huì)提前加載那,這就是 AMD 規(guī)范,即 Asynchronous Module Definition,這種異步模塊定義規(guī)范,RequireJs 實(shí)現(xiàn)了這一規(guī)范:先定義所有依賴,然后在加載完成后的回調(diào)函數(shù)中執(zhí)行,語法類似 require([module], callback)
AMD 雖然實(shí)現(xiàn)了異步加載,但是必須開始就把所有依賴寫出來,其并不符合書寫的邏輯順序,能不能像 CommonJS 那樣用的時(shí)候在 require,并且還支持異步加載呢?
CMD ,Common Module Definition, 淘寶的玉伯實(shí)現(xiàn)了 SeaJs 并遵循該規(guī)范。CMD則是依賴就近,用的時(shí)候再 require。類似這樣:
define(function(require, exports, module){
var a=require('module a')
? ? a.doSomething()
? ? var b=require('module b') // 依賴可以就近加載
? ? b.doSomething()
})
AMD 和 CMD 最大的不同是對依賴模塊的執(zhí)行時(shí)機(jī)不同,AMD 是提前執(zhí)行,CMD 是延遲執(zhí)行。不過 RequireJS 從 2.0 開始,也可以延遲執(zhí)行。AMD 依賴前置,JavaScript 代碼可以方便知道依賴模塊有哪些,立即加載;而 CMD 推崇就近依賴,需要使用把模塊變?yōu)樽址馕鲆槐楹蟛胖烙心男┮蕾嚕@是一些人詬病 CMD 的點(diǎn),犧牲性能來換取開發(fā)的便利性,不過實(shí)際上運(yùn)行中,解析模塊犧牲的時(shí)間幾乎可以忽略。
*請認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。