背景
ES Module
是JavaScript
在ES2015
版本開始提供的語言標準級別的模組化方案,在此之前JavaScript
一直沒有語言級別的模組化體系。沒有模組化的支援,使用JavaScript開發大型應用將舉步維艱,所以經過大量的實踐,社群制定了一些模組載入方案,最主要的有執行於瀏覽器的AMD
方案和執行於以Nodejs為代表的服務端的CommonJS
方案。
由於Webpack
和Babel
等打包、轉義工具的出現,開發者已經可以在開發中使用ES Module
,AMD
已是明日黃花,使用的人越來越少,不太值得去關注。但CommonJS
方案由於Nodejs
在前端構建工具和服務端中的普及度,在Nodejs全面支援ES Module
、老版本Nodejs消亡之前,我們還是要關注CommonJS
方案以及它與ES Module
之間的區別,以免搞混、記憶混淆,釀成bug。為了為後面的禁止點做鋪墊,先讓我們來了解或回顧兩個API:Object.preventExtensions
和Object.freeze
。
Object.preventExtensions
Object.preventExtensions()將物件標記為不再可擴充套件,因此它將永遠不會具有超出它被標記為不可擴充套件的屬性。注意,一般來說,不可擴充套件物件的屬性可能仍然可被刪除。嘗試將新屬性新增到不可擴充套件物件將靜默失敗或丟擲TypeError(在strict mode下)
當我們在嚴格模式下,嘗試對不可擴充的物件進行屬性新增時,就會丟擲異常,具體程式碼如下:
"use strict"
var obj = {
age: 23,
name: `rioli`,
city: [`sz`, `jy`]
};
Object.preventExtensions(obj);
obj.province = `GD`;
執行結果:
Uncaught TypeError: Cannot add property `province`, object is not extensible
Object.freeze
Object.freeze() 方法可以凍結一個物件,凍結指的是不能向這個物件新增新的屬性,不能修改其已有屬性的值,不能刪除已有屬性,以及不能修改該物件已有屬性的可列舉性、可配置性、可寫性。該方法返回被凍結的物件。
當我們在嚴格模式下,嘗試對已凍結的物件進行屬性修改時,就會丟擲異常,具體程式碼如下:
"use strict"
var obj = {
age: 23,
name: `rioli`,
city: [`sz`, `jy`]
};
Object.freeze(obj);
obj.age = 26;
執行結果:
Uncaught TypeError: Cannot assign to read only property `age` of object `#<Object>`
構建不可擴充的凍結物件
我們可以利用Object.preventExtensions
和Object.freeze
這兩個API來組合構建一個不可擴充的凍結物件,即:不能對物件的頂級屬性物件增、刪、改等操作。
"use strict"
var obj = {
age: 23,
name: `rioli`,
city: [`sz`, `jy`]
};
Object.freeze(obj);
Object.preventExtensions(obj);
// obj.age = 26; 修改頂級屬性將引發報錯
// obj.province = `GD`; 新增頂級屬性將引發報錯
// delete obj.name; 刪除頂級屬性將引發報錯
// 但仍然可以修改物件的子物件的屬性,因為修改物件的子物件的屬性並不會修改子物件的引用,對於引用型別來說等於沒有發生值的改變
obj.city[0] = `zq`;
obj.city.push(`st`);
ES Module
禁忌之不可以修改整體匯入模組物件的直接屬性
為什麼會列出這個禁忌,這是因為在CommonJS
方案中,你是可以修改整體匯入模組物件的直接屬性的,長期在CommonJS
在ES Module
中交叉使用,難免會造成不必要的記憶混淆。但是在 ES Module
中,整體匯入的模組物件是一個不可擴充的凍結的常量物件,對其直接屬性的修改和新增將引發報錯。
假定我們正處於CommonJs
環境下,例如NodeJS中,我們匯入等個模組,然後嘗試對模組物件的屬性進行修改和新增,具體程式碼如下:
lib.js
exports.time = Date.now();
exports.getCurrrentYear = function () {
return new Date().getFullYear();
}
exports.people = {
age: 26,
name: `rioli`,
cities: [`jieyang`, `shenzhen`]
};
main.js
const lib = require(`./lib`);
const people = lib.people;
const print = (data) => {
console.log("===================================");
console.log(data);
};
print(people);
print(lib);
people.age = 999;
print(people);
people.father = `baba`;
print(people);
lib.people = 23;
print(lib);
lib.people.age = 666;
print(lib);
lib.provices = [`GD`, `FJ`];
print(lib);
執行結果通過,控制檯輸出結果如下:
===================================
{ age: 26, name: `rioli`, cities: [ `jieyang`, `shenzhen` ] }
===================================
{ time: 1545274516494,
getCurrrentYear: [Function],
people: { age: 26, name: `rioli`, cities: [ `jieyang`, `shenzhen` ] } }
===================================
{ age: 999, name: `rioli`, cities: [ `jieyang`, `shenzhen` ] }
===================================
{ age: 999,
name: `rioli`,
cities: [ `jieyang`, `shenzhen` ],
father: `baba` }
===================================
{ time: 1545274516494, getCurrrentYear: [Function], people: 23 }
===================================
{ time: 1545274516494, getCurrrentYear: [Function], people: 23 }
===================================
{ time: 1545274516494,
getCurrrentYear: [Function],
people: 23,
provices: [ `GD`, `FJ` ] }
在ES Module
環境下,整體匯入的模組物件,但我們對其進行修改和新增屬性的時候,其表現和上述利用Object.preventExtensions
和Object.freeze
這兩個API來組合構建一個不可擴充的凍結物件的表現一樣,即:這個模組物件是一個不可擴充的凍結的常量物件。演示程式碼如下:lib.mjs
export const time = Date.now();
export function getCurrrentYear() {
return new Date().getFullYear();
}
export const people = {
age: 26,
name: `rioli`,
cities: [`jieyang`, `shenzhen`]
};
main.mjs
import { people } from `./lib.mjs`;
import * as lib from `./lib.mjs`;
// 首先定一個基調,其實但我們用 import * as xx from `yy` 將一個模組做整體匯入時,此時xx會作為作為模組名稱空間
const print = (data) => {
console.log("===================================");
console.log(data);
};
print(people);
print(lib);
// 允許修改
people.age = 999;
print(people);
// 允許修改
people.father = `baba`;
print(people);
// 不允許修改,因為作為整體匯入時,模組物件的直接一級屬性是隻讀屬性,修改將引發報錯
try {
lib.people = 23;
print(lib);
} catch (e) {
print(e);
print("不允許修改lib.people,因為作為整體匯入時,模組物件的直接一級屬性是隻讀屬性,修改將引發報錯");
}
// 允許修改,因為作為整體匯入時,模組物件的直接一級屬性是隻讀屬性,但一級
// 屬性引用的物件不是隻讀物件,可以修改其屬性(只要沒有被設定為不可寫)
lib.people.age = 666;
print(lib);
// 整體匯入時,不允許給模組物件新增屬性,因為整體匯入時的模組物件是一個不可擴充的物件,不可以給模組物件新增任何屬性
try {
lib.provices = [`GD`, `FJ`];
print(lib);
} catch (e) {
print(e);
print("不允許對lib物件新增屬性,整體匯入時,不允許給模組物件新增屬性,因為整體匯入時的模組物件是一個不可擴充的物件,不可以給模組物件新增任何屬性");
}
具體執行結果:
===================================
{ age: 26, name: `rioli`, cities: [ `jieyang`, `shenzhen` ] }
===================================
{ getCurrrentYear: [Function: getCurrrentYear],
people: { age: 26, name: `rioli`, cities: [ `jieyang`, `shenzhen` ] },
time: 1545275257126 }
===================================
{ age: 999, name: `rioli`, cities: [ `jieyang`, `shenzhen` ] }
===================================
{ age: 999,
name: `rioli`,
cities: [ `jieyang`, `shenzhen` ],
father: `baba` }
===================================
TypeError: Cannot assign to read only property `people` of object `[object Module]`
at ModuleJob.run (internal/modules/esm/ModuleJob.js:106:14)
at <anonymous>
===================================
不允許修改lib.people,因為作為整體匯入時,模組物件的直接一級屬性是隻讀屬性,修改將引發報錯
===================================
{ getCurrrentYear: [Function: getCurrrentYear],
people:
{ age: 666,
name: `rioli`,
cities: [ `jieyang`, `shenzhen` ],
father: `baba` },
time: 1545275257126 }
===================================
TypeError: Cannot add property provices, object is not extensible
at ModuleJob.run (internal/modules/esm/ModuleJob.js:106:14)
at <anonymous>
===================================
不允許對lib物件新增屬性,整體匯入時,不允許給模組物件新增屬性,因為整體匯入時的模組物件是一個不可擴充的物件,不可以給模組物件新增任何屬性