關於模塊化立即執行函數和ESModule的詳解

立即執行函數

在瞭解立即執行函數之前,先明確一下函數聲明,函數表達式以及匿名函數的形式

function test(){//函數聲明
   console.log('test')
}
var test=function(){//函數表達式
    console.log('test')
}
function(){//匿名函數
    console.log('test')
}
什麼是立即執行函數

立即函數就是

  • 聲明一個匿名函數
  • 馬上調用這個匿名函數
    接下來看看立即執行函數的兩種常見形式
(function(){})()

(function(){}())

一個是一個匿名函數包裹在一個括號中,後面跟着一個小括號

另一個是匿名函數後面跟一個小括號,然後整個包裹在一個括號運算符中。

這兩種寫法是等價的。想要立即執行函數能做到立即執行,要注意兩點:
1 函數體後面要有小括號
2 函數體必須是函數表達式而不能是函數聲明

;-(function() {
  /* */
})()

+(function() {
 /* */
})()

~(function() {
  /* */
})()

!(function() {
  /* */
})()

從上可以看出,除了使用()運算符之外,!,+,-,=等運算符都能起到立即執行的作用。這些運算符的作用就是將匿名函數或函數聲明轉換爲函數表達式

爲什麼要使用括號把匿名函數包起來呢?

小括號能把我們的表達式組合分塊,並且每一塊,也就是每一對小括號,都有一個返回值。這個返回值實際上也就是小括號中表達式的返回值。所以,當我們用一對小括號把匿名函數括起來的時候,實際上小括號對返回的,就是一個匿名函數的Function對象。因此,小括號對加上匿名函數就如同有名字的函數般被我們取得它的引用位置了。所以如果在這個引用變量後面再加上參數列表,就會實現普通函數的調用形式。

立即執行函數有什麼作用?

只有一個作用:創建一個獨立的作用域。

通過定義一個匿名函數,創建了一個新的函數作用域,相當於創建了一個“私有”的命名空間,該命名空間的變量和方法,不會破壞污染全局的命名空間

ESModule
ES模塊引入主要有以下幾個優點:

1 可以將代碼分隔成功能獨立的更小的文件
2 有助於消除命名衝突
3 不在需要對象作爲命名空間(比如Math對象),不會污染全局變量
4 ES6模塊在編譯時就能確認模塊的依賴關係,以及輸入和輸出的變量,從而可以進行靜態優化

ES模塊的基本用法

模塊功能中主要以下幾個關鍵詞:export,import,as,default *

  • export用於規定輸出模塊的對外接口
  • import用於輸入模塊提供的接口
  • as用於重命名輸出和輸入接口
  • *表示輸入模塊的所有接口
export

用法1:直接輸出一個變量聲明,函數聲明或者類聲明

export var m = 1;

export function m() {};

export class M {};

用法2:輸出內容爲大括號包裹的一組變量,切記 export不能直接輸出常規的對象

var m1=1

var m2=2

export {m1,m2}

用法3:輸出指定變量,並重名,則外部引入時得到是as後的名稱

var n =1 

export {n as m}

用戶4:使用default輸出默認接口,default後可跟值或變量

export default 1

var m=1

dexport default m
錯誤用法
//用法1
export 1 ;

export {m:'1'};

//用法2
var m=1

export m;

//用法3 
function foo(){
    export default 'bar' //SyntaxError
}

用法1 和用法2 錯誤相同,export必須輸入一個接口,不能輸出一個值(哪怕對象也不行)
或者一個已賦值的變量,已賦值的變量對應的也是一個值。上述常規用法中,
export default後之所以可以直接跟值是因爲default爲輸出的接口

用法3錯誤的原因:export只能出現在模塊的頂層作用域,不能存在塊級作用域中,如果出現在塊級
作用域的話,就沒法做靜態優化了,這違背ES6中的模塊的設計初衷

import
//用法1  進執行 moduleA 模塊,不輸入任何值(沒啥用但是合法)
import 'moduleA'

//用法2 輸入 moduleA的默認接口,默認接口重命名爲m
import m from 'moduleA'

//用法3 輸入moduleA 的m接口
import {m} from 'moduleA'

//用法4 輸入moduleA的m接口,使用as重命名m接口
import {m as myM} from 'moduleA'

//用法5:導入所有接口
import * as all from 'moduleA'

需要注意的是,如果多次重複執行同一句import語句,那麼只會執行一次,而不會執行多次

//用法1 重複引入 module1 只執行一次
import 'module1'
import 'module2'


//用法2
import {m1} from 'module'
import {m2} from 'module'

此外,import命名輸入的變量都是隻讀的,加載後不能修改接口

import { m } from 'my_module';

m = 1; // SyntaxError: "m" is read-only

如果m是一個對象,改寫m的屬性是可以的。但是筆者不建議這麼做

需要注意的是,import也必須在頂級作用域內,並且其中不能使用表達式和變量。其常見的錯誤用法示例如下:

// 用法1:不能使用表達式

import { 'm' + '1' } from 'my_module';

 

// 用法2:不能使用變量

let module = 'my_module';

import { m } from module;

 

// 用法3:不能用於條件表達式

if (x === 1) {

  import { m } from 'module1';

} else {

  import { m } from 'module2';

}
如何在瀏覽器中下快速使用import?

各大瀏覽器已經開始逐步支持ES模塊了,如果我們想在瀏覽器中使用模塊,可以在script標籤上添加一個type="module"的屬性來表示這個文件是以module的方式來運行的。如下:

// myModule.js

export default {

  name: 'my-module'

}

 

// script腳本引入

<script type="module">

  import myModule from './myModule.js'

 

  console.log(myModule.name) // my-module

</script>

不過,由於ES的模塊功能還沒有完全支持,在不支持的瀏覽器下,我們需要一些回退方案,可以通過nomodule屬性來指定某腳本爲回退方案。如下,在支持的瀏覽器中進行提示。

<script type="module">

  import myModule from './myModule.js'

</script>

 

<script nomodule>

  alert('你的瀏覽器不支持ES模塊,請先升級!')

</script>

如上,當瀏覽器支持type=module時,會忽略帶有nomodule的script;如果不支持,則忽略帶有type=module的腳本,執行帶有nomodule的腳本。

在使用type=module引入模塊時還有一點需要注意的,module的文件默認爲defer,也就是說該文件不會阻塞頁面的渲染,會在頁面加載完成後按順序執行。

當心,不要修改export輸出的對象

前面有提到如果export輸出的接口是一個對象,那麼是可以修改這個對象的屬性的。

而我的建議是,儘管你能改,也不要修改。

大家可能都會有這樣一個常規的用法,即在編寫某個組件時,可能會存在包含基礎配置的代碼,我們姑且稱其爲options.js,其輸出一堆配置文件。

// options.js

export default {

  // 默認樣式

  style: {

    color: 'green',

    fontSize: 14,

  }

}

如果你沒有類似需求,你可以想象下,你現在要把EChart的某個圖表抽象成自己代碼庫裏的組件,那麼這時候應該就有一大堆基礎配置文件了。

既然稱其爲基礎配置,那麼言外之意就是,根據組件的用法不同,會一定程度上對配置進行修改。比如我們會在引入後將顏色改爲紅色

// use-options.js

import options from "./options.js";

 

console.log(options); // { style: { color: 'green', fontSize: 14 } }

 

options.style.color = "red";

這時候就需要格外注意了,如果我們直接對輸入的默認配置對象進行修改,就可能會導致一些bug。

因爲export輸出的值是動態綁定的,如果我們修改了其中的值,就會導致其他地方再次引入該值時會發生變化,此時的默認配置就不是我們所設想的默認配置了。如上例,我們再次引入基礎配置後,就會發現顏色的默認值已經變成紅色了。

這時候就需要格外注意了,如果我們直接對輸入的默認配置對象進行修改,就可能會導致一些bug。

因爲export輸出的值是動態綁定的,如果我們修改了其中的值,就會導致其他地方再次引入該值時會發生變化,此時的默認配置就不是我們所設想的默認配置了。如上例,我們再次引入基礎配置後,就會發現顏色的默認值已經變成紅色了。

所以,筆者建議,當我們有需求對輸入的對象接口進行改變時,可以先對其進行深度複製,然後在進行修改,這樣就不會導致上述所說的問題了。如下所示:

// use-options.js

import _ from "./lodash.js";

import options from "./options.js";

 

const myOptions = _.cloneDeep(options);

console.log(myOptions); // { style: { color: 'green', fontSize: 14 } }

myOptions.style.color = "red";
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章