首頁  >  文章  >  web前端  >  Js中的模組化是如何實現的

Js中的模組化是如何實現的

小云云
小云云原創
2018-01-09 17:01:532176瀏覽

由於 Js 起初定位的原因(剛開始沒想到會應用在過於複雜的場景),所以它本身並沒有提供模組系統,隨著應用的複雜化,模組化成為了一個必須解決的問題。本著菲麥深入原理的原則,很有必要來揭開模組化的面紗,本文主要介紹了詳解Js中的模組化是如何實現的,詳細的介紹了模組化的運行,具有一定的參考價值,有興趣的可以了解下,希望能幫助大家。

一、模組化需要解決的問題

要對一個東西進行深入的剖析,有必要帶著目的去看。模組化所要解決的問題可以用一句話概括

在沒有全局污染的情況下,更好的組織項目代碼

舉一個簡單的栗子,我們現在有如下的程式碼:

function doSomething () {
 const a = 10;
 const b = 11;
 const add = function (a + b) {
  return a + b
 }
 add (a + b)
}

在現實的應用場景中,doSomething 可能需要做很多很多的事情,add 函數可能也更為複雜,並且可以復用,那麼我們希望可以將add 函數獨立到一個單獨的文件中,於是:

// doSomething.js 文件
const add = require('add.js');
const a = 10;
const b = 11;
add(a+ b);
// add.js 文件
function add (a, b) {
 return a + b;
}
module.exports = add;

這樣做的目的顯而易見,更好的組織項目代碼,注意到兩個文件中的require 和module.exports,從現在的上帝視角來看,這出自CommonJS規範(後文會有一個章節來專門講規範)中的關鍵字,分別代表導入和導出,拋開規範而言,這其實是我們模組化之路上需要解決的問題。另外,雖然add 模組需要重複使用,但是我們並不希望在引入add 的時候造成全局污染

二、引入的模組如何運行

在上述的例子中,我們已經將程式碼拆分到了兩個模組檔案當中,在不造成全域污染的情況下,如何實作require,才能讓範例中的程式碼做到正常運作呢?

先不考慮模組檔案程式碼的載入過程,假設require 已經可以從模組檔案讀取到程式碼字串,那麼require 可以這樣實作

function require (path) {
  // lode 方法读取 path 对应的文件模块的代码字符串
  // let code = load(path);
  // 不考虑 load 的过程,直接获得模块 add 代码字符串
  let code = 'function add(a, b) {return a+b}; module.exports = add';
  // 封装成闭包
  code = `(function(module) {$[code]})(context)`
  // 相当于 exports,用于导出对象
  let context = {};
  // 运行代码,使得结果影响到 context
  const run = new Function('context', code);
  run(context, code);
  //返回导出的结果
  return context.exports;
}

這有幾個要點:

1) 為了不造成全域污染,需要將程式碼字串封裝成閉包的形式,並且導出關鍵字module.exports ,module 是與外界聯繫的唯一載體,需要作為閉包匿名函數的入參,與引用方傳入的上下文context 進行關聯

2) 使用new Function 來執行程式碼字串,估計大部分同學對new Function 是不熟悉的,因為一般情況下定義一個函數無需如此,要知道,用Function 類別可以直接建立函數,語法如下:

var function_name = new function(arg1, arg2, ..., argN, function_body)

在上面的形式中,每個arg 都是一個參數,最後一個參數是函數主體(要執行的程式碼)。這些參數必須是字串。也就是說,可以使用它來執行字串程式碼,類似於eval,並且相比eval, 還可以透過參數的形式傳入字串程式碼中的某些變數的值

3 )如果曾經你有疑惑過為什麼規範的匯出關鍵字只有exports 而我們實際使用過程中卻要使用module.exports(寫過Node 程式碼的應該不會陌生),那在這段程式碼中就可以找到答案了,如果只用exports 來接收context,那麼對exports 的重新賦值對context 不會有任何影響(參數的位址傳遞),不信將程式碼改成如下形式再跑一跑:

#示範結果

三、程式碼載入方式

解決了程式碼的執行問題,還需要解決模組檔案程式碼的載入問題,根據上述實例,我們的目標是將模組檔案程式碼以字串的形式載入

在Node 容器,所有的模組檔案都在本地,只需要從本地磁碟讀取模組檔案載入字串程式碼,再走上述的流程就可以了。事實證明,Node 非內建、核心、c++ 模組的載入執行方式大致如此(雖然使用的不是new Function,但也是一個類似的方法)

在RN/Weex 容器,要載入一個遠端bundle.js,可以透過Native 的能力請求一個遠端的js 文件,再讀取成字串程式碼載入即可(按照這個邏輯,Node 讀取一個遠端的js 模組好像也無不可,雖然大多數情況下我們不需要這麼做)

在瀏覽器環境,所有的Js 模組都需要遠端讀取,尷尬的是,受限於瀏覽器提供的能力,並不能透過ajax 以檔案流的形式將遠端的js 檔案直接讀取為字串程式碼。前提條件無法達成,上述運行策略便行不通,只能另闢蹊徑

這就是為什麼有了CommonJs 規範了,為什麼還會出現AMD/CMD 規範的原因

#那麼瀏覽器上是怎麼做的呢?在瀏覽器中透過 Js 控制動態的載入一個遠端的 Js 模組文件,需要動態的插入一個