Es6中的模塊化Module,導入(import)導出(export)


如果你想閱讀體驗更好直戳鏈接Es6中的模塊化Module,導入(import),導出(export)

在Es6之前,javascript沒有模塊系統,它無法將一個大程序拆分成若干個互相依賴的小文件,然后在用簡單的方法拼裝起來.為了做到模塊化,在Es6之前,引入了AMD(Asynchronous module definition)與CMD(common module definition)

前者典型代表是requireJS(外國人搞出來的),后者是seajs(國內)

共同點:都是對模塊定義的不同規范,都是異步加載模塊,並且解決文件之間的依賴重命名沖突等問題。

不同點:模塊定義的方式和模塊加載機制是不同的,前者AMD(requirejs)是將所有文件同時加載,一次性引入,推崇依賴前置,也就是在定義模塊時要先聲明其依賴的模塊,加載完模塊后會立馬執行該模塊(運行時加載)

CMD(seajs)強調的是一個文件一個模塊,可按需引入,推崇依賴就近,加載完某個模塊后不會立即執行,而是等遇到了require語句的時候在執行 .

兩者的使用加載機制不同,也就導致了AMD(requirejs)模塊會提前執行,用戶體驗好,而CMD(seajs)性能好,因為只有在需要時候才執行,在服務器端,nodejs使用的就是cmd規范,也就是需要什么包,就引入什么包,按需加入(編譯時加載)

而在Es6的語言規格中引入了模塊化功能,也就很好的取代了之前的commonjs和AMD規范了,成為了瀏覽器和服務器的通用的模塊解決方案,在現今(vuejs,ReactJS)等框架大行其道中,都引入了Es6中的模塊化(Module)機制,一些自動化打包工具webpack或者微信小游戲中也同樣如此

您將在本文中學習到什么是模塊,以及模塊的導入導出,理解了這個,在一些基於腳手架搭建的項目里或者自動化構建工具中,就不覺得寫法怪怪和迷路了的

正文從這里開始~

什么是模塊?

在Es6中引入let,const定義變量是解決訪問變量的全局作用域問題,從而引入塊級作用域,解決命名沖突,同名全局污染,安全等問題

模塊可以理解為函數代碼塊的功能,是封裝對象的屬性和方法的javascript代碼,它可以是某單個文件,變量或者函數,

在Es6模塊中,無論有沒有加"use strict",都會自動采用嚴格模式,而且在模塊頂部創建的變量不會自動被添加全局作用域中,這個變量僅在模塊的頂級作用域中存在,而且模塊必須導出一些外部代碼可以訪問的元素,如變量或者函數,模塊也可以從其他模塊導入綁定

在模塊與模塊之間的特性與作用域關系不大(例如微信小程序或者小游戲中的各個文件就是不同的模塊,在該文件定義的變量或者函數只在該文件內作用),但也很重要,在模塊的頂部,this的值是undefined,另外,模塊不支持HTML風格的代碼注釋

模塊實質上是對業務邏輯分離實現低耦合高內聚,也便於代碼管理而不是所有功能代碼堆疊在一起,模塊真正的魔力所在是僅導出和導入你需要的綁定,而不是將所有的東西都放到一個文件

引入模塊與引入腳本是有區別的,前者更多是按需引入加載,后者而是無論有沒有用,全部一次性引入和加載,類似於通過script標簽引入jQuery等庫都是一次性載入

Node中模塊的導出與導入

在Node模塊中,采用的是commonjs規范,也就是使用require方式引入模塊,而使用module.exports導出接口,在node中,例如如下代碼example.js,當然你也是可以把屬性值定義到外面去的,把下面這段代碼存儲腳本為example

/* * 通過module.exports將數據進行對外暴露 */ module.exports = { name:"隨筆川跡", funA:function(){ return `我是${this.name}` } } // 或者把變量函數值定義在外面,例如,與上面等價,以下是常見寫法 var name = "隨筆川跡"; var funA = function(){ return `我是${name}` } module.exports = { name:name, // 至於前面的變量名可以任意,但是在另外一模塊中引入時要與該變量名保持一致,否則就會報錯,也可以只寫一個name funA:funA // 也可以只寫一個funA 

而在另外一文件命名requireExample.js中使用require方式引入

/* * * 通過require()的方式將外部模塊引入 * */ var m = require("./requireExample.js"); console.log(m.name); // 隨筆川跡 console.log(m.funA()); // 我是隨筆川跡 

