<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                [TOC] # 模塊化 使用一個技術肯定是有原因的,那么使用模塊化可以給我們帶來以下好處 * 解決命名沖突 * 提供復用性 * 提高代碼可維護性 ## 立即執行函數 在早期,使用立即執行函數實現模塊化是常見的手段,通過函數作用域解決了命名沖突、污染全局作用域的問題 ~~~ (function(globalVariable){ globalVariable.test = function() {} // ... 聲明各種變量、函數都不會污染全局作用域 })(globalVariable) ~~~ <br> <br> ## AMD RequireJS 是 AMD 規范的代表之作,它之所以能代表 AMD 規范,是因為 RequireJS 的作者 (James Burke) 就是 AMD 規范的提出者。同時作者還開發了[`amdefine`](https://github.com/jrburke/amdefine),一個讓你在 node 中也可以使用 AMD 規范的庫。 <br> AMD 規范由 CommonJS 的 Modules/Transport/C 提案發展而來,毫無疑問,Modules/Transport/C 提案的發起者就是 James Burke。 <br> James Burke 指出了 CommonJS 規范在瀏覽器上的一些不足: 1. 缺少模塊封裝的能力:CommonJS 規范中的每個模塊都是一個文件。這意味著每個文件只有一個模塊。這在服務器上是可行的,但是在瀏覽器中就不是很友好,瀏覽器中需要做到盡可能少的發起請求。 2. 使用同步的方式加載依賴:雖然同步的方法進行加載可以讓代碼更容易理解,但是在瀏覽器中使用同步加載會導致長時間白屏,影響用戶體驗。 3. CommonJS 規范使用一個名為`export`的對象來暴露模塊,將需要導出變量附加到`export`上,但是不能直接給該對象進行賦值。如果需要導出一個構造函數,則需要使用`module.export`,這會讓人感到很疑惑。 <br> AMD 規范定義了一個`define`全局方法用來定義和加載模塊,當然 RequireJS 后期也擴展了`require`全局方法用來加載模塊 。通過該方法解決了在瀏覽器使用 CommonJS 規范的不足。 ~~~ define(id?, dependencies?, factory); ~~~ <br> 1. 使用匿名函數來封裝模塊,并通過函數返回值來定義模塊,這更加符合 JavaScript 的語法,這樣做既避免了對`exports`變量的依賴,又避免了一個文件只能暴露一個模塊的問題。 2. 提前列出依賴項并進行異步加載,這在瀏覽器中,這能讓模塊開箱即用。 ~~~ define("foo", ["logger"], function (logger) { logger.debug("starting foo's definition") return { name: "foo" } }) ~~~ 3. 為模塊指定一個模塊 ID (名稱) 用來唯一標識定義中模塊。此外,AMD的模塊名規范是 CommonJS 模塊名規范的超集。 ~~~ define("foo", function () { return { name: 'foo' } }) ~~~ ### RequireJS 原理 在討論原理之前,我們可以先看下 RequireJS 的基本使用方式。 * 模塊信息配置: ~~~ require.config({ paths: { jquery: 'https://code.jquery.com/jquery-3.4.1.js' } }) ~~~ * 依賴模塊加載與調用: ~~~ require(['jquery'], function ($){ $('#app').html('loaded') }) ~~~ * 模塊定義: ~~~ if ( typeof define === "function" && define.amd ) { define( "jquery", [], function() { return jQuery; } ); } ~~~ 我們首先使用`config`方法進行了 jquery 模塊的路徑配置,然后調用`require`方法加載 jquery 模塊,之后在回調中調用已加載完成的`$`對象。在這個過程中,jquery 會使用`define`方法暴露出我們所需要的`$`對象。 在了解了基本的使用過程后,我們就繼續深入 RequireJS 的原理。 #### 模塊信息配置 模塊信息的配置,其實很簡單,只用幾行代碼就能實現。定義一個全局對象,然后使用`Object.assign`進行對象擴展。 ~~~ // 配置信息 const cfg = { paths: {} } // 全局 require 方法 req = require = () => {} // 擴展配置 req.config = config => { Object.assign(cfg, config) } ~~~ #### 依賴模塊加載與調用 `require`方法的邏輯很簡單,進行簡單的參數校驗后,調用`getModule`方法對`Module`進行了實例化,getModule 會對已經實例化的模塊進行緩存。因為 require 方法進行模塊實例的時候,并沒有模塊名,所以這里產生的是一個匿名模塊。Module 類,我們可以理解為一個模塊加載器,主要作用是進行依賴的加載,并在依賴加載完畢后,調用回調函數,同時將依賴的模塊逐一作為參數回傳到回調函數中。 ~~~ // 全局 require 方法 req = require = (deps, callback) => { if (!deps && !callback) { return } if (!deps) { deps = [] } if (typeof deps === 'function') { callback = deps deps = [] } const mod = getModule() mod.init(deps, callback) } let reqCounter = 0 const registry = {} // 已注冊的模塊 // 模塊加載器的工廠方法 const getModule = name => { if (!name) { // 如果模塊名不存在,表示為匿名模塊,自動構造模塊名 name = `@mod_${++reqCounter}` } let mod = registry[name] if (!mod) { mod = registry[name] = new Module(name) } return mod } ~~~ 模塊加載器是是整個模塊加載的核心,主要包括`enable`方法和`check`方法。 模塊加載器在完成實例化之后,會首先調用`init`方法進行初始化,初始化的時候傳入模塊的依賴以及回調。 ~~~ // 模塊加載器 class Module { constructor(name) { this.name = name this.depCount = 0 this.depMaps = [] this.depExports = [] this.definedFn = () => {} } init(deps, callback) { this.deps = deps this.callback = callback // 判斷是否存在依賴 if (deps.length === 0) { this.check() } else { this.enable() } } } ~~~ `enable`方法主要用于模塊的依賴加載,該方法的主要邏輯如下: 1. 遍歷所有的依賴模塊; 2. 記錄已加載模塊數 (`this.depCount++`),該變量用于判斷依賴模塊是否全部加載完畢; 3. 實例化依賴模塊的模塊加載器,并綁定`definedFn`方法; > `definedFn`方法會在依賴模塊加載完畢后調用,主要作用是獲取依賴模塊的內容,并將`depCount`減 1,最后調用`check`方法 (該方法會判斷`depCount`是否已經小于 1,以此來界定依賴全部加載完畢); 4. 最后通過依賴模塊名,在配置中獲取依賴模塊的路徑,進行模塊加載。 ~~~ class Module { ... // 啟用模塊,進行依賴加載 enable() { // 遍歷依賴 this.deps.forEach((name, i) => { // 記錄已加載的模塊數 this.depCount++ // 實例化依賴模塊的模塊加載器,綁定模塊加載完畢的回調 const mod = getModule(name) mod.definedFn = exports => { this.depCount-- this.depExports[i] = exports this.check() } // 在配置中獲取依賴模塊的路徑,進行模塊加載 const url = cfg.paths[name] loadModule(name, url) }); } ... } ~~~ `loadModule`的主要作用就是通過 url 去加載一個 js 文件,并綁定一個 onload 事件。onload 會重新獲取依賴模塊已經實例化的模塊加載器,并調用`init`方法。 ~~~ // 緩存加載的模塊 const defMap = {} // 依賴的加載 const loadModule = (name, url) => { const head = document.getElementsByTagName('head')[0] const node = document.createElement('script') node.type = 'text/javascript' node.async = true // 設置一個 data 屬性,便于依賴加載完畢后拿到模塊名 node.setAttribute('data-module', name) node.addEventListener('load', onScriptLoad, false) node.src = url head.appendChild(node) return node } // 節點綁定的 onload 事件函數 const onScriptLoad = evt => { const node = evt.currentTarget node.removeEventListener('load', onScriptLoad, false) // 獲取模塊名 const name = node.getAttribute('data-module') const mod = getModule(name) const def = defMap[name] mod.init(def.deps, def.callback) } ~~~ 看到之前的案例,因為只有一個依賴 (jQuery),并且 jQuery 模塊并沒有其他依賴,所以`init`方法會直接調用`check`方法。這里也可以思考一下,如果是一個有依賴項的模塊后續的流程是怎么樣的呢? ~~~ define( "jquery", [] /* 無其他依賴 */, function() { return jQuery; } ); ~~~ `check`方法主要用于依賴檢測,以及調用依賴加載完畢后的回調。 ~~~ // 模塊加載器 class Module { ... // 檢查依賴是否加載完畢 check() { let exports = this.exports //如果依賴數小于1,表示依賴已經全部加載完畢 if (this.depCount < 1) { // 調用回調,并獲取該模塊的內容 exports = this.callback.apply(null, this.depExports) this.exports = exports //激活 defined 回調 this.definedFn(exports) } } ... } ~~~ 最終通過`definedFn`重新回到被依賴模塊,也就是最初調用`require`方法實例化的匿名模塊加載器中,將依賴模塊暴露的內容存入`depExports`中,然后調用匿名模塊加載器的`check`方法,調用回調。 ~~~ mod.definedFn = exports => { this.depCount-- this.depExports[i] = exports this.check() } ~~~ #### 模塊定義 還有一個疑問就是,在依賴模塊加載完畢的回調中,怎么拿到的依賴模塊的依賴和回調呢? ~~~ const def = defMap[name] mod.init(def.deps, def.callback) ~~~ 答案就是通過全局定義的`define`方法,該方法會將模塊的依賴項還有回調存儲到一個全局變量,后面只要按需獲取即可。 ~~~ const defMap = {} // 緩存加載的模塊 define = (name, deps, callback) => { defMap[name] = { name, deps, callback } } ~~~ #### RequireJS 原理總結 最后可以發現,RequireJS 的核心就在于模塊加載器的實現,不管是通過`require`進行依賴加載,還是使用`define`定義模塊,都離不開模塊加載器。 <br> <br> ## CMD CMD 規范由國內的開發者玉伯提出,盡管在國際上的知名度遠不如 AMD ,但是在國內也算和 AMD 齊頭并進。相比于 AMD 的異步加載,CMD 更加傾向于懶加載,而且 CMD 的規范與 CommonJS 更貼近,只需要在 CommonJS 外增加一個函數調用的包裝即可。 ~~~ define(function(require, exports, module) { require("./a").doSomething() require("./b").doSomething() }) ~~~ 作為 CMD 規范的實現 sea.js 也實現了類似于 RequireJS 的 api: ~~~ seajs.use('main', function (main) { main.doSomething() }) ~~~ sea.js 在模塊加載的方式上與 RequireJS 一致,都是通過在 head 標簽插入 script 標簽進行加載的,但是在加載順序上有一定的區別。要講清楚這兩者之間的差別,我們還是直接來看一段代碼: **RequireJS**: ~~~ // RequireJS define('a', function () { console.log('a load') return { run: function () { console.log('a run') } } }) define('b', function () { console.log('b load') return { run: function () { console.log('b run') } } }) require(['a', 'b'], function (a, b) { console.log('main run') a.run() b.run() }) ~~~ ![requirejs result](https://segmentfault.com/img/remote/1460000020621570 "requirejs result") **sea.js**: ~~~ // sea.js define('a', function (require, exports, module) { console.log('a load') exports.run = function () { console.log('a run') } }) define('b', function (require, exports, module) { console.log('b load') exports.run = function () { console.log('b run') } }) define('main', function (require, exports, module) { console.log('main run') var a = require('a') a.run() var b = require('b') b.run() }) seajs.use('main') ~~~ ![sea.js result](https://segmentfault.com/img/remote/1460000020621571 "sea.js result") 可以看到 sea.js 的模塊屬于懶加載,只有在 require 的地方,才會真正運行模塊。而 RequireJS,會先運行所有的依賴,得到所有依賴暴露的結果后再執行回調。 正是因為懶加載的機制,所以 sea.js 提供了`seajs.use`的方法,來運行已經定義的模塊。所有 define 的回調函數都不會立即執行,而是將所有的回調函數進行緩存,只有 use 之后,以及被 require 的模塊回調才會進行執行。 ### sea.js 原理 下面簡單講解一下 sea.js 的懶加載邏輯。在調用 define 方法的時候,只是將 模塊放入到一個全局對象進行緩存。 ~~~ const seajs = {} const cache = seajs.cache = {} define = (id, factory) => { const uri = id2uri(id) const deps = parseDependencies(factory.toString()) const mod = cache[uri] || (cache[uri] = new Module(uri)) mod.deps = deps mod.factory = factory } class Module { constructor(uri, deps) { this.status = 0 this.uri = uri this.deps = deps } } ~~~ 這里的 Module,是一個與 RequireJS 類似的模塊加載器。后面運行的 seajs.use 就會從緩存取出對應的模塊進行加載。 > 注意:這一部分代碼只是簡單介紹 use 方法的邏輯,并不能直接運行。 ~~~ let cid = 0 seajs.use = (ids, callback) => { const deps = isArray(ids) ? ids : [ids] deps.forEach(async (dep, i) => { const mod = cache[dep] mod.load() }) } ~~~ 另外 sea.js 的依賴都是在 factory 中聲明的,在模塊被調用的時候,sea.js 會將 factory 轉成字符串,然后匹配出所有的`require('xxx')`中的`xxx`,來進行依賴的存儲。前面代碼中的`parseDependencies`方法就是做這件事情的。 早期 sea.js 是直接通過正則的方式進行匹配的: ~~~ const parseDependencies = (code) => { const REQUIRE_RE = /"(?:\\"|[^"])*"|'(?:\\'|[^'])*'|\/\*[\S\s]*?\*\/|\/(?:\\\/|[^/\r\n])+\/(?=[^\/])|\/\/.*|\.\s*require|(?:^|[^$])\brequire\s*\(\s*(["'])(.+?)\1\s*\)/g const SLASH_RE = /\\\\/g const ret = [] code .replace(SLASH_RE, '') .replace(REQUIRE_RE, function(_, __, id) { if (id) { ret.push(id) } }) return ret } ~~~ 但是后來發現正則有各種各樣的 bug,并且過長的正則也不利于維護,所以 sea.js 后期舍棄了這種方式,轉而使用狀態機進行詞法分析的方式獲取 require 依賴。 詳細代碼可以查看 sea.js 相關的子項目:[crequire](https://github.com/seajs/crequire)。 #### sea.js 原理總結 其實 sea.js 的代碼邏輯大體上與 RequireJS 類似,都是通過創建 script 標簽進行模塊加載,并且都有實現一個模塊記載器,用于管理依賴。 主要差異在于,sea.js 的懶加載機制,并且在使用方式上,sea.js 的所有依賴都不是提前聲明的,而是 sea.js 內部通過正則或詞法分析的方式將依賴手動進行提取的。 <br> ## CommonJS 十年前的前端沒有像現在這么火熱,模塊化也只是使用閉包簡單的實現一個命名空間。2009 年對 JavaScript 無疑是重要的一年,新的 JavaScript 引擎 (v8) ,并且有成熟的庫 (jQuery、YUI、Dojo),ES5 也在提案中,然而 JavaScript 依然只能出現在瀏覽器當中。早在2007年,AppJet 就提供了一項服務,創建和托管服務端的 JavaScript 應用。后來 Aptana 也提供了一個能夠在服務端運行 Javascript 的環境,叫做 Jaxer。網上還能搜到關于 AppJet、Jaxer 的博客,甚至 Jaxer 項目還在[github](https://github.com/aptana/Jaxer)上。 <br> ![Jaxer](https://image-static.segmentfault.com/312/044/3120446892-5d9d3d8640748_articlex) <br> 但是這些東西都沒有發展起來,Javascript 并不能替代傳統的服務端腳本語言 (PHP、Python、Ruby) 。盡管它有很多的缺點,但是不妨礙有很多人使用它。后來就有人開始思考 JavaScript 要在服務端運行還需要些什么?于是在 2009 年 1 月,Mozilla 的工程師[Kevin Dangoor](http://www.kevindangoor.com/)發起了 CommonJS 的提案,呼吁 JavaScript 愛好者聯合起來,編寫 JavaScript 運行在服務端的相關規范,一周之后,就有了 224 個參與者。 <br> > "\[This\] is not a technical problem,It's a matter of people getting together and making a decision to step forward and start building up something bigger and cooler together." <br> CommonJS 標準囊括了 JavaScript 需要在服務端運行所必備的基礎能力,比如:模塊化、IO 操作、二進制字符串、進程管理、Web網關接口 (JSGI) 。但是影響最深遠的還是 CommonJS 的模塊化方案,CommonJS 的模塊化方案是JavaScript社區第一次在模塊系統上取得的成果,不僅支持依賴管理,而且還支持作用域隔離和模塊標識。再后來 node.js 出世,他直接采用了`CommonJS`的模塊化規范,同時還帶來了npm (Node Package Manager,現在已經是全球最大模塊倉庫了) 。 <br> CommonJS 在服務端表現良好,很多人就想將 CommonJS 移植到客戶端 (也就是我們說的瀏覽器) 進行實現。由于CommonJS 的模塊加載是同步的,而服務端直接從磁盤或內存中讀取,耗時基本可忽略,但是在瀏覽器端如果還是同步加載,對用戶體驗極其不友好,模塊加載過程中勢必會向服務器請求其他模塊代碼,網絡請求過程中會造成長時間白屏。所以從 CommonJS 中逐漸分裂出來了一些派別,在這些派別的發展過程中,出現了一些業界較為熟悉方案 AMD、CMD、打包工具(Component/Browserify/Webpack)。 ~~~ // a.js module.exports = { a: 1 } // or exports.a = 1 // b.js var module = require('./a.js') module.a // -> log 1 ~~~ 因為 CommonJS 還是會使用到的,所以這里會對一些疑難點進行解析 <br> 先說`require`吧 ~~~ var module = require('./a.js') module.a // 這里其實就是包裝了一層立即執行函數,這樣就不會污染全局變量了, // 重要的是 module 這里,module 是 Node 獨有的一個變量 module.exports = { a: 1 } // module 基本實現 var module = { id: 'xxxx', // 我總得知道怎么去找到他吧 exports: {} // exports 就是個空對象 } // 這個是為什么 exports 和 module.exports 用法相似的原因 var exports = module.exports var load = function (module) { // 導出的東西 var a = 1 module.exports = a return module.exports }; // 然后當我 require 的時候去找到獨特的 // id,然后將要使用的東西用立即執行函數包裝下,over ~~~ 另外雖然`exports`和`module.exports`用法相似,但是不能對`exports`直接賦值。因為`var exports = module.exports`這句代碼表明了`exports`和`module.exports`享有相同地址,通過改變對象的屬性值會對兩者都起效,但是如果直接對`exports`賦值就會導致兩者不再指向同一個內存地址,修改并不會對`module.exports`起效。 <br> <br> # 參考資料 * 前端面試之道 - 掘金小冊 * [前端模塊化的前世](https://segmentfault.com/a/1190000020621564)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看