ES6
經常使用但被忽略的方法 系列文章,整理做者認爲一些平常開發可能會用到的一些方法、使用技巧和一些應用場景,細節深刻請查看相關內容鏈接,歡迎補充交流。CommonJS
和AMD
模塊,都只能在運行時肯定這些東西。 ES6
能夠在編譯時就完成模塊加載,效率要比 CommonJS
模塊的加載方式高,這種加載稱爲「編譯時加載」或者靜態加載。JavaScript
的語法,好比引入宏(macro
)和類型檢驗(type system
)這些只能靠靜態分析實現的功能。UMD
模塊格式。API
就能用模塊格式提供,再也不必須作成全局變量或者navigator
對象的屬性。Math
對象),將來這些功能能夠經過模塊提供。ES6
的模塊自動採用嚴格模式,無論你有沒有在模塊頭部加上"use strict"
。with
語句0
表示八進制數,不然報錯delete prop
,會報錯,只能刪除屬性delete global[prop]
eval
不會在它的外層做用域引入變量eval
和arguments
不能被從新賦值arguments
不會自動反映函數參數的變化arguments.callee
和arguments.caller
this
指向全局對象fn.caller
和fn.arguments
獲取函數調用的堆棧protected
、static
和interface
)ES6
模塊之中,頂層的this
指向undefined
,即不該該在頂層代碼使用this
。export
命令用於規定模塊的對外接口。export
關鍵字輸出該變量。除了輸出變量,還能夠輸出函數或類(class
)。// index.js
export const name = 'detanx';
export const year = 1995;
export function multiply(x, y) {
return x * y;
};
// 寫法二
const name = 'detanx';
const year = 1995;
function multiply(x, y) {
return x * y;
};
export { name, year, multiply }
複製代碼
export
輸出的變量就是原本的名字,可是可使用as
關鍵字重命名。重命名後,能夠用不一樣的名字輸出屢次。function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
複製代碼
export
命令規定的是對外的接口,必須與模塊內部的變量創建一一對應關係。// 報錯
export 1;
var m = 1;
export m;
// 正確
export var m = 1;
var m = 1;
export {m};
var n = 1;
export {n as m};
複製代碼
export
命令能夠出如今模塊的任何位置,只要處於模塊頂層就能夠。export *
命令會忽略模塊的default
方法。// 總體輸出
export * from 'my_module';
複製代碼
export
命令定義了模塊的對外接口之後,其餘 JS
文件就能夠經過import
命令加載這個模塊。想爲輸入的變量從新取一個名字,import
命令要使用as
關鍵字,將輸入的變量重命名。import { name, year } from './index.js';
import { name as username } from './profile.js';
複製代碼
import
命令輸入的變量都是隻讀的,由於它的本質是輸入接口。 也就是說,不容許在加載模塊的腳本里面,改寫接口。若是a
是一個對象,改寫a
的屬性是容許的。import {a} from './xxx.js'
a = {}; // Syntax Error : 'a' is read-only;
a.foo = 'hello'; // 合法操做
複製代碼
import
後面的from
指定模塊文件的位置,能夠是相對路徑,也能夠是絕對路徑,.js
後綴能夠省略。若是隻是模塊名,不帶有路徑,那麼必須有配置文件(例如使用webpack
配置路徑),告訴 JavaScript
引擎該模塊的位置。import {myMethod} from 'util';
複製代碼
import
命令具備提高效果,會提高到整個模塊的頭部,首先執行。foo(); // 不會報錯
import { foo } from 'my_module';
複製代碼
import
是靜態執行,因此不能使用表達式和變量,這些只有在運行時才能獲得結果的語法結構。// 報錯
import { 'f' + 'oo' } from 'my_module';
// 報錯
let module = 'my_module';
import { foo } from module;
// 報錯
if (x === 1) {
import { foo } from 'module1';
} else {
import { foo } from 'module2';
}
複製代碼
import
語句,那麼只會執行一次,而不會執行屢次。import 'lodash';
import 'lodash'; // 只會執行一次
import { foo } from 'my_module';
import { bar } from 'my_module';
// 等同於
import { foo, bar } from 'my_module';
複製代碼
*
)指定一個對象,全部輸出值都加載在這個對象上面。import * as user from './index.js';
user.name; // 'detanx'
user.year; // 1995
複製代碼
export default
命令,爲模塊指定默認輸出。其餘模塊加載該模塊時,import
命令(import
命令後面,不使用大括號)能夠爲該匿名函數指定任意名字。// export-default.js
export default function () {
console.log('detanx');
}
// import-default.js
import customName from './export-default';
customName(); // 'detanx'
複製代碼
export default
時,對應的import
語句不須要使用大括號;使用export
,對應的import
語句須要使用大括號。 一個模塊只能有一個默認輸出,所以export default
命令只能使用一次。export default function crc32() { ...}
import crc32 from 'crc32';
export function crc32() { ... };
import { crc32 } from 'crc32';
複製代碼
import
語句能夠與export
語句寫在一塊兒。寫成一行之後,foo
和bar
實際上並無被導入當前模塊,只是至關於對外轉發了這兩個接口,致使當前模塊不能直接使用foo
和bar
。export { foo, bar } from 'my_module';
// 能夠簡單理解爲
import { foo, bar } from 'my_module';
export { foo, bar };
複製代碼
// 接口更名
export { foo as myFoo } from 'my_module';
// 總體輸出
export * from 'my_module';
複製代碼
export { default } from 'foo';
複製代碼
export { es6 as default } from './someModule';
// 等同於
import { es6 } from './someModule';
export default es6;
複製代碼
export { default as es6 } from './someModule';
ES2020 以前,有一種import語句,沒有對應的複合寫法。
import * as someIdentifier from "someModule";
複製代碼
ES2020
補上了這個寫法。export * as ns from "mod";
// 等同於
import * as ns from "mod";
export {ns};
複製代碼
constant
的文件,咱們須要什麼就加載什麼。// constants.js 模塊
export const A = 1;
export const B = 3;
export const C = 4;
// use.js
import {A, B} from './constants';
複製代碼
import
命令會被 JavaScript
引擎靜態分析,先於模塊內的其餘語句執行(import
命令叫作「鏈接」 binding
其實更合適)。因此咱們只能在最頂層去使用。ES2020
引入import()
函數,支持動態加載模塊。import()
返回一個 Promise
對象。const main = document.querySelector('main');
import(`./section-modules/${someVariable}.js`)
.then(module => {
module.loadPageInto(main);
})
.catch(err => {
main.textContent = err.message;
});
複製代碼
import()
函數能夠用在任何地方,不只僅是模塊,非模塊的腳本也可使用。它是運行時執行,也就是說,何時運行到這一句,就會加載指定的模塊。另外,import()
函數與所加載的模塊沒有靜態鏈接關係,這點也是與import語句不相同。import()
相似於 Node
的require
方法,區別主要是前者是異步加載,後者是同步加載。import()
加載模塊成功之後,這個模塊會做爲一個對象,看成then
方法的參數。所以,可使用對象解構賦值的語法,獲取輸出接口。import('./myModule.js')
.then(({export1, export2}) => {
// ...·
});
複製代碼
export1
和export2
都是myModule.js
的輸出接口,能夠解構得到。default
輸出接口,能夠用參數直接得到。import('./myModule.js')
.then(myModule => {
console.log(myModule.default);
});
複製代碼
import('./myModule.js')
.then(({default: theDefault}) => {
console.log(theDefault);
});
複製代碼
Promise.all([
import('./module1.js'),
import('./module2.js'),
import('./module3.js'),
])
.then(([module1, module2, module3]) => {
···
});
複製代碼
import()
也能夠用在 async
函數之中。async function main() {
const myModule = await import('./myModule.js');
const {export1, export2} = await import('./myModule.js');
const [module1, module2, module3] =
await Promise.all([
import('./module1.js'),
import('./module2.js'),
import('./module3.js'),
]);
}
main();
複製代碼
JavaScript
腳本,即渲染引擎遇到<script>
標籤就會停下來,等到執行完腳本,再繼續向下渲染。爲了解決<script>
標籤打開defer
或async
屬性,腳本就會異步加載。defer
與async
的區別是:defer
要等到整個頁面在內存中正常渲染結束(DOM
結構徹底生成,以及其餘腳本執行完成),纔會執行;async
一旦下載完,渲染引擎就會中斷渲染,執行這個腳本之後,再繼續渲染。一句話,defer
是「渲染完再執行」,async
是「下載完就執行」。另外,若是有多個defer
腳本,會按照它們在頁面出現的順序加載,而多個async
腳本是不能保證加載順序的。ES6
模塊,也使用<script>
標籤,可是要加入type="module"
屬性。等同於打開了<script>
標籤的defer
屬性。<script type="module" src="./foo.js"></script>
<!-- 等同於 -->
<script type="module" src="./foo.js" defer></script>
複製代碼
"use strict"
。import
命令加載其餘模塊(.js
後綴不可省略,須要提供絕對 URL
或相對 URL
),也可使用export
命令輸出對外接口。this
關鍵字返回undefined
,而不是指向window
。也就是說,在模塊頂層使用this
關鍵字,是無心義的。import utils from 'https://example.com/js/utils.js';
const x = 1;
console.log(x === window.x); //false
console.log(this === undefined); // true
複製代碼
this
等於undefined
這個語法點,能夠偵測當前代碼是否在 ES6
模塊之中。const isNotModuleScript = this !== undefined;
複製代碼
Node.js
加載 ES6
模塊以前,必須瞭解 ES6
模塊與 CommonJS
模塊徹底不一樣。CommonJS
模塊輸出的是一個值的拷貝,ES6
模塊輸出的是值的引用。CommonJS
模塊是運行時加載,ES6
模塊是編譯時輸出接口。(由於 CommonJS
加載的是一個對象(即module.exports
屬性),該對象只有在腳本運行完纔會生成。而 ES6 模塊不是對象,它的對外接口只是一種靜態定義,在代碼靜態解析階段就會生成。)CommonJS
模塊輸出的是值的拷貝,也就是說,一旦輸出一個值,模塊內部的變化就影響不到這個值。除非寫成一個函數,才能獲得內部變更後的值。// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
// main.js
var mod = require('./lib');
console.log(mod.counter); // 3
mod.incCounter();
console.log(mod.counter); // 3
// 寫成函數
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
get counter() {
return counter
},
incCounter: incCounter,
};
$ node main.js
3
4
複製代碼
ES6
模塊是動態引用,而且不會緩存值,模塊裏面的變量綁定其所在的模塊。// lib.js
export let counter = 3;
export function incCounter() {
counter++;
}
// main.js
import { counter, incCounter } from './lib';
console.log(counter); // 3
incCounter();
console.log(counter); // 4
複製代碼
ES6
輸入的模塊變量,只是一個「符號鏈接」,因此這個變量是隻讀的,對它進行從新賦值會報錯。// lib.js
export let obj = {};
// main.js
import { obj } from './lib';
obj.prop = 123; // OK
obj = {}; // TypeError
複製代碼
export
經過接口,輸出的是同一個值。不一樣的腳本加載這個接口,獲得的都是一樣的實例。// mod.js
function C() {
this.sum = 0;
this.add = function () {
this.sum += 1;
};
this.show = function () {
console.log(this.sum);
};
}
export let c = new C();
複製代碼
mod.js
,輸出的是一個C
的實例。不一樣的腳本加載這個模塊,獲得的都是同一個實例。// x.js
import {c} from './mod';
c.add();
// y.js
import {c} from './mod';
c.show();
// main.js
import './x';
import './y';
複製代碼
main.js
,輸出的是 1
。$ babel-node main.js
1
複製代碼
x.js
和y.js
加載的都是C
的同一個實例。Node.js
要求 ES6
模塊採用.mjs
後綴文件名。Node.js
遇到.mjs
文件,就認爲它是 ES6
模塊,默認啓用嚴格模式,沒必要在每一個模塊文件頂部指定"use strict"
。 若是不但願將後綴名改爲.mjs
,能夠在項目的package.json
文件中,指定type
字段爲module
。{
"type": "module"
}
複製代碼
這時還要使用 CommonJS
模塊,那麼須要將 CommonJS
腳本的後綴名都改爲.cjs
。若是沒有type
字段,或者type
字段爲commonjs
,則.js
腳本會被解釋成 CommonJS
模塊。node
總結:.mjs
文件老是以 ES6
模塊加載,.cjs
文件老是以 CommonJS
模塊加載,.js
文件的加載取決於package.json
裏面type
字段的設置。webpack
注意,ES6
模塊與 CommonJS
模塊儘可能不要混用。require
命令不能加載.mjs
文件,會報錯,只有import
命令才能夠加載.mjs
文件。反過來,.mjs
文件裏面也不能使用require
命令,必須使用import。
es6
Node.js 加載 主要是介紹ES6
模塊和 CommonJS
相互之間的支持,有興趣的能夠本身去看看。web
circular dependency
)指的是,a
腳本的執行依賴b
腳本,而b
腳本的執行又依賴a
腳本。「循環加載」表示存在強耦合,若是處理很差,還可能致使遞歸加載,使得程序沒法執行,所以應該避免出現,但很難避免尤爲是特別複雜的項目。