腳本和模塊
概念
在ES5和之前的版本中,JavaScript源代碼只有一種類型:腳本。但是幻碱,從ES6開始,還加入了另一種源代碼類型:模塊细溅。
從概念上褥傍,可以認(rèn)為腳本是主動(dòng)性的JavaScript代碼段,是控制宿主完成特定任務(wù)的代碼喇聊;而模塊是被動(dòng)性的JavaScript代碼段恍风,是等待被調(diào)用的庫(kù)。
現(xiàn)代瀏覽器支持用script
標(biāo)簽引入腳本或模塊誓篱,但如果引入的是模塊朋贬,則要加入type="module"
。
<script type="module" src="somemodule.js"></script>
這樣窜骄,一個(gè)JavaScript程序的代碼結(jié)構(gòu)如下:
- 腳本
- 語(yǔ)句
- 模塊
- import聲明
- export聲明
- 語(yǔ)句
import
import
聲明表示引入某個(gè)模塊锦募,既可以引入整個(gè)模塊中的內(nèi)容,也可以引入部分內(nèi)容邻遏,區(qū)別在于有沒有from
關(guān)鍵字糠亩。
// 引入整個(gè)模塊
import "module1.js"
// 引入部分內(nèi)容
import {Class1, Class2, function1} from "module2.js"
// 引入模塊中的所有內(nèi)容,并以類似類屬性的方式調(diào)用准验。只有必要時(shí)才建議這么使用赎线,因?yàn)榭赡芸赡軙?huì)引入無用變量。
import * as x from "module3.js"
引入整個(gè)模塊只能保證里面的代碼被執(zhí)行糊饱,無法獲取里面的任何內(nèi)容垂寥。相反,使用from
可以引入模塊中的一部分,并把它們變成本地變量矫废。
另外盏缤,還有一種import
寫法:
import x from "module3.js"
這種寫法引入的是模塊中的默認(rèn)值,x
可自定義蓖扑,默認(rèn)值是和default
搭配的export
語(yǔ)句唉铜,后面有解釋。
// ?? m1.js
export var num = 1;
export function increaseNum(){
num++;
console.log("increased variable 'num'")
}
// ?? m2.js
import {num, increaseNum} from "./m1.js"
console.log(num) // 1
increaseNum() // increased variable 'num'
console.log(num) // 2
通過這個(gè)例子律杠,我們知道導(dǎo)入的變量還是原來的變量潭流,只是在修改名稱之后放在了其他位置而已。在實(shí)際工作中柜去,用這種方式在多個(gè)模塊之間共享變量是一個(gè)可選的方案灰嫉。
export
與聲明連寫
將export
和聲明語(yǔ)句寫在一起,比如:
// export an array
export let months = ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
// export a constant
export const MODULES_BECAME_STANDARD_YEAR = 2015;
// export a class
export class User {
constructor(name) {
this.name = name;
}
}
與聲明不連寫
將export
和聲明語(yǔ)句分開寫嗓奢,比如:
// ?? say.js
function sayHi(user) {
alert(`Hello, ${user}!`);
}
function sayBye(user) {
alert(`Bye, ${user}!`);
}
export {sayHi, sayBye};
as
export
也可以和as
連用讼撒,給變量重命名,比如對(duì)于上面的代碼股耽,可以這么寫:
// ?? say.js
...
export {sayHi as hi, sayBye as bye};
// ?? main.js
import * as say from './say.js';
say.hi('John'); // Hello, John!
say.bye('John'); // Bye, John!
default
default
和export
配合根盒,表示導(dǎo)出一個(gè)默認(rèn)變量值,可以和class
與function
連用物蝙。
// ?? user.js
// 方式1:直接添加default
export default class User {
constructor(name) {
this.name = name;
}
}
// 方式2:聲明和default分開寫
class User {
constructor(name) {
this.name = name;
}
}
export default User;
// or
export {User as default};
在這種場(chǎng)景中炎滞,import
語(yǔ)句不需要使用大括號(hào){ }
,而且可自定義變量值的名稱诬乞。
Statement | Named export | Default export |
---|---|---|
export | export class User {...} |
export default class User {...} |
import | import { User } from ... |
import User/MyUser/... from ... |
在工程實(shí)踐中册赛,更建議一個(gè)模塊中只包含一個(gè)變量,并作為默認(rèn)變量值導(dǎo)出震嫉,同時(shí)結(jié)合文件結(jié)構(gòu)組織代碼森瘪,以形成良好的代碼風(fēng)格。
export ... from
export ... from
表示從其他模塊導(dǎo)出變量责掏。這種方式適合將多個(gè)模塊中的變量整合在一起柜砾,統(tǒng)一向外提供訪問入口,能起到優(yōu)化代碼的作用换衬。例如這種情況:
auth/
index.js
user.js
helpers.js
tests/
login.js
providers/
github.js
facebook.js
...
現(xiàn)在要把所有模塊中的內(nèi)容放在index.js
中統(tǒng)一導(dǎo)出,既可以這樣寫:
import {login, logout} from './helpers.js';
export {login, logout};
// import default as User and export it
import User from './user.js';
export {User};
...
也可以這樣寫:
export {login, logout} from './helpers.js';
export {default as User} from './user.js';
...
顯然证芭,第二種方式比第一種方式簡(jiǎn)便多了瞳浦。
預(yù)處理
預(yù)處理是指,在JavaScript引擎執(zhí)行代碼之前废士,會(huì)提前處理聲明變量語(yǔ)句var
叫潦、let
、const
官硝,函數(shù)聲明function
矗蕊,類聲明class
短蜕,以明確所有變量的基本信息。
var
var
聲明永遠(yuǎn)作用于模塊傻咖、腳本和函數(shù)體級(jí)別朋魔。在預(yù)處理階段,不關(guān)心賦值部分卿操,只管聲明部分警检。
var a = 1;
function foo() {
console.log(a);
var a = 2;
}
foo();
在這個(gè)例子中,經(jīng)過預(yù)處理之后得知害淤,函數(shù)foo
作用域內(nèi)也聲明a
扇雕,所以不會(huì)訪問外面的a
,但是在執(zhí)行到console.log
時(shí)窥摄,還沒有賦值镶奉,所以是undefined
。
如果給里面的聲明語(yǔ)句加上控制語(yǔ)句if
:
var a = 1;
function foo() {
console.log(a);
if(false) {
var a = 2;
}
}
foo();
經(jīng)過執(zhí)行發(fā)現(xiàn)還是undefined
崭放,這是因?yàn)殡m然if(false)
里面的語(yǔ)句永遠(yuǎn)不會(huì)被執(zhí)行腮鞍,但是在預(yù)處理階段并不管這些,var
會(huì)穿透一切語(yǔ)句結(jié)構(gòu)莹菱,所以結(jié)果和前面的一樣移国。
function
function
和var
類似,但是在新的JavaScript標(biāo)準(zhǔn)中道伟,對(duì)其進(jìn)行了一些修改迹缀,使其更加復(fù)雜。主要是function
的聲明在預(yù)處理階段蜜徽,不但會(huì)在作用域內(nèi)加入變量祝懂,還會(huì)賦值。
console.log(foo);
function foo(){
console.log("foo")
}
經(jīng)過執(zhí)行驗(yàn)證拘鞋,的確輸出了函數(shù)值砚蓬。如果再加入if
:
console.log(foo);
if (true) {
function foo(){
console.log("foo")
}
}
當(dāng)加入控制語(yǔ)句if
后,在預(yù)處理階段只會(huì)聲明變量盆色,而不會(huì)賦值灰蛙,所以得到了undefined
。
再看另一個(gè)例子:
console.log(b)
function b() { };
var b = 1 ;
console.log(b)
經(jīng)過執(zhí)行發(fā)現(xiàn)先打印函數(shù)值隔躲,然后打印1摩梧。這說明:在預(yù)處理階段,function
聲明的優(yōu)先級(jí)高于var
宣旱。
class
class
聲明在全局的行為和function
與var
都不太一樣仅父。例如下面的例子:
console.log(c1)
class c1 {
constructor(a) {
this.a = a
}
}
經(jīng)過執(zhí)行得知,在class
聲明之前就使用class
,會(huì)報(bào)錯(cuò)笙纤。再來看看另一個(gè)例子:
var c = 1;
function foo(){
console.log(c);
class c {}
}
foo();
同樣耗溜,還是報(bào)錯(cuò),但是去掉函數(shù)體內(nèi)的class
聲明省容,則正常打印1抖拴。
這至少說明:函數(shù)體內(nèi)的class
聲明語(yǔ)句經(jīng)過了某些預(yù)處理,它會(huì)在作用域中創(chuàng)建變量蓉冈,并且要求訪問它時(shí)拋出錯(cuò)誤城舞。這樣更符合我們一般的認(rèn)知,如果還沒有聲明變量寞酿,那么應(yīng)該更早地拋出錯(cuò)誤信息家夺。
指令序言
腳本和模塊都支持一種特別的語(yǔ)法:指令序言,最早是為了use strict
設(shè)計(jì)的伐弹,它規(guī)定了一種給JavaScript代碼添加元信息的方式拉馋。
use strict
use strict
表示JavaScript代碼是在嚴(yán)格模式下執(zhí)行,而非普通模式惨好。顧名思義煌茴,在嚴(yán)格模式下,就是代碼在執(zhí)行時(shí)會(huì)被更加嚴(yán)格的規(guī)則來約束其行為日川。根據(jù)阮一峰老師的博客文章蔓腐,嚴(yán)格模式有下面這些目的:
- 消除Javascript語(yǔ)法的一些不合理、不嚴(yán)謹(jǐn)之處龄句,減少一些怪異行為;
- 消除代碼運(yùn)行的一些不安全之處回论,保證代碼運(yùn)行的安全;
- 提高編譯器效率分歇,增加運(yùn)行速度傀蓉;
- 為未來新版本的Javascript做好鋪墊。
另外职抡,嚴(yán)格模式有兩種使用方式葬燎,一是在整個(gè)腳本中,另一個(gè)是在單個(gè)函數(shù)體內(nèi)使用缚甩。
在整個(gè)腳本中使用時(shí)谱净,必須放在文件第一行,否則無效蹄胰。
'use strict';
console.log("this is on strict mode")
function f(){
console.log(this);
};
f.call(null); // null
在嚴(yán)格模式下岳遥,普通函數(shù)中的this
將嚴(yán)格按照傳入進(jìn)去的值執(zhí)行。而如果不在嚴(yán)格模式下裕寨,則為global
。
同樣,在函數(shù)中使用嚴(yán)格模式宾袜,也必須將use strict
放在第一行捻艳。
function useStrict() {
"use strict";
return this;
}
useStrict(); // undefined
no lint
no lint
表示此文件不需要進(jìn)行進(jìn)行語(yǔ)法檢查。
"no lint";
"use strict";
function doSth(){
//...
}
...
總結(jié)
JavaScript程序源代碼可分為兩種形式庆猫,一種是腳本认轨,另一種是在ES6中才加入的模塊。
模塊的加入讓代碼的組織更加靈活月培,結(jié)合import
和export
可以靈活地控制變量作用域嘁字。import
語(yǔ)句不但能夠引入整個(gè)模塊,讓模塊內(nèi)的所有代碼被執(zhí)行杉畜,還可以引入模塊中的部分內(nèi)容作為本地變量來使用纪蜒,而引入模塊中的默認(rèn)值是以值得形式引入的,也就是說此叠,引入之后將和其他作用域沒有關(guān)系纯续。export
語(yǔ)句既可以和聲明變量語(yǔ)句連寫,也可以分開寫灭袁,當(dāng)和default
配合使用時(shí)猬错,表示導(dǎo)出一個(gè)默認(rèn)值。另外茸歧,export
還支持從其他模塊導(dǎo)出變量倦炒,主要為了將多個(gè)模塊整合在一起,統(tǒng)一向外提供訪問入口软瞎。
預(yù)處理機(jī)制是JavaScript引擎在執(zhí)行代碼之前逢唤,會(huì)對(duì)模塊、腳本和函數(shù)體內(nèi)的聲明語(yǔ)句var
铜涉、function
智玻、class
進(jìn)行處理,以明確變量的基本信息芙代。對(duì)于不同的聲明語(yǔ)句吊奢,預(yù)處理機(jī)制各不相同,需要單獨(dú)記憶纹烹。理解這部分內(nèi)容页滚,對(duì)于理解JavaScript代碼的某些執(zhí)行邏輯,至關(guān)重要铺呵。
指令序言是一種為JavaScript代碼添加元信息的方式裹驰,最早是為use strict
設(shè)計(jì)的。嚴(yán)格模式use strict
的出現(xiàn)片挂,就是為了讓JavaScript代碼更加嚴(yán)謹(jǐn)幻林,更加安全贞盯,以避免像在預(yù)處理機(jī)制中發(fā)生的那些奇怪現(xiàn)象,這是大型項(xiàng)目必然需要的一種約束沪饺。