es6常見知識點

官方文檔:[https://es6.ruanyifeng.com/](https://es6.ruanyifeng.com/)

一、Class

1、Class

Class只是一個語法糖,其功能用es5也能實現,但是比es5更符合類的期待

定義:

constructor代表構造方法,而this指向new 生成的實例

定義類方法時,可以不使用function

注意:類的內部所有定義的方法,都是不可枚舉的(non-enumerable)。

//定義類
class Point {constructor(x, y) {this.x = x;this.y = y;}toString() {return '(' + this.x + ', ' + this.y + ')';}
}

使用

new Point(x,y)

2、constructor

類的默認方法,new生成對象實例的時候執行的就是這個方法

一個類必須有constructor方法

constructor默認返回實例對象

3、Class不存在變量提升

```plain new Foo(); // ReferenceError class Foo {} ```

4、Class的繼承

extends關鍵字
class ColorPoint extends Point {}

注意:

1.子類必須調用super,子類本身沒有this,super是父類的構造函數,調用super,子類才有this

這是因為子類實例的構建,是基于對父類實例加工,只有super方法才能返回父類實例。

5、類的prototype屬性和__proto__屬性

1.子類的__proto__指向父類

2.子類的prototype的__proto__指向父類的.prototype

class A {
}
class B extends A {
}
B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true

super關鍵字

1.調用super方法時,super代表父類的constructor方法

2.作為屬性super調用時,super代表父類本身

ES6改變了Object構造函數的行為,一旦發現Object方法不是通過new Object()這種形式調用,ES6規定Object構造函數會忽略參數。

6、getter和setter

7、Generate方法

直接在方法屬性前加*

8、靜態方法static

一個方法前加static,則該方法不會被繼承,而是通過類來直接調用

父類的靜態方法,可以被子類繼承。

靜態方法也可以從super上調用,因為super指向父類本身

注意:Class內部只有靜態方法,沒有靜態屬性。

9、new.target

(在構造函數中)返回new命令作用于的那個構造函數。

二、Module

1、嚴格模式

ES6 的模塊自動采用嚴格模式,不管你有沒有在模塊頭部加上`"use strict";` 。

嚴格模式主要有以下限制。

  • 變量必須聲明后再使用
  • 禁止this指向全局對象
  • 不能使用fn.callerfn.arguments獲取函數調用的堆棧
  • 增加了保留字(比如protectedstaticinterface

其中,尤其需要注意this的限制。ES6 模塊之中,頂層的this指向undefined,即不應該在頂層代碼使用this

2、export命令

模塊功能主要由兩個命令構成:`export` 和`import` 。`export` 命令用于規定模塊的對外接口,`import` 命令用于輸入其他模塊提供的功能。

一個模塊就是一個獨立的文件。該文件內部的所有變量,外部無法獲取。如果你希望外部能夠讀取模塊內部的某個變量,就必須使用export關鍵字輸出該變量。下面是一個 JS 文件,里面使用export命令輸出變量。

// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;

上面代碼是profile.js文件,保存了用戶信息。ES6 將其視為一個模塊,里面用export命令對外部輸出了三個變量。

export的寫法,除了像上面這樣,還有另外一種。

// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export { firstName, lastName, year };

上面代碼在export命令后面,使用大括號指定所要輸出的一組變量。它與前一種寫法是等價的,但是應該優先考慮使用這種寫法。因為這樣就可以在腳本尾部,一眼看清楚輸出了哪些變量。

export命令除了輸出變量,還可以輸出函數或類(class)。

export function multiply(x, y) {return x * y;
};

上面代碼對外輸出一個函數multiply

通常情況下,export輸出的變量就是本來的名字,但是可以使用as關鍵字重命名。

function v1() { ... }
function v2() { ... }
export {v1 as streamV1,v2 as streamV2,v2 as streamLatestVersion
};

上面代碼使用as關鍵字,重命名了函數v1v2的對外接口。重命名后,v2可以用不同的名字輸出兩次。

3、import命令

使用`export`命令定義了模塊的對外接口以后,其他 JS 文件就可以通過`import`命令加載這個模塊。
// main.js
import { firstName, lastName, year } from './profile.js';
function setName(element) {element.textContent = firstName + ' ' + lastName;
}

import命令輸入的變量都是只讀的,因為它的本質是輸入接口。也就是說,不允許在加載模塊的腳本里面,改寫接口。

import {a} from './xxx.js'
a = {}; // Syntax Error : 'a' is read-only;

上面代碼中,腳本加載了變量a,對其重新賦值就會報錯,因為a是一個只讀的接口。但是,如果a是一個對象,改寫a的屬性是允許的。

import {a} from './xxx.js'
a.foo = 'hello'; // 合法操作

上面代碼中,a的屬性可以成功改寫,并且其他模塊也可以讀到改寫后的值。不過,這種寫法很難查錯,建議凡是輸入的變量,都當作完全只讀,不要輕易改變它的屬性。

import后面的from指定模塊文件的位置,可以是相對路徑,也可以是絕對路徑。如果不帶有路徑,只是一個模塊名,那么必須有配置文件,告訴 JavaScript 引擎該模塊的位置。

import { myMethod } from 'util';

上面代碼中,util是模塊文件名,由于不帶有路徑,必須通過配置,告訴引擎怎么取到這個模塊。

注意,import命令具有提升效果,會提升到整個模塊的頭部,首先執行。

foo();
import { foo } from 'my_module';

上面的代碼不會報錯,因為import的執行早于foo的調用。這種行為的本質是,import命令是編譯階段執行的,在代碼運行之前。

import()類似于 Node 的require方法,區別主要是前者是異步加載,后者是同步加載

3、模塊的整體加載

除了指定加載某個輸出值,還可以使用整體加載,即用星號(`*`)指定一個對象,所有輸出值都加載在這個對象上面。

下面是一個circle.js文件,它輸出兩個方法areacircumference

// circle.js
export function area(radius) {return Math.PI * radius * radius;
}
export function circumference(radius) {return 2 * Math.PI * radius;
}

現在,加載這個模塊。

// main.js
import { area, circumference } from './circle';
console.log('圓面積:' + area(4));
console.log('圓周長:' + circumference(14));

上面寫法是逐一指定要加載的方法,整體加載的寫法如下。

import * as circle from './circle';
console.log('圓面積:' + circle.area(4));
console.log('圓周長:' + circle.circumference(14));

4、export default命令

從前面的例子可以看出,使用`import`命令的時候,用戶需要知道所要加載的變量名或函數名,否則無法加載。

為了給用戶提供方便,讓他們不用閱讀文檔就能加載模塊,就要用到export default命令,為模塊指定默認輸出。

// export-default.js
export default function () {console.log('foo');
}

上面代碼是一個模塊文件export-default.js,它的默認輸出是一個函數。

其他模塊加載該模塊時,import命令可以為該匿名函數指定任意名字。

// import-default.js
import customName from './export-default';
customName(); // 'foo'

上面代碼的import命令,可以用任意名稱指向export-default.js輸出的方法,這時就不需要知道原模塊輸出的函數名。需要注意的是,這時import命令后面,不使用大括號。

5、export、import、export default幾種指令的用法總結

一,import(模塊、文件)引入方式
1 引入第三方插件
```plain import echarts from 'echarts' ```
2.導入 css文件
```plain import 'iview/dist/styles/iview.css'; ```

如果是在.vue文件中那么在外面套個style

1. <style>
2. @import './test.css'; 
3. </style>
3.導入組件
```plain 1. import name1 from './name1' 2. import name2 from './name2' 3. components:{ 4. name1, 5. name2, 6. }, ```
4.import '@…'的語句
@ 等價于 /src 這個目錄,避免寫麻煩又易錯的相對路徑
5.引入工具類
```plain 1. 第一種是引入單個方法 2. 3. import {axiosfetch} from './util'; 4. 5. 下面是寫法,需要export導出 6. export function axiosfetch(options) { 7. 8. } ```
1. 第二種  導入成組的方法
2. 
3. import * as tools from './libs/tools'
4. 
5. 其中tools.js中有多個export方法,把tools里所有export的方法導入
6. 
7. vue中怎么用呢?
8. Vue.prototype.$tools = tools
9. 直接用 this.$tools.method調用就可以了

說到這 export 和 export default 又有什么區別呢?

下面看下區別

1.  先是 export
2. import {axiosfetch} from './util';  //需要加花括號  可以一次導入多個也可以一次導入一個,但都要加括號
3. 如果是兩個方法
4. import {axiosfetch,post} from './util'; 
5. 再是 export default
6. import axiosfetch from './util';  //不需要加花括號  只能一個一個導入

二,export,import和export default的關系
export 與import是es6中新增模塊功能最主要的兩個命令。

1.export與export default均可用于導出常量、函數、文件、模塊等

2.在一個文件或模塊中,export、import可以有多個,export default僅有一個

3.通過export方式導出,在導入時要加{ },export default則不需要{ }

一、import引入文件路徑
` import` 引入一個依賴包,不需要相對路徑。如:**import? app from ‘app’**;

<font style="color:#C7254E;">import</font> 引入一個自己寫的js文件,是需要相對路徑的。如:import app from ‘./app.js’;

二、import引入文件變量名
1 、使用` export` 拋出的變量需要用{}進行` import`
1. //a.js
2. export const str = "blablabla~";
3. export function log(sth) {
4. 	  return sth;
5. 	}
6. 
7. 對應的導入方式:
8. 
9. //b.js
10. import { str, log as _log } from 'a'; //也可以分開寫兩次,導入的時候帶花括號。還可以用as重命名

2、使用<font style="color:#C7254E;">export default</font>拋出的變量,只需要自己起一個名字就行:

1. //a.js :
2. var obj = { name: ‘example’ }; 
3. export default obj; 
4. 
5. //b.js: 
6. import newNname from ‘./a.js’;   //newNname 是自己隨便取的名字,這里可以隨便命名
7. console.log(newNname .name);       // example;

總結

其中export和export default最大的區別就是export不限變量數 可以一直寫,而export default? 只輸出一次 而且 export出的變量想要使用必須使用{}來盛放,而export default 不需要 只要import任意一個名字來接收對象即可。

三,部分導入和部分導出,全部導入和全部導出
一、部分導出和部分導入
部分導出和部分導入的優勢,當資源比較大時建使用部分導出,這樣一來使用者可以使用部分導入來減少資源體積,比如element-ui官方的就推薦使用部分導入來減少項目體積,因為element-ui是一個十分龐大的框架,如果我們只用到其中的一部分組件, 那么只將用到的組件導入就可以了。
1. //部分導出
2. //A.js
3. export function helloWorld(){
4.  conselo.log("Hello World");
5. }
6. export function test(){
7.  conselo.log("this's test function");
8. }
9. 
10. //部分導入
11. //B.js
12. import {helloWorld} from "./A.js" //只導入A.js中的helloWorld方法
13. helloWorld(); //執行utils.js中的helloWorld方法

如果我們需要A.js中的全部資源,則可以全部導入,如下:

1. import * as _A from "./A.js" //導入全部的資源,_A為別名,在調用時使用
2. _A.helloWorld(); //執行A.js中的helloWorld方法
3. _A.test(); //執行A.js中的test方法
二、全部導出和全部導入
如果使用全部導出,那么使用者在導入時則必須全部導入,推薦在寫方法庫時使用部分導出,從而將全部導入或者部分導入的權力留給使用者。

需要注意的是:一個js文件中可以有多個export,但只能有一個export default

1. //全部導出  A.js
2. var helloWorld=function(){
3.  conselo.log("Hello World");
4. }
5. var test=function(){
6.  conselo.log("this's test function");
7. }
8. export default{
9.  helloWorld,
10.  test
11. }
12. 
13. //全部導入  B.js
14. import A from "./A.js"
15. A.helloWorld();
16. A.test();

6、Module 的加載實現

瀏覽器加載
傳統方法
HTML 網頁中,瀏覽器通過`
<!-- 頁面內嵌的腳本 -->
<script type="application/javascript">// module code
</script>
<!-- 外部腳本 -->
<script type="application/javascript" src="path/to/myModule.js">
</script>

默認情況下,瀏覽器是同步加載 JavaScript 腳本,即渲染引擎遇到<script>標簽就會停下來,等到執行完腳本,再繼續向下渲染。如果是外部腳本,還必須加入腳本下載的時間。如果腳本體積很大,下載和執行的時間就會很長,因此造成瀏覽器堵塞,用戶會感覺到瀏覽器“卡死”了,沒有任何響應。這顯然是很不好的體驗,所以瀏覽器允許腳本異步加載,下面就是兩種異步加載的語法。

<script src="path/to/myModule.js" defer></script>
<script src="path/to/myModule.js" async></script>

上面代碼中,<script>標簽打開deferasync屬性,腳本就會異步加載。渲染引擎遇到這一行命令,就會開始下載外部腳本,但不會等它下載和執行,而是直接執行后面的命令。

deferasync的區別是:

1、defer要等到整個頁面在內存中正常渲染結束(DOM 結構完全生成,以及其他腳本執行完成),才會執行;async一旦下載完,渲染引擎就會中斷渲染,執行這個腳本以后,再繼續渲染。一句話,<font style="color:#F5222D;">defer</font>是“渲染完再執行”,<font style="color:#F5222D;">async</font>是“下載完就執行”。

2、如果有多個defer腳本,會按照它們在頁面出現的順序加載,而多個async腳本是不能保證加載順序的。

加載規則
瀏覽器加載 ES6 模塊,也使用`
<script type="module" src="./foo.js"></script>

上面代碼在網頁中插入一個模塊foo.js,由于type屬性設為module,所以瀏覽器知道這是一個 ES6 模塊。

瀏覽器對于帶有type="module"<script>,都是異步加載,不會造成堵塞瀏覽器,即等到整個頁面渲染完,再執行模塊腳本,等同于打開了<script>標簽的defer屬性。

<script type="module" src="./foo.js"></script>
<!-- 等同于 -->
<script type="module" src="./foo.js" defer></script>

如果網頁有多個<script type="module">,它們會按照在頁面出現的順序依次執行。

<script>標簽的async屬性也可以打開,這時只要加載完成,渲染引擎就會中斷渲染立即執行。執行完成后,再恢復渲染。

<script type="module" src="./foo.js" async></script>

一旦使用了async屬性,<script type="module">就不會按照在頁面出現的順序執行,而是只要該模塊加載完成,就執行該模塊。

ES6 模塊與 CommonJS 模塊的差異
討論 Node.js 加載 ES6 模塊之前,必須了解 ES6 模塊與 CommonJS 模塊完全不同。

它們有三個重大差異。

  • CommonJS 模塊輸出的是一個值的拷貝,ES6 模塊輸出的是值的引用。
  • CommonJS 模塊是運行時加載,ES6 模塊是編譯時輸出接口。
  • CommonJS 模塊的require()是同步加載模塊,ES6 模塊的import命令是異步加載,有一個獨立的模塊依賴的解析階段。

第二個差異是因為 CommonJS 加載的是一個對象(即<font style="color:#F5222D;">module.exports</font>屬性),該對象只有在腳本運行完才會生成。而 ES6 模塊不是對象,它的對外接口只是一種靜態定義,在代碼靜態解析階段就會生成。

下面重點解釋第一個差異。

CommonJS 模塊輸出的是值的拷貝,也就是說,一旦輸出一個值,模塊內部的變化就影響不到這個值。請看下面這個模塊文件lib.js的例子。

// lib.js
var counter = 3;
function incCounter() {counter++;
}
module.exports = {counter: counter,incCounter: incCounter,
};

上面代碼輸出內部變量counter和改寫這個變量的內部方法incCounter。然后,在main.js里面加載這個模塊。

// main.js
var mod = require('./lib');
console.log(mod.counter);  // 3
mod.incCounter();
console.log(mod.counter); // 3

上面代碼說明,lib.js模塊加載以后,它的內部變化就影響不到輸出的mod.counter了。這是因為mod.counter是一個原始類型的值,會被緩存。除非寫成一個函數,才能得到內部變動后的值。

// lib.js
var counter = 3;
function incCounter() {counter++;
}
module.exports = {get counter() {return counter},incCounter: incCounter,
};

上面代碼中,輸出的counter屬性實際上是一個取值器函數。現在再執行main.js,就可以正確讀取內部變量counter的變動了。

$ node main.js
3
4

ES6 模塊的運行機制與 CommonJS 不一樣。JS 引擎對腳本靜態分析的時候,遇到模塊加載命令import,就會生成一個只讀引用。等到腳本真正執行時,再根據這個只讀引用,到被加載的那個模塊里面去取值。換句話說,ES6 的import有點像 Unix 系統的“符號連接”,原始值變了,<font style="color:#F5222D;">import</font>加載的值也會跟著變。因此,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 模塊輸入的變量counter是活的,完全反應其所在模塊lib.js內部的變化。

由于 ES6 輸入的模塊變量,只是一個“符號連接”,所以這個變量是只讀的,對它進行重新賦值會報錯。

// lib.js
export let obj = {};
// main.js
import { obj } from './lib';
obj.prop = 123; // OK
obj = {}; // TypeError

上面代碼中,main.jslib.js輸入變量obj,可以對<font style="color:#F5222D;">obj</font>添加屬性,但是重新賦值就會報錯。因為變量<font style="color:#F5222D;">obj</font>指向的地址是只讀的,不能重新賦值,這就好比main.js創造了一個名為objconst變量。

最后,<font style="color:#F5222D;">export</font>通過接口,輸出的是同一個值。不同的腳本加載這個接口,得到的都是同樣的實例。

// 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.jsy.js加載的都是C的同一個實例。

Node.js 的模塊加載方法
概述
JavaScript 現在有兩種模塊。一種是 ES6模塊,一種是 CommonJS 模塊。

CommonJS 模塊是 Node.js 專用的,與 ES6 模塊不兼容。語法上面,兩者最明顯的差異是,CommonJS 模塊使用require()module.exports,ES6 模塊使用importexport

它們采用不同的加載方案。從 Node.js v13.2 版本開始,Node.js 已經默認打開了 ES6 模塊支持。

Node.js 要求 ES6 模塊采用.mjs后綴文件名。也就是說,只要腳本文件里面使用import或者export命令,那么就必須采用.mjs后綴名。Node.js 遇到.mjs文件,就認為它是 ES6 模塊,默認啟用嚴格模式,不必在每個模塊文件頂部指定"use strict"

如果不希望將后綴名改成.mjs,可以在項目的package.json文件中,指定type字段為module

{"type": "module"
}

一旦設置了以后,該目錄里面的 JS 腳本,就被解釋用 ES6 模塊。

# 解釋成 ES6 模塊
$ node my-app.js

如果這時還要使用 CommonJS 模塊,那么需要將 CommonJS 腳本的后綴名都改成.cjs。如果沒有type字段,或者type字段為commonjs,則.js腳本會被解釋成 CommonJS 模塊。

總結為一句話:.mjs文件總是以 ES6 模塊加載,.cjs文件總是以 CommonJS 模塊加載,.js文件的加載取決于package.json里面type字段的設置。

注意,ES6 模塊與 CommonJS 模塊盡量不要混用。require命令不能加載.mjs文件,會報錯,只有import命令才可以加載.mjs文件。反過來,.mjs文件里面也不能使用require命令,必須使用import

package.json 的 main 字段
`package.json`文件有兩個字段可以指定模塊的入口文件:`main`和`exports`。比較簡單的模塊,可以只使用`main`字段,指定模塊加載的入口文件。
// ./node_modules/es-module-package/package.json
{"type": "module","main": "./src/index.js"
}

上面代碼指定項目的入口腳本為./src/index.js,它的格式為 ES6 模塊。如果沒有type字段,index.js就會被解釋為 CommonJS 模塊。

然后,import命令就可以加載這個模塊。

// ./my-app.mjs
import { something } from 'es-module-package';
// 實際加載的是 ./node_modules/es-module-package/src/index.js

上面代碼中,運行該腳本以后,Node.js 就會到./node_modules目錄下面,尋找es-module-package模塊,然后根據該模塊package.jsonmain字段去執行入口文件。

這時,如果用 CommonJS 模塊的require()命令去加載es-module-package模塊會報錯,因為 CommonJS 模塊不能處理export命令。

package.json 的 exports 字段
`exports`字段的優先級高于`main`字段。它有多種用法。

(1)子目錄別名

package.json文件的exports字段可以指定腳本或子目錄的別名。

// ./node_modules/es-module-package/package.json
{"exports": {"./submodule": "./src/submodule.js"}
}

上面的代碼指定src/submodule.js別名為submodule,然后就可以從別名加載這個文件。

import submodule from 'es-module-package/submodule';
// 加載 ./node_modules/es-module-package/src/submodule.js

下面是子目錄別名的例子。

// ./node_modules/es-module-package/package.json
{"exports": {"./features/": "./src/features/"}
}
import feature from 'es-module-package/features/x.js';
// 加載 ./node_modules/es-module-package/src/features/x.js

如果沒有指定別名,就不能用“模塊+腳本名”這種形式加載腳本。

// 報錯
import submodule from 'es-module-package/private-module.js';
// 不報錯
import submodule from './node_modules/es-module-package/private-module.js';

(2)main 的別名

exports字段的別名如果是.,就代表模塊的主入口,優先級高于main字段,并且可以直接簡寫成exports字段的值。

{"exports": {".": "./main.js"}
}
// 等同于
{"exports": "./main.js"
}

由于exports字段只有支持 ES6 的 Node.js 才認識,所以可以用來兼容舊版本的 Node.js。

{"main": "./main-legacy.cjs","exports": {".": "./main-modern.cjs"}
}

上面代碼中,老版本的 Node.js (不支持 ES6 模塊)的入口文件是main-legacy.cjs,新版本的 Node.js 的入口文件是main-modern.cjs

(3)條件加載

利用.這個別名,可以為 ES6 模塊和 CommonJS 指定不同的入口。目前,這個功能需要在 Node.js 運行的時候,打開--experimental-conditional-exports標志。

{"type": "module","exports": {".": {"require": "./main.cjs","default": "./main.js"}}
}

上面代碼中,別名.require條件指定require()命令的入口文件(即 CommonJS 的入口),default條件指定其他情況的入口(即 ES6 的入口)。

上面的寫法可以簡寫如下。

{"exports": {"require": "./main.cjs","default": "./main.js"}
}

注意,如果同時還有其他別名,就不能采用簡寫,否則或報錯。

{// 報錯"exports": {"./feature": "./lib/feature.js","require": "./main.cjs","default": "./main.js"}
}
CommonJS 模塊加載 ES6 模塊
CommonJS 的`require()`命令不能加載 ES6 模塊,會報錯,只能使用`import()`這個方法加載。
(async () => {await import('./my-app.mjs');
})();

上面代碼可以在 CommonJS 模塊中運行。

require()不支持 ES6 模塊的一個原因是,它是同步加載,而 ES6 模塊內部可以使用頂層await命令,導致無法被同步加載。

ES6 模塊加載 CommonJS 模塊
ES6 模塊的`import`命令可以加載 CommonJS 模塊,但是只能整體加載,不能只加載單一的輸出項。

CommonJS模塊輸出的是一個值的拷貝,而ES6模塊輸出的是值的引用。

CommonJS一旦輸出一個值,模塊內部的變化就影響不到這個值。

ES6模塊原始值變了,import輸入的值也會跟著變。

// 正確
import packageMain from 'commonjs-package';
// 報錯
import { method } from 'commonjs-package';

這是因為 ES6 模塊需要支持靜態代碼分析,而 CommonJS 模塊的輸出接口是module.exports,是一個對象,無法被靜態分析,所以只能整體加載。

加載單一的輸出項,可以寫成下面這樣。

import packageMain from 'commonjs-package';
const { method } = packageMain;

還有一種變通的加載方法,就是使用 Node.js 內置的module.createRequire()方法。

// cjs.cjs
module.exports = 'cjs';
// esm.mjs
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const cjs = require('./cjs.cjs');
cjs === 'cjs'; // true

上面代碼中,ES6 模塊通過module.createRequire()方法可以加載 CommonJS 模塊。但是,這種寫法等于將 ES6 和 CommonJS 混在一起了,所以不建議使用。

同時支持兩種格式的模塊
一個模塊同時要支持 CommonJS 和 ES6 兩種格式,也很容易。

如果原始模塊是 ES6 格式,那么需要給出一個整體輸出接口,比如export default obj,使得 CommonJS 可以用import()進行加載。

如果原始模塊是 CommonJS 格式,那么可以加一個包裝層。

import cjsModule from '../index.js';
export const foo = cjsModule.foo;

上面代碼先整體輸入 CommonJS 模塊,然后再根據需要輸出具名接口。

你可以把這個文件的后綴名改為.mjs,或者將它放在一個子目錄,再在這個子目錄里面放一個單獨的package.json文件,指明{ type: "module" }

另一種做法是在package.json文件的exports字段,指明兩種格式模塊各自的加載入口。

"exports":{"require": "./index.js","import": "./esm/wrapper.js"
}

上面代碼指定require()import,加載該模塊會自動切換到不一樣的入口文件。

Node.js 的內置模塊
Node.js 的內置模塊可以整體加載,也可以加載指定的輸出項。
// 整體加載
import EventEmitter from 'events';
const e = new EventEmitter();
// 加載指定的輸出項
import { readFile } from 'fs';
readFile('./foo.txt', (err, source) => {if (err) {console.error(err);} else {console.log(source);}
});
加載路徑
ES6 模塊的加載路徑必須給出腳本的完整路徑,不能省略腳本的后綴名。`import`命令和`package.json`文件的`main`字段如果省略腳本的后綴名,會報錯。
// ES6 模塊中將報錯
import { something } from './index';

為了與瀏覽器的import加載規則相同,Node.js 的.mjs文件支持 URL 路徑。

import './foo.mjs?query=1'; // 加載 ./foo 傳入參數 ?query=1

上面代碼中,腳本路徑帶有參數?query=1,Node 會按 URL 規則解讀。同一個腳本只要參數不同,就會被加載多次,并且保存成不同的緩存。由于這個原因,只要文件名中含有:%#?等特殊字符,最好對這些字符進行轉義。

目前,Node.js 的import命令只支持加載本地模塊(file:協議)和data:協議,不支持加載遠程模塊。另外,腳本路徑只支持相對路徑,不支持絕對路徑(即以///開頭的路徑)。

內部變量
ES6 模塊應該是通用的,同一個模塊不用修改,就可以用在瀏覽器環境和服務器環境。為了達到這個目標,Node.js 規定 ES6 模塊之中不能使用 CommonJS 模塊的特有的一些內部變量。

首先,就是this關鍵字。ES6 模塊之中,頂層的this指向undefined;CommonJS 模塊的頂層this指向當前模塊,這是兩者的一個重大差異。

其次,以下這些頂層變量在 ES6 模塊之中都是不存在的。

  • arguments
  • require
  • module
  • exports
  • __filename
  • __dirname
循環加載
“循環加載”(circular dependency)指的是,`a`腳本的執行依賴`b`腳本,而`b`腳本的執行又依賴`a`腳本。
// a.js
var b = require('b');
// b.js
var a = require('a');

通常,“循環加載”表示存在強耦合,如果處理不好,還可能導致遞歸加載,使得程序無法執行,因此應該避免出現。

但是實際上,這是很難避免的,尤其是依賴關系復雜的大項目,很容易出現a依賴bb依賴cc又依賴a這樣的情況。這意味著,模塊加載機制必須考慮“循環加載”的情況。

對于 JavaScript 語言來說,目前最常見的兩種模塊格式 CommonJS 和 ES6,處理“循環加載”的方法是不一樣的,返回的結果也不一樣。

CommonJS 模塊的加載原理
介紹 ES6 如何處理“循環加載”之前,先介紹目前最流行的 CommonJS 模塊格式的加載原理。

CommonJS 的一個模塊,就是一個腳本文件。require命令第一次加載該腳本,就會執行整個腳本,然后在內存生成一個對象。

{id: '...',exports: { ... },loaded: true,...
}

上面代碼就是 Node 內部加載模塊后生成的一個對象。該對象的id屬性是模塊名,exports屬性是模塊輸出的各個接口,loaded屬性是一個布爾值,表示該模塊的腳本是否執行完畢。其他還有很多屬性,這里都省略了。

以后需要用到這個模塊的時候,就會到exports屬性上面取值。即使再次執行require命令,也不會再次執行該模塊,而是到緩存之中取值。也就是說,CommonJS 模塊無論加載多少次,都只會在第一次加載時運行一次,以后再加載,就返回第一次運行的結果,除非手動清除系統緩存。

CommonJS 模塊的循環加載
CommonJS 模塊的重要特性是加載時執行,即腳本代碼在`require`的時候,就會全部執行。一旦出現某個模塊被"循環加載",就只輸出已經執行的部分,還未執行的部分不會輸出。

讓我們來看,Node 官方文檔里面的例子。腳本文件a.js代碼如下。

exports.done = false;
var b = require('./b.js');
console.log('在 a.js 之中,b.done = %j', b.done);
exports.done = true;
console.log('a.js 執行完畢');

上面代碼之中,a.js腳本先輸出一個done變量,然后加載另一個腳本文件b.js。注意,此時a.js代碼就停在這里,等待b.js執行完畢,再往下執行。

再看b.js的代碼。

exports.done = false;
var a = require('./a.js');
console.log('在 b.js 之中,a.done = %j', a.done);
exports.done = true;
console.log('b.js 執行完畢');

上面代碼之中,b.js執行到第二行,就會去加載a.js,這時,就發生了“循環加載”。系統會去a.js模塊對應對象的exports屬性取值,可是因為a.js還沒有執行完,從exports屬性只能取回已經執行的部分,而不是最后的值。

a.js已經執行的部分,只有一行。

exports.done = false;

因此,對于b.js來說,它從a.js只輸入一個變量done,值為false

然后,b.js接著往下執行,等到全部執行完畢,再把執行權交還給a.js。于是,a.js接著往下執行,直到執行完畢。我們寫一個腳本main.js,驗證這個過程。

var a = require('./a.js');
var b = require('./b.js');
console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done);

執行main.js,運行結果如下。

$ node main.js
在 b.js 之中,a.done = false
b.js 執行完畢
在 a.js 之中,b.done = true
a.js 執行完畢
在 main.js 之中, a.done=true, b.done=true

上面的代碼證明了兩件事。一是,在b.js之中,a.js沒有執行完畢,只執行了第一行。二是,main.js執行到第二行時,不會再次執行b.js,而是輸出緩存的b.js的執行結果,即它的第四行。

exports.done = true;

總之,CommonJS 輸入的是被輸出值的拷貝,不是引用。

另外,由于 CommonJS 模塊遇到循環加載時,返回的是當前已經執行的部分的值,而不是代碼全部執行后的值,兩者可能會有差異。所以,輸入變量的時候,必須非常小心。

var a = require('a'); // 安全的寫法
var foo = require('a').foo; // 危險的寫法
exports.good = function (arg) {return a.foo('good', arg); // 使用的是 a.foo 的最新值
};
exports.bad = function (arg) {return foo('bad', arg); // 使用的是一個部分加載時的值
};

上面代碼中,如果發生循環加載,require('a').foo的值很可能后面會被改寫,改用require('a')會更保險一點。

ES6 模塊的循環加載
ES6 處理“循環加載”與 CommonJS 有本質的不同。ES6 模塊是動態引用,如果使用`import`從一個模塊加載變量(即`import foo from 'foo'`),那些變量不會被緩存,而是成為一個指向被加載模塊的引用,需要開發者自己保證,真正取值的時候能夠取到值。

請看下面這個例子。

// a.mjs
import {bar} from './b';
console.log('a.mjs');
console.log(bar);
export let foo = 'foo';
// b.mjs
import {foo} from './a';
console.log('b.mjs');
console.log(foo);
export let bar = 'bar';

上面代碼中,a.mjs加載b.mjsb.mjs又加載a.mjs,構成循環加載。執行a.mjs,結果如下。

$ node --experimental-modules a.mjs
b.mjs
ReferenceError: foo is not defined

上面代碼中,執行a.mjs以后會報錯,foo變量未定義,這是為什么?

讓我們一行行來看,ES6 循環加載是怎么處理的。首先,執行a.mjs以后,引擎發現它加載了b.mjs,因此會優先執行b.mjs,然后再執行a.mjs。接著,執行b.mjs的時候,已知它從a.mjs輸入了foo接口,這時不會去執行a.mjs,而是認為這個接口已經存在了,繼續往下執行。執行到第三行console.log(foo)的時候,才發現這個接口根本沒定義,因此報錯。

解決這個問題的方法,就是讓b.mjs運行的時候,foo已經有定義了。這可以通過將foo寫成函數來解決。

// a.mjs
import {bar} from './b';
console.log('a.mjs');
console.log(bar());
function foo() { return 'foo' }
export {foo};
// b.mjs
import {foo} from './a';
console.log('b.mjs');
console.log(foo());
function bar() { return 'bar' }
export {bar};

這時再執行a.mjs就可以得到預期結果。

$ node --experimental-modules a.mjs
b.mjs
foo
a.mjs
bar

這是因為函數具有提升作用,在執行import {bar} from './b'時,函數foo就已經有定義了,所以b.mjs加載的時候不會報錯。這也意味著,如果把函數foo改寫成函數表達式,也會報錯。

// a.mjs
import {bar} from './b';
console.log('a.mjs');
console.log(bar());
const foo = () => 'foo';
export {foo};

上面代碼的第四行,改成了函數表達式,就不具有提升作用,執行就會報錯。

我們再來看 ES6 模塊加載器SystemJS給出的一個例子。

// even.js
import { odd } from './odd'
export var counter = 0;
export function even(n) {counter++;return n === 0 || odd(n - 1);
}
// odd.js
import { even } from './even';
export function odd(n) {return n !== 0 && even(n - 1);
}

上面代碼中,even.js里面的函數even有一個參數n,只要不等于 0,就會減去 1,傳入加載的odd()odd.js也會做類似操作。

運行上面這段代碼,結果如下。

$ babel-node
> import * as m from './even.js';
> m.even(10);
true
> m.counter
6
> m.even(20)
true
> m.counter
17

上面代碼中,參數n從 10 變為 0 的過程中,even()一共會執行 6 次,所以變量counter等于 6。第二次調用even()時,參數n從 20 變為 0,even()一共會執行 11 次,加上前面的 6 次,所以變量counter等于 17。

這個例子要是改寫成 CommonJS,就根本無法執行,會報錯。

// even.js
var odd = require('./odd');
var counter = 0;
exports.counter = counter;
exports.even = function (n) {counter++;return n == 0 || odd(n - 1);
}
// odd.js
var even = require('./even').even;
module.exports = function (n) {return n != 0 && even(n - 1);
}

上面代碼中,even.js加載odd.js,而odd.js又去加載even.js,形成“循環加載”。這時,執行引擎就會輸出even.js已經執行的部分(不存在任何結果),所以在odd.js之中,變量even等于undefined,等到后面調用even(n - 1)就會報錯。

$ node
> var m = require('./even');
> m.even(10)
TypeError: even is not a function

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/72528.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/72528.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/72528.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

國內外優秀AI外呼產品推薦

在數字化轉型浪潮中&#xff0c;AI外呼系統憑借其高效率、低成本、精準交互的特點&#xff0c;成為企業客戶觸達與服務的核心工具。本文基于行業實踐與技術測評&#xff0c;推薦國內外表現突出的AI外呼產品&#xff0c;重點解析國內標桿企業云蝠智能&#xff0c;并對比其他代表…

【無標題】FrmImport

文章目錄 前言一、問題描述二、解決方案三、軟件開發&#xff08;源碼&#xff09;四、項目展示五、資源鏈接 前言 我能抽象出整個世界&#xff0c;但是我不能抽象你。 想讓你成為私有常量&#xff0c;這樣外部函數就無法訪問你。 又想讓你成為全局常量&#xff0c;這樣在我的…

給定計算預算下的最佳LLM模型尺寸與預訓練數據量分配

給定計算預算下的最佳LLM模型尺寸與預訓練數據量分配 FesianXu 20250304 at Wechat Search Team 前言 如果給定了計算預算 C C C&#xff0c;如何分配LLM的模型尺寸 N N N和訓練的數據量 D D D&#xff0c;才能使得模型的效果 L L L最好呢&#xff1f;筆者在此介紹一篇經典的文…

青訓營:簡易分布式爬蟲

一、項目介紹 該項目是一個簡易分布式爬蟲系統&#xff0c;以分布式思想為基礎&#xff0c;通過多節點協作的方式&#xff0c;將大規模的網頁抓取任務分解&#xff0c;從而高效、快速地獲取網絡數據 。 項目地址&#xff1a;https://github.com/yanchengsi/distributed_crawle…

任務9:交換機基礎及配置

CSDN 原創主頁&#xff1a;不羈https://blog.csdn.net/2303_76492156?typeblog 一、交換機基礎 交換機的概念&#xff1a;交換機是一種網絡設備&#xff0c;用于連接多臺計算機或網絡設備&#xff0c;實現數據包在局域網內的快速交換。交換機基于MAC地址來轉發數據包&#x…

YOLOv8改進------------SPFF-LSKA

YOLOv8改進------------SPFF-LSKA 1、LSAK.py代碼2、添加YAML文件yolov8_SPPF_LSKA.yaml3、添加SPPF_LSKA代碼4、ultralytics/nn/modules/__init__.py注冊模塊5、ultralytics/nn/tasks.py注冊模塊6、導入yaml文件訓練 1、LSAK.py代碼 論文 代碼 LSKA.py添加到ultralytics/nn/…

[Lc(2)滑動窗口_1] 長度最小的數組 | 無重復字符的最長子串 | 最大連續1的個數 III | 將 x 減到 0 的最小操作數

目錄 1. 長度最小的字數組 題解 代碼 ?2.無重復字符的最長子串 題解 代碼 3.最大連續1的個數 III 題解 代碼 4.將 x 減到 0 的最小操作數 題解 代碼 1. 長度最小的字數組 題目鏈接&#xff1a;209.長度最小的字數組 題目分析: 給定一個含有 n 個 正整數 的數組…

安卓binder驅動內核日志調試打印開放及原理(第一節)

背景&#xff1a; 經常有學員朋友在做系統開發時候&#xff0c;有時候遇到binder相關的一些問題&#xff0c;這個時候可能就需要比較多的binder相關日志&#xff0c;但是正常情況下這些binder通訊的的內核日志都是沒有的打印的&#xff0c;因為經常binder通訊太過于頻繁&#…

docker 安裝達夢數據庫(離線)

docker安裝達夢數據庫&#xff0c;官網上已經下載不了docker版本的了&#xff0c;下面可通過百度網盤下載 通過網盤分享的文件&#xff1a;dm8_20240715_x86_rh6_rq_single.tar.zip 鏈接: https://pan.baidu.com/s/1_ejcs_bRLZpICf69mPdK2w?pwdszj9 提取碼: szj9 上傳到服務…

MWC 2025 | 紫光展銳聯合移遠通信推出全面支持R16特性的5G模組RG620UA-EU

2025年世界移動通信大會&#xff08;MWC 2025&#xff09;期間&#xff0c;紫光展銳聯合移遠通信&#xff0c;正式發布了全面支持5G R16特性的模組RG620UA-EU&#xff0c;以強大的靈活性和便捷性賦能產業。 展銳芯加持&#xff0c;關鍵性能優異 RG620UA-EU模組基于紫光展銳V62…

達夢適配記錄-檢查服務器

service DmServicedmdb status 查看是否開啟&#xff0c;沒有配置systemctl&#xff0c;查看《DM8_Linux 服務腳本使用手冊》2.1.2.2 1 &#xff0e;拷貝服務模板文件&#xff08; DmService &#xff09;到目錄&#xff08; /opt/dmdbms/bin &#xff09;&#xff0c;并將新文…

Pipeline模式詳解:提升程序處理效率的設計模式

文章目錄 Pipeline模式詳解&#xff1a;提升程序處理效率的設計模式引言Pipeline的基本概念Pipeline的工作原理Pipeline的優勢Pipeline的應用場景1. 數據處理2. DevOps中的CI/CD3. 機器學習4. 圖像處理 常見的Pipeline實現方式1. 函數式編程中的Pipeline2. 基于消息隊列的Pipel…

STM32單片機芯片與內部115 DSP-FIR IIR低通 高通 帶通 帶阻 中值 自適應 濾波器 逐個數據實時 樣條插值擬合

目錄 一、FIR 低通、高通、帶通、帶阻 1、FIR濾波器特點 2、濾波器結構 3、濾波器系數 4、濾波實現 5、FIR 濾波后的群延遲 二、IIR 低通、高通、帶通、帶阻 1、IIR濾波器特點 2、濾波器結構 3、濾波器系數 4、濾波實現 5、IIR濾波后的群延遲 三、中值濾波 1、中值…

C語言_圖書管理系統_借閱系統管理

?? 歡迎大家來到小傘的大講堂?? &#x1f388;&#x1f388;養成好習慣&#xff0c;先贊后看哦~&#x1f388;&#x1f388; 所屬專欄&#xff1a;數據結構與算法 小傘的主頁&#xff1a;xiaosan_blog 本文所需對順序表的理解&#xff1a; 注&#xff1a;由于順序表實現圖書…

表達式基礎

文章目錄 1、表達式組成1、運算符 2、表達式的分類1、算數運算符1、自增運算符和自減運算2、取余運算(%)3、除法運算(/)4、案例 2、關系運算符3、邏輯運算符4、條件運算符(三目運算符)1、案例 5、賦值運算()1、賦值類型轉換2、復合賦值運算 6、逗號運算7、取地址運算(&)8、…

除了合并接口,還有哪些優化 Flask API 的方法?

除了合并接口&#xff0c;還有許多其他方法可以優化 Flask API&#xff0c;以下從性能優化、代碼結構優化、安全性優化、錯誤處理優化等方面詳細介紹&#xff1a; 性能優化 1. 使用緩存 內存緩存&#xff1a;可以使用 Flask-Caching 擴展來實現內存緩存&#xff0c;減少對數…

Web服務器配置

配置虛擬主機 通過虛擬主機&#xff0c;可以實現用自定義的域名來訪問&#xff0c;并且可以為不同的域名指定不同的站點目錄。 配置IP地址和域名的映射關系 申請真實的域名需要一定的費用&#xff0c;為了方便開發&#xff0c;可以通過修改hosts文件來實現將任意域名解析到本…

爬蟲逆向實戰小記——解決webpack實記

注意&#xff01;&#xff01;&#xff01;&#xff01;某XX網站實例僅作為學習案例&#xff0c;禁止其他個人以及團體做謀利用途&#xff01;&#xff01;&#xff01; aHR0cHM6Ly9wbW9zLnhqLnNnY2MuY29tLmNuOjIwMDgwL3B4Zi1zZXR0bGVtZW50LW91dG5ldHB1Yi8jL3B4Zi1zZXR0bGVtZW5…

藍橋杯 之 前綴和與查分

文章目錄 題目求和棋盤挖礦 前綴和有利于快速求解 區間的和、異或值 、乘積等情況差分是前綴和的反操作 前綴和 一維前綴和&#xff1a; # 原始的數組num,下標從1到n n len(num) pre [0]*(n1) for i in range(n):pre[i1] pre[i] num[i] # 如果需要求解num[l] 到num[r] 的區…

Windows10下本地搭建Manim環境

文章目錄 1. 簡介2. Python環境3. uv工具4. Latex軟件5. 安裝Manim數學庫6. 中文支持參考 1. 簡介 manim是個一科普動畫的庫&#xff0c; 本文用到的是社區版本。 2. Python環境 這個不用多說&#xff0c;可以參考其他的文章。記得把pip也安上。 3. uv工具 上面的pip是老…