執行結果如下圖所示

 
image

以上代碼是在node中, 通過module.exports對外暴露變量對象,函數等常見方式,而通過require()的方式引入本地模塊或者導入包

 

這個module.exports是node提供的一個私有全局變量屬性,而require也是node提供的一個私有全局方法,那么在Es6模塊中並沒有采用node中require導入模塊的方式

在微信小程序中,暫不支持Es6中的export和import模塊導出與導入的語法,它依然采用的是類似node中對外暴露數據用module.exports方式,而引入數據則用require的方式,勾選了微信開發者工具底下Es5轉Es6,使用Es6中模塊化,仍然會報錯

注意:小程序中用import方式引入外部wxss是可以的,但在微信小游戲中卻已經支持來Es6中的export與import模塊語法

如下為小游戲測試:Es6中export與import的使用,但遺憾的是在小程序暫且還不支持Es6中模塊的寫法,對外暴露數據仍然采用module.export 的方式而引入模塊采用require的方式,與在node中使用相似
[圖片上傳失敗...(image-f6ee80-1533184743362)]

如何檢測node.js對Es6的支持情況

命令行終端下全局安裝es-checker

npm install -g es-checker

安裝后,在命令行中執行 es-checker命令

es-checker

在命令行終端就會有一個Es6在該node版本中支持結果:如下圖所示,紅色的表示是暫不支持的


 
image

另外一種檢測Es6的方法是:在node的repl環境中測試,如果不支持就會報錯,運行正常就說明支持Es6寫法

