前端模塊化淺析

平時寫代碼的時候,知道如何導出變量,如何引入變量。可見模塊化就在咱們的身邊,但是爲何前端會引入模塊化的概念,以及爲何有同步加載和異步加載呢?javascript

爲何要模塊化

在以前的項目中,若是沒有模塊化的概念,不少變量都有重名或者不當心從新賦值的危險。並且用 script 有可能阻塞 HTML 的下載或者渲染,影響用戶體驗。css

在平時編碼中,咱們都習慣把一些通用的方法提出來放在一個文件裏,哪一個地方須要用到就引用,這樣可以很好的梳理頁面的邏輯,維護代碼的成本也下降了很多。因此模塊化給咱們帶來的好處是顯而易見的。html

  • 分離: 代碼須要分離成小塊,以便能爲人所理解。
  • 可組合性: 在一個文件中編碼,被許多其餘文件重複使用。這提高了代碼庫的靈活性。
  • 解決全局變量重名問題
  • 提升複用性

現有的一些模塊化方案有如下幾種:前端

  • ES 6 模塊
  • Commonjs
  • AMD
  • CMD

下面我就自身的理解對這幾種方案作一個對比和總結:java

ES6 Module

  • 編譯時就能肯定模塊的依賴關係

ES6 模塊遇到 import 命令時,不會去執行模塊,而是生成一個引用,等用到的時候,纔去模塊中取值。由於是動態引用,因此不存在緩存的問題。能夠看一下下面的例子:node

// util.js
export let env = 'qa';
setTimeout(() => env = 'local', 1000);

// main.js
import {env} from './util';
console.log('env:', env);

setTimeout(() => console.log('new env:', env), 1500);複製代碼

執行 main.js,會輸出下面的結果:react

// env: qa
// new env: local複製代碼

能夠看出 ES6 模塊是動態的取值,不會緩存運行的結果。es6

目前瀏覽器還沒有支持 ES6 模塊 ,因此須要使用 babel 轉換,你們能夠在 Babel 提供的 REPL 在線編譯器 中查看編譯後的結果。瀏覽器

// es 6
import {add} from './config';

// es 5
'use strict';
var _config = require('./config');複製代碼

能夠看出,最後轉換成 require 的方式了。ES6 模塊在瀏覽器和服務器端均可以用,ES6 模塊的設計思想是儘可能的靜態化,使得編譯時就能肯定模塊的依賴關係,以及輸入和輸出的變量。緩存

import 命令具備提高效果,會提高到整個模塊的頭部,首先執行,因此不能把 import 寫在表達式裏面。這和 ES 6 模塊的概念不符合。

CommonJS

  • 用於服務器端
  • 只能在運行時肯定
  • 同步加載
  • 模塊加載的順序,按照其在代碼中出現的順序。

node 的模塊遵循 CommonJS 規範。在服務器端,依賴是保存在本地硬盤的,因此讀取的速度很是快,使用同步加載不會有什麼影響。

看一下 CommonJS 的語法:

// header.js
module.exports = {
    title: '我是柚子'
};

// main.js
var header = require('./header');複製代碼

module

這裏的 module 表明的是當前模塊,它是一個對象,把它打印出來是下面的結果:

{
Module {
  id: '/Users/yanmeng/2017FE/css-animation/js/b.js',
  exports: { item: 'item' },
  parent:
   Module {
     id: '.',
     exports: {},
     parent: null,
     filename: '/Users/yanmeng/2017FE/css-animation/js/main.js',
     loaded: false,
     children: [ [Circular] ],
     paths:
      [ '/Users/yanmeng/2017FE/css-animation/js/node_modules',
        '/Users/yanmeng/2017FE/css-animation/node_modules',
        '/Users/yanmeng/2017FE/node_modules',
        '/Users/yanmeng/node_modules',
        '/Users/node_modules',
        '/node_modules' ] },
  filename: '/Users/yanmeng/2017FE/css-animation/js/b.js',
  loaded: false,
  children: [],
  paths:
   [ '/Users/yanmeng/2017FE/css-animation/js/node_modules',
     '/Users/yanmeng/2017FE/css-animation/node_modules',
     '/Users/yanmeng/2017FE/node_modules',
     '/Users/yanmeng/node_modules',
     '/Users/node_modules',
     '/node_modules' 
    ] 
}複製代碼
  • id 是該模塊的 id
  • loaded 表明改模塊是否加載完畢
  • exports 是一個對象,裏面有模塊輸出的各個接口。

以後調用這個模塊的時候,就會從 exports 中取值,即便再執行,也不會再執行改模塊,而是從緩存中取值,返回的是第一次運行的結果,除非手動清除緩存。

// 刪除指定模塊的緩存
delete require.cache[moduleName];

// 刪除全部模塊的緩存
Object.keys(require.cache).forEach(function(key) {
  delete require.cache[key];
})複製代碼

緩存是根據絕對路徑識別模塊的,若是同一個模塊放在不一樣的路徑下,仍是會從新加載這個模塊。

require

require 命令第一次執行的時候,會加載並執行整個腳本,而後在內存中生成此腳本返回的 exports 對象。

ES6 模塊與 CommonJS 模塊的差別

  • CommonJS 模塊輸出的是一個值的拷貝,ES6 模塊輸出的是值的引用。
  • CommonJS 模塊是運行時加載,ES6 模塊是編譯時輸出接口。

ES6 模塊是動態引用,而且不會緩存值。

ES6 模塊在對腳本靜態分析的時候,遇到 import 就會生成一個只讀引用,等到腳本真正執行的時候,再根據這個只讀引用,到被加載的那個模塊裏取值,因此說 ES6 模塊是動態引用。
從依賴中引入的模塊變量是一個地址引用,是隻讀的,能夠爲它新增屬性,但是不能從新賦值。

// lib.js
export let obj = {};

// main.js
import { obj } from './lib';

obj.prop = 123; // OK
obj = {}; // TypeError複製代碼

AMD

又稱異步加載模塊(Asynchronous Module Definition)

  • 依賴前置
  • 比較適合瀏覽器環境
  • 實現 js 文件的異步加載,避免網頁失去響應
  • 管理模塊之間的依賴性,便於代碼的編寫和維護
  • 表明庫: RequireJS

若是在瀏覽器環境,就須要在服務端加載模塊,那麼採用同步加載的方法就會影響用戶體驗,因此瀏覽器端通常採用 AMD 規範。

它採用異步方式加載模塊,模塊的加載不影響它後面語句的運行。全部依賴這個模塊的語句,都定義在一個回調函數中,等到加載完成以後,這個回調函數纔會運行。

// lib.js
  define('[./util.js]', function(util){
      function bar() {
          util.log('it is sunshine');
      };
      return {
          bar: bar
      };
  });
  
// main.js
require(['./lib.js'], function(lib){
    console.log(lib.bar());
})複製代碼

CMD

  • 依賴就近
  • 須要用到依賴的時候才申明
  • 表明庫: Sea.js

Sea.js 實現了這個規範,Sea.js 遇到依賴後只會去下載 JS 文件,並不會執行,而是等到全部被依賴的 JS 腳本都下載完之後,才從頭開始執行主邏輯。所以被依賴模塊的執行順序和書寫順序徹底一致。

define(function(require, exports, module) {
    var a = require('./a')
    a.doSomething()
    // ...
    var b = require('./b') 
    b.doSomething()
    // ...
})複製代碼

本文只是淺顯的介紹了一些模塊的概念和用法,關於 ES6 模塊、 CommonJs 的循環加載和 ES 6 模塊和 CommonJs的互相引用,你們能夠動手實踐一下,會受益不淺。

參考:

相關文章
相關標籤/搜索