還有一種檢測方法就是:參考官方文檔Es6對Node或者瀏覽器的支持情況具體可Ecmascript6 compatibility Table(https://kangax.github.io/compat-table/es6/),微信不支持訪問外鏈,直接將地止復制到瀏覽器訪問即可

Es6中模塊導出的基本語法

模塊的導出,export關鍵字用於暴露數據,暴露給其他模塊

使用方式是,可以將export放在任何變量,函數或類聲明的前面,從而將他們從模塊導出,而import用於引入數據,例如如下所示

將下面這些js存儲到exportExample.js中,分別導出的是數據,函數,類

/* * * @authors 隨筆川跡 (itclanCode@163.com) * @date 2018-07-07 18:01:23 * @desc:導出數據 * */ // 導出數據 export var name = "隨筆川跡"; // 導出暴露name變量 export let weChatPublic = "itclanCoder"; // 暴露weChatPublic export const time = 2018; // 暴露time // 導出函數 export function sum(num1,num2){ return num1+num2; } /* * * 以上等價於 * function sum(num1,num2){ * return num1+num2; * } * export sum; * */ // 導出類 export class People{ constructor(name,age){ this.name = name; this.age = age; } info(){ return `${this.name}${this.age}歲了`; } } 

若將上面代碼進行拆分

1. 導出數據,變量前面加上export關鍵字

export var name = "隨筆川跡"; export let weChatPublic = "itclanCoder"; export const time = 2018; // 上面的等價於下面的寫法,以下這種是常見寫法 var name = "隨筆川跡"; let weChatPublic = "itclanCoder"; const time = 2018; export {name,weChatPublic,time} 

2. 導出函數,函數前面加上export關鍵字

export function sum(num1,num2){ return num1+num2; } 

也可以這樣:在定義它時沒有馬上導出它,由於不必總是導出聲明,可以導出引用,因此下面這段代碼也是可以運行的

function sum(num1,num2){ return num1+num2; } // 之后將其導出 export sum; 

注意:一個模塊就是一個獨立的文件,該文件內部的所有變量,外部無法獲取,同樣,任何未顯示導出的變量,函數或類都是模塊私有的,若沒有用export對外暴露,是無法從模塊外部訪問的 例如:

function countResult(num1,num2){ return num1-num2; } // 沒有通過export關鍵字導出,在外部是無法訪問該模塊的變量或者函數的 

3. 導出類,類前面加上export關鍵字

export class People{ constructor(name,age){ this.name = name; this.age = age; } info(){ return `${this.name}${this.age}` } } 

對應在另一個模塊中通過import導入如下所示,模塊命名為importExample.js

/* * * @desc:從exportExample模塊中導入數據,通過import的方式 * @說明:由於我在node環境中測試,因為node暫且不支持Es6中的module語法,所以得先把es6代碼通過babel轉化成Es5代碼,方可在node環境中執行該腳本,from后面具體路徑引入的應該是通過Es6轉化為Es5的代碼 * */ import { name, weChatPublic,time,sum,People} from "../modelTest1/exportExampleEs5.js" var people = new People("小美",18); // 實例化perople對象 console.log(name); console.log(weChatPublic); console.log(time); console.log(sum(1,2)); console.log(people.info()); 

注意1:在上面的示例中,除了export關鍵字外,每一個聲明與腳本中的一模一樣,因為導出的函數和類聲明需要有一個名稱,所以代碼中的每一個函數或類也確實有這個名稱,除非用default關鍵字,否則不能用這個語法導出匿名函數或類

意2:因為在現今node版本環境中,目前還不直接支持export和import語法,也就是說在node環境中,直接寫Es6的模塊代碼,用node執行js腳本,會拋出錯誤,所以得先把Es6轉換成Es5版本的代碼,然后在node環境下運行該腳本才不會報錯,這種轉換方式可以通過babel進行轉化

安裝babel如下所示:命令行終端下通過npm全局安裝babel-cli

npm install --global babel-cli npm install --save babel-preset-es2015 

然后在當前目錄下新建配置文件.babelrc,注意存儲的位置不要帶有中文路徑,否則使用babel命令時會拋出錯誤

{ "presets":["es2015"] } 

在編寫好es6代碼后通過 babel Es6源腳本 -o Es5腳本 這里的-o或--out-file指的從Es6標准格式轉化生成的輸出Es5文件


 
image

讓我們對比看一下,其實在node中Es6中的export通過babel編譯后Es5中代碼是以exports方式進行導出的,而Es6中的import導入模塊通過babel編譯后是通過轉變為require的方式引入的:

如下對比所示:Es6中export導出模塊代碼

/* * * @authors 隨筆川跡 (itclanCode@163.com) * @date 2018-07-07 18:01:23 * @desc:導出數據 * */ // 導出數據 export var name = "隨筆川跡"; // 導出暴露name變量 export let weChatPublic = "itclanCoder"; // 暴露weChatPublic export const time = 2018; // 暴露time // 導出函數 export function sum(num1,num2){ return num1+num2; } /* * * 以上等價於 * function sum(num1,num2){ * return num1+num2; * } * export sum; * */ function multiply(num1,num2){ return num1+num2; } export multiply; // 導出類 export class People{ constructor(name,age){ this.name = name; this.age = age; } info(){ return `${this.name}${this.age}歲了`; } } 

通過babel編譯轉變為Es5代碼

"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function() { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function(Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); exports.sum = sum; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /* * * @authors 隨筆川跡 (itclanCode@163.com) * @date 2018-07-07 18:01:23 * @desc:導出數據 * */ // 導出數據 var name = exports.name = "隨筆川跡"; // 導出暴露name變量 var weChatPublic = exports.weChatPublic = "itclanCoder"; // 暴露weChatPublic var time = exports.time = 2018; // 暴露time var flag = true; // 導出函數 function sum(num1, num2) { return num1 + num2; } /* * * 以上等價於 * function sum(num1,num2){ * return num1+num2; * } * export sum; * */ // 導出類 var People = exports.People = function() { function People(name, age) { _classCallCheck(this, People); this.name = name; this.age = age; } _createClass(People, [{ key: "info", value: function info() { return "" + this.name + this.age + "\u5C81\u4E86"; } }]); return People; }(); 

而在另一個模塊中importExample.js中,這里是Es6中import導入模塊的代碼

/* * * @desc:從exportExample模塊中導入數據,通過import的方式 * */ import { name, weChatPublic,time,sum,People} from "../modelTest1/exportExampleEs5.js" var people = new People("小美",18); // 實例化perople對象 console.log(name); console.log(weChatPublic); console.log(time); console.log(sum(1,2)); console.log(people.info()); 

在node中通過babel編譯轉化為Es5代碼后,import相當於require的作用,但是他們兩者是不同的,前者是按需引入,而后者是一次性全部引入

"use strict"; var _exportExampleEs = require("../modelTest1/exportExampleEs5.js"); var people = new _exportExampleEs.People("小美", 18); // 實例化perople對象 /* * * @desc:從exportExample模塊中導入數據,通過import的方式 * */ console.log(_exportExampleEs.name); console.log(_exportExampleEs.weChatPublic); console.log(_exportExampleEs.time); console.log((0, _exportExampleEs.sum)(1, 2)); console.log(people.info()); 

Es6中模塊導入的基本語法·

如果想從一個文件(模塊)訪問另一個文件(模塊)的功能,則需要通過import關鍵字在另一個模塊中引入數據,import語句的兩個部分組成分別是:要導入的標識符和標識符應當從那個模塊導入,另外,導入的標識符的順序可以是任意位置,但是導入的標識符(也就是大括號里面的變量)與export暴露出的變量名應該是一致的 具體的寫法如下:

import {identifer1,indentifer2} from "./example.js" // import {標識符1,標識符2} from "本地模塊路徑" 

import后面的雙大括號表示從后面給定的模塊導入的綁定,關鍵字from表示從哪個模塊導入給定的綁定,該模塊由表示模塊路徑的字符串指定(被稱為模塊說明符),如果在瀏覽器中,使用路徑格式與<script>元素的相同,也就是說,必須把文件拓展名也加上

注意:在nodejs中,區分加前綴和不加前綴,不加路徑前綴的表示的是包,而加入路徑前綴的表示本地文件,例如:require("http")引入的是一個包;而require("./http.js")引入的是一個本地文件

注意:導入綁定的列表看起來與解構對象很相似,但兩者不是一樣的

當從模塊中導入一個綁定時,它就好像使用了const定義的一樣,也就是自動默認使用嚴格模式,你無法定義另一個同名變量(包括導入另一個同名綁定),也無法在import語句前使用標識符或改變綁定的值
1. 導入單個綁定
假設前面的實例在一個名為ExportExample.js的模塊當中,我們可以導入並以多種方式使用這個模塊中的綁定,可以只導入一個標識符:例如:

// 只導入一個 import {sum} from "./example.js" console.log(sum(1,2)); // 3 sum = 1; // 拋出一個錯誤,是不能對導入的綁定變量對象進行改寫操作的 

盡管ExportExample.js導出的函數不止一個,但這個示例導入的卻只有sum()函數,如果嘗試給sum賦新值,那么就會拋出一個錯誤,因為不能給導入的綁定重新賦值 為了兼容多個瀏覽器和Nodejs壞境,一定要在字符串之前包含/,./或../來表示要導入的文件
2. 導入多個綁定

如果想從示例模塊中導入多個綁定,與單個綁定相似,多個綁定值之間用逗號隔開即可

// 導入多個 import {sum,multiply,time} from "./exportExample.js" console.log(sum(1,2)); // 3 console.log(multiply(1,2)); // 3 console.log(time); // 2018 

在這段代碼中,從exportExample.js模塊導入3個綁定,sum,multiply和time之后使用它們,就像使用本地定義的一樣 等價於下面這個: 不管在import語句中把一個模塊寫了多少次,該模塊將只執行一次,導入模塊的代碼執行后,實例化過的模塊被保存在內存中,只要另一個import語句使用它就可以重復使用它

import {sum} from "./exportExample.js" import {multiply} from "./exportExample.js" import {time} from "./exportExample.js 

3. Es6中導入整個模塊

特殊情況下,可以導入整個模塊作為一個單一的對象,然后所有的導出都可以作為對象的屬性使用,例如

// 導入一整個模塊 import * as example from "./exportExample.js" console.log(example.sum(1,example.time)); consoole.log(example.multiply(1,2));// multiply與sum函數功能一樣 

在上面這段代碼中,從本地模塊的exportExample.js中導出的所有綁定被加載到一個被稱作為example的對象中,指定的導出sum()函數,multiply()函數和time之后作為example的屬性被訪問,這種導入格式被稱為命名空間導入,因為exportExample.js文件中不存在example對象,所以它被作為exportExample.js中所有導出成員的命名空間對象而被創建

Es6中模塊語法的限制

export和import的一個重要的限制是,他們必須在其他語句和函數之外使用,例如,下面的代碼會給出一個語法錯誤

if(flag){ export flag; // 語法錯誤 } 

下面以在微信小游戲中測試為證


 
image

export和import的一個重要的限制是,他們必須在其他語句和函數之外使用,例如,下面的代碼會給出一個語法錯誤

export語句不允許出現在if語句中,不能有條件導出或以任何方式動態導出,也就是說export命令規定的是對外的接口,必須與模塊內部的變量建立一一對應的關系,不能這樣寫: export 5;或者 var num = 5; export num;必須得加上大括號 {變量名}去暴露它 模塊語法存在的一個原因是要讓javascipt引擎靜態的確定哪些可以導出,因此,只能在模塊頂部使用export

同樣,不能在一條語句中使用import,只能在頂部使用它(這也是為什么很多框架在業務邏輯代碼之前,需要什么插件,都得提前引入),如下代碼所示,import語句也不能放在一條語句當中

function testImport(){ import flag from "./ExportExample.js" // 語法錯誤 } 

下面時在微信小游戲中測試可證

 
image

由於同樣的原因, 不能動態的導入或導出綁定,export和import關鍵字被設計成靜態的 以上這種通過import導入模塊與require的寫法的具體區別是:

 

import 導入的方式更加靈活隨意一些,要想用哪個變量,函數,模塊就導入哪一個,按需加載,現在想想在使用框架當中,使用某個UI庫里面的某單個組件,使用import導入單個組件而非全部一次性引入的原因了.

**而使用require是全部都引入了的,若想要更加效率的話,那么推崇import導入的方式 **

例1:全局完整引入,沒有大括號,從element-ui庫中引入Element,當然在vue中,還得Vue.use(插件名)全局注冊一下

import Element from 'element-ui'; Vue.use(Element); 

例2:從element-ui庫中導入兩個Button,Select組件

import { Button, Select } from 'element-ui Vue.use(Button); Vue.use(Select); 

Es6中如何給導入導出時標識符重命名

從一個模塊導入變量,函數或者類時,我們可能不希望使用他們的原始名稱,就是導入導出時模塊內的標識符(變量名,函數,或者類)可以不用一一對應,保持一致,可以在導出和導入過程中改變導出變量對象的名稱

使用方式: 使用as關鍵字來指定變量,函數,或者類在模塊外應該被稱為什么名稱 例如如下一函數

function sum(num1,num2){ return num1+num2; } export {sum as add} // as后面是重新指定的函數名 

如上代碼,函數sum是本地名稱,add是導出時使用的名稱,換句話說,當另一個模塊要導入這個函數時,必須使用add這個名稱

若在importExample.js一模塊中,則導入的變量對象應是add而不是sum,是由它導出時變量對象決定的

import  {add} from "./exportExample.js" 

如果模塊想使用不同的名稱來導入函數,也可以使用as關鍵字

import {add as sum} from "./exportExample.js" console.log(sum(1,2)); // 3 console.log(typeof add); // undefined 

如上代碼導入add函數時使用了一個導入名稱來重命名sum函數,注意這種寫法與前面導出export時的區別,使用import方式時,重新命名的標識符在前面,as后面是本地名稱,但是這種方式,即使導入時改變函數的本地名稱,即使模塊導入了add函數,在當前模塊中也沒有add()標識符,如上對add的類型檢測就是很好的驗證

Es6中導入綁定時的一個注意點,導入定義時的變量無法更改

在Es6中的import語句為變量,函數,類創建的目的是只讀綁定所要導入的對象,並不是像正常變量一樣簡單的引用原始綁定,標識符只有在被導出的模塊中可以修改(也就是只能在export模塊中修改),當導入綁定的模塊后,它是無法更改綁定的值的(在import中無法對已導入綁定的變量作修改),from前面的就是綁定的變量對象,例如:如下代碼所示

import {name,setName} from "./exportExample.js" // from前面雙大括號中的變量對象是不可以被修改的,想嘗試修改就會報錯 console.log(name); // 隨筆川跡,此時訪問name是全局變量 setName("好好先生"); console.log(name); // 好好先生,函數內的同名變量會覆蓋全局變量 name = "itclanCoder" // 拋出錯誤,此處的name並非導入時name 
 
image

當想嘗試更改導入時變量對象的名稱時,就會拋出錯誤


 
image

如上代碼:當調用setName("好好先生")時會回到導出setName()的模塊中去執行,並將name設置為好好先生,通過import導入的name標識符是export導出時的name標識符本地名稱

總結

本文主要從什么是模塊,Node中模塊的導出與導入,如何檢測node.js對Es6的支持情況 ,以及在Node中通過babel將es6代碼轉化為Es5代碼在Node中執行,模塊的導出(導出數據,函數和類)模塊的導入(單個導入,多個導入,導入整個)

模塊中在用export關鍵字導出所要暴露的對象和用import關鍵字導入暴露的對象中,導入的變量對象需要和導出的保持一致,當然也可以通過as關鍵字進行重命名,並且模塊導入的變量對象無法被改寫,如果要改寫,那么需要到export所暴露對象的模塊中進行改寫




原文鏈接:https://www.jianshu.com/p/29f89e8b9cb6


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM