可以先使用變數再宣告
因為考量到:「編寫網頁的人雖然懂 HTML 語法,但不表示他懂程式設計。」因此當初在設計 JavaScript 時有許多「容錯」的機制。
其中最經典的是「變數可以先使用再宣告」這見鬼的容錯設計:
顯示的是 124。
這就是為何大多數的程式設計師會說 JavaScript 是種糟糕的程式語言。
Hoist
可以先使用變數再宣告,是因為 JavaScript 對於識別名稱採 hoist 的做法,也就是自動把散落的識別名稱,統一提放到最上頭。所以像下面程式的 n 與 a 變數:
其實對 JavaScript 來講是:
這樣的特性,常常整死傳統程式設計師!因為這表示 n 與 a 變數並不屬於 for 區塊,而是整個 f 函式的區域變數!當寫出來的 JavaScript 程式因此不小心覆寫了變數出問題時,根本不知道錯在哪裡…甚至以為是 JavaScript 的 Bug,傻傻地等瀏覽器修正 Orz
如果識別名稱是函式,卻因為 hoist 的關係被覆寫,不知情的人在除錯時,恐怕會想放棄這一行~
區塊不是一個獨立的區域
習慣上,我們認為 { } 裡面的程式是個獨立的區塊,因此裡面的變數與大括號外面的變數是隔開的。但 JavaScript 並不是這麼回事……
顯示的是 124。
JavaScript 只有 function(){} 能實現與外界隔絕的區塊,或者說,JavaScript 沒有 block scoping(區塊範疇),只有 function scoping(函式範疇)。因此,確定要使用 block scoping 的話,通常採用「直接呼叫匿名函式」的手法來實現!
自動轉型
接下來這點超級重要!JavaScript 是「自動轉換資料型態」的程式語言,所以常常發生「123 等於 '123'」的情況,如果你不了解這點,你的 JavaScript 將破綻百出,要命的是除錯時完全摸不著頭緒。
來看幾個範例,下面的程式因為自動轉型的關係,結果顯示 3:
所以看到下面「線路比較複雜一點」的程式,居然也能顯示 3,不用覺得奇怪:
這是因為 a['1'] 傳回 '2',所以這看起來複雜的程式跟 a['2'] 是一樣的意思。
設計 JavaScript 程式,時時刻刻都需要考慮這樣情況的發生,尤其在函式中處理所接受參數時,更要留意自動轉型可能帶來的影響。
有人因此懷念 Java 程式語言嚴格的型別檢查機制,但也有人善用 JavaScript 這樣的特性,寫出看上去相當清爽的語法結構。
=== 與 !==
JavaScript 的「自動轉換資料型態」誇張到連「比對兩資料是否相等」時照樣發生!
上面程式將顯示 HELLO,數值 1 與字串 1 居然是相等的。
如果你不希望這樣的結果,請改用 === 或 !== 來比對資料,例如下面程式就不會跟你說 HELLO:
題外話,真希望兩者顛倒過來,=== 是自動轉型比對,== 則跟傳統程式語言一樣~
false
JavaScript 將以下資料的值視為 false:
'' 與 ''
0
false
NaN
null
undefined
所以我們常常設計出如下函式,代入並不覺得有什麼不妥的參數,卻發生摸不著頭緒的錯誤:
因為 0 是 false,所以 if(0) 不會是 true,於是不會將 x 顯示出來,反而要求你輸入參數。
如果這不是你想要的結果,上述的程式寫得詳細一點就好:
undefined、null、NaN
其實 undefined 和 null 並不是「關鍵字」,比較像是「識別名稱」,這兩個識別名稱保存特殊的資料值。
所以幸好 undefined=='undefined'
不是 true。
NaN 詭異一點,它不是「關鍵字」也不是「識別名稱」,這組字母本身就是一種「資料值」了。
但 NaN 這個值,卻不能拿來做比較運算……
顯示的是「不等於」,這還真的是見鬼了~
所以在處理 NaN 的時候,注意!千萬要注意!請改用 isNaN() 來判斷是不是 NaN,不要用 == 和 === 來比較。
NaN 告訴我們 JavaScript 的數值是 double 型態
JavaScript 的數值型態會出現 NaN,告訴我們一件事:「數值其實是 double 資料型態。」因為這是 IEEE 754 浮點數標準的特有產物,它表示 Not a Number,用來處理「除以零」 或「與無限大值運算」之類的情況。
所以要注意!JavaScript 沒有真正的整數,它是由「64 位元雙精度值」產生的。否則,搭上 JavaScript 從頭到尾都在自動轉型的特性,稍有不慎被自動進行了小數運算,就會發生一般程式設計 double 數值不夠精確的問題,導致計算錯誤或無窮迴圈,除錯時我們卻以為自己都在進行整數運算而摸不著頭緒。
這點無解,只能自己多留意計算過程中是否混搭了整數與小數,是的話要多測試,判斷哪些地方該用 parseInt() 或 Math 的各式操作來解決問題:「因為 JavaScript 只有 double 數值,沒有其它~」
直接附加屬性
JavaScript 是具有很強表達能力的程式語言,其中一項就是「直接附加屬性」到程式結構上的語法特性。
首先建立如下程式結構:
看到這裡,你可能會問:「f() 函式設計錯誤,M 根本沒有 a 變數。」遺憾的是,JavaScript 並不認為這個函式語法錯誤,它只會說 a 是 undefined,但程式是合法的。
不過這不是重點,讓我們接著看:
執行結果,居然顯示 999!也就是 M.a 這樣的寫法居然不會通知說 M 裡面並沒有 a 這筆變數,反而把 a 給添加到 M 裡面了!
雖然這很方便,例如 HTML5 用來保存使用者資料的 localStorage,就能用這樣的方式,讓存取資料的程式碼變得簡潔、易懂:
否則你原本應該寫得這麼冗雜:
但如果是寫程式不小心打錯字時,這種非但不提醒說程式寫錯,反而「將錯就錯」的機制,往往造成我們發現程式計算結果不正常時,會有很長一段時間找不到除錯的源頭在哪:「你根本不會想到說居然是打錯字的原因,而且打錯字還造成新的變數,造成計算結果跑到另一筆變數去了。」
this
this 其實不是「物件本身」或「函式本身」,而是「呼叫端」。
或者說,this 是相對於呼叫它的場合而來,不是絕對性地專屬於物件自身。
所以,使用 this 指向自己旗下的成員時看似正常,是因為從物件內部使用 this 時,自己剛好是「呼叫端」。
但如果呼叫 this 的場合不是從物件內部自身,那就無法如你所預期地計算結果;JavaScript 這樣富有表達力的程式語言,總會寫出讓人意想不到的程式結構。
關於最上層物件 window
window 做為最上層物件,除了可將 window.alert()
省略為 alert()
外,還有一個涵義,那就是全域變數其實都是 window 的屬性。
例如在全域空間寫 var a=123
,其實等於 window.a=123
。
由於 window 物件本身用了許多常見的識別名稱,像是 name、top、self、open()、close()、print()…,在全域空間宣告變數時,很容易就覆寫掉瀏覽器既有的程式功能。
因此,常常會看到有人把 JavaScript 程式寫在匿名函式裡面來執行:
Closure
原理
一個原本生命週期只存在於函式範疇的變數,在內部函式存取了外部函式的區域變數,而內部函式生命週期卻比外部函式來得長的情況下,延續了該區域變數的生命週期,這種現象就叫 closure,俗稱「閉包」。
如下範例,在 F 物件裡,函式 closure() 延續了外部變數 g 的生命週期:
從叫用兩次 f() 分別得到 99 與 98 來看,f 函式能夠保存計算的結果,但又只有 g 區域內能夠操作這筆資料,因此 closure 通常被物件導向思維的人充當 private 使用。但對早已習慣一級函數程式語言的人,則用在數學計算的場合,讓函數追蹤計算過程來推演下去的思維使用它。
私有變數的應用
總之,有了 closure 這語言特性,我們便能設計出這樣的程式結構:
如果你試圖存取 F 物件裡的變數 g:
因為 f.g 是 f 物件的 g 屬性,並不是 F 物件的閉包 g,兩者是不一樣的資料。所以 f.f3() 照樣輸出 124,而不是 456。而閉包 g 只有內部函式能存取,無法經由 f 參考去存取,因此達到私有變數的效果。
輸出 f.g 就可以清楚了解 g 屬性和 g 閉包,是兩筆各自存在的資料:
各自保存獨立資料的物件
上一節範例單純示範函式物件的 closure 程式結構,宣告 f 物件時沒有使用 new 建立一個新的 F 物件。如果你需要建立多個資料獨立分開保存的 F 物件,就要用 new:
沒使用 new 的話,a 和 b 會指向同一個 F 物件,這樣就無法達成資料獨立保存在兩個不同物件的設計了。
匿名 function 物件
承 Closure 的範例,像這種傳回的函式只有一個時,可以進一步使用「匿名函式」的技巧:
執行結果與 Closure 的範例一樣,只是程式結構簡化了!需要注意的是,第 5 行以 ; 結尾。
function f() 與 var f=function() 的區別
function f()
是建立具名函式為 f,或者說 f 真的是函式本身。
var f=function()
則是宣告 f 識別名稱然後指向後面那個匿名函式,或者說 f 是 reference(參照)。
立即函式
利用 JavaScript 的函式其實是物件的特性,可以在建立好函式時當場執行。只要把「函式表示式」包在 () 裡面成為一個優先運算的區塊,後面再使用一個 () 表示要呼叫這個區塊即可。這樣特性的語法結構如下:
(function(){})();
底下是針對該語法結構的範例:
進階:區域變數與參數
底下範例,為 immediate function 設置了 x 參數,並且宣告一個 a 變數,然後代入 10 來執行 for 迴圈:
補充:也可以變成這樣的語法結構
通常大家比較喜歡用這樣的方式來使用 immediate function:
(function(){}());
也就是把語法結構的重點,擺在 function(){}() 表示這是直接呼叫的函式,再整個用 () 包起表示優先運算的區域。
函式串接
要讓函式串接起來呼叫,每個函式最後寫上 return this; 即可。
prototype
共用物件狀態與功能
prototype 是每個 JavaScript 物件共同繫結為 this 的屬性,這會形成一條 prototype 物件關係鏈,JavaScript 會自動循線追訪物件,調用其狀態與功能,達成共用程式的目的。
聽不懂沒關係,先用實際例子來試試 prototype 吧!我們讓 JavaScript 內建的 Array 物件,新增一個 f() 函式:
接著實際建立幾個 Array 物件並呼叫 f() 看看:
a 與 b 兩者都顯示 HELLO 的訊息。
共用與繼承
很多人將 prototype 視為 JavaScript 的繼承機制:
但這並不很正確,因為將變數或函式加入到 prototype 後,每個關聯到該 prototype 的物件將共用該變數或函式,並不是每個物件各自實作一份該變數與函式。或者說,prototype 類似一種 static,而不是 insctance。所以在使用 prototype 時,思考的是「是否共用變數與函式」,而不是「是否開放繼承」。
從上一節範例,可以清楚了解 prototype 的應用:「它不是用來繼承的機制,而是可以擴充功能到既有物件的機制。」例如希望舊版 ECMAScript 可以相容新版 ECMAScript 擴增的規格,就可以從 prototype 來下手。或者你覺得手上有個基本的功能,認為它實用到應該擴充給每個 JavaScript 使用,那就加入到 Object.prototype!
事實上,JavaScript 設計之初,Brendan Eich 就很不屑用物件導向來擴充程式碼,因為透過原型機制擴充語法功能更好!所以 prototype 是站在擴充語法的層次引進到 JavaScript,與 Java 用來整理原始碼的 extends 並不是一樣的層次。Java 的 extends 是在類別的層次上繼承程式碼,JavaScript 的 prototype 是在物件的層次上繼承狀態和功能。
由於 JavaScript 沒有繼承,所以 prototype 被習慣 Java 程式語言的使用者當作一種繼承的機制,但無法站在 prototype 的層次來「擴充語法」,會寫不出 JavaScript 的剽悍風格。甚至更嚴重的是,始終無法了解 prototype 和繼承是兩回事:「是共用物件狀態與功能,不是繼承程式碼。」
Prototype 程式風格
ECMAScript 5 開始,針對 prototype 與 new 兩者混用所造成的弊端,提供許多有利於還原 prototype 風格的功能,讓 JavaScript 的物件導向程式設計可以變得更優雅…
Object.create()
類似 new 的用法
class1 與 class2 共同擁有一樣的行為,卻各自擁有獨立的屬性值,修改各自的屬性值也不影響其它物件。
但不適用以閉包做為私有變數的物件:
B 修改閉包變數 a,A 的閉包變數也跟著修改了,並非各自擁有一份 a 屬性。只有將 var a 改為 this.a 才能各自擁有一份 a,但這樣就失去私有變數的作用了~
類似 extends 的用法
這接近 Java 程式設計的 extends 寫法:
class Parent{
String field='umm';
void method(){
System.out.print(field);
}
}
class Sub extends Parent{
public Sub(){
field='hello';
}
void hello(){
method();
}
}
Sub sub=new Sub();
sub.hello();
雖然 ECMAScript 6 已支援 extends 語法,可以讓 JavaScript 的程式寫得更像 Java,但它基於「建構函式(constructor function)」,而不是「物件語句(object literal)」,前者必須 new 出物件才能使用,後者直接就是物件。因此 Object.create() 還是有其存在價值,它搭配 object literal 的話,可以表現基於原型的程式設計風格(prototype based programming),extends 只能用來表現物件導向程式設計風格(object oriented programming)。
就是 prototype 的用法
由於 Object.create() 是基於 prototype,也就是物件繼承,不是類別繼承,因此要仿子類別繼承父類別,而用「建構函式」的話,需傳入 prototype:
看起來很彆扭,因為基於 prototype 的繼承,這樣寫也做得到:
只是這種寫法不完美,它並未真正繼承到屬性,所以用 Object.create(),它會臨時建立一個真正的物件,用真正的物件進行原型繼承,然後傳回,這樣就可以更完美繼承~
無論如何,基於 prototype 的 Object.create(),還是應該用在物件才恰當:
仿類別繼承的彆扭寫法,在 ECMAScript 6 推出新語法 class
和 extends
漂亮解決,不建議再寫彆扭的程式,請把 Object.create() 用在物件層次的繼承,不要用在仿類別層次的繼承。
var A={};
這種「物件語句」寫法,直接就是一個物件出來,就跟 var B=[];
直接就是一個陣列出來一樣,所以盡量將 Object.create() 用在這上面,發揮 prototype 從物件層次上繼承的精神!
Function.bind()
bind() 會傳回一個函式物件,讓你將東西繫結到物件上,效果接近 new 一個物件,但比 new 厲害的是可以選擇作用域,而非只能用 this 做為函式物件的作用域:
通常為了 new 一個物件,必須讓 JavaScript 的函式偽裝得像 Java 一樣有 field 有 method,但 JavaScript 的函式物件不見得要以這樣架構來執行。於是有人將 ECMAScript 5 新增的 bind() 來重新詮釋 JavaScript 該有的特質!
Object.getPrototypeOf()
Object.getPrototypeOf() 傳回物件的 prototype。
Literal
new Array()
可寫成 []
new Object()
可寫成 {}
new String('')
可寫成 ''
new Number()
可寫成 ()
function(){}
可寫成 (function(){})
對於純物件導向來說,資料直接就是物件!因此:
像這樣直接使用 literal 對資料物件本身操作功能,程式寫起來會非常爽快!我個人將 literal 稱為符文,像畫符咒施展法術一樣神奇 (●'◡'●)
其中 function(){} 在 ECMAScript 6 有新語法,請參考《箭頭函式》。
a=true && f()
由於 JavaScript 鼓勵使用 literal syntax,因此有時候連這樣的程式結構:
也會被這樣來寫:
無論你接不接受這種寫作風格,你至少要知道這樣的程式會正常動作。
NaN != NaN
兩個值為 NaN 的 reference(變數名稱)或 instance(實體)無論怎麼比較,結果都是 false,甚至拿自己跟自己相比也是 false,因此要判斷是否為 NaN 時,通常建議用 isNaN():
但這個世間就是會出天才,乾脆利用 NaN 時,自己跟自己比居然是 false 的情況,改用 != 來判斷一個變數是否為 NaN:
正常情況下,自己怎麼可能不等於自己?但如果自己真的不等於自己、a!=a 居然傳回 true 時,那就是 NaN。
除非前後文交代得很清楚,例如 if(a!=a) alert('NaN');
,否則很少人會願意讀到這樣的程式。但你還是得知道為什麼會這樣寫,因為在 JavaScript 就是有少數人偏好這種符文式寫作風格,明明沒有語意,卻覺得程式碼看起來比較一致性、比較整齊~
不過,也有人是因為 isNaN() 並不可靠,所以才改用 NaN!=NaN。但那根本是自己的設計有問題,與其改用 NaN!=NaN 才能正常,還不如思考為什麼自己的設計跑 isNaN() 會不正常。的確 NaN!=NaN 是很棒的做法,也不會出問題,但留著不正常的設計不改,然後為 NaN!=NaN 沾沾自喜,很容易到後來還是搞砸了整個設計~
for.. in.. 不是強化迴圈
for.. in.. 其實是「for 迴圈」+「in 迭代器」,跟 Java 的「強化迴圈(Enhanced for loop)」並不全然是一樣的設計!
從上面的例子可以看到,in araay
傳給 var n
的是索引值。因此要逐元素存取陣列的值時:
這很明顯與 Java 的強化迴圈傳回情況不一樣。
in 迭代器
有些人不知道 in 其實是語法指令(operator),可以單獨使用:
0 和 3 因為都在 array 索引範圍內,所以「資料成立」傳回 true。4 因為 array[4] 的話是 undefined,所以傳回 false。
除了陣列,原型物件也可用 in 迭代器:
別緊張,其實原理很簡單:object.['user'] 與 object.['email'] 傳回 true,object.['connections'] 的話 undefined 所以傳回 false。
函數式 API
Array.prototype.forEach()
過去我們很常使用 for 迴圈來巡查陣列的內容:
現在這整個邏輯結構,直接裝在 forEach() 裡面來完成,不用再敲那重複的動作:
Array.prototype.map()
我們也很常拿另一個陣列來保存某個需要經過加工的陣列:
現在可以這樣寫:
跟 forEach() 一樣,只是會傳回一個陣列,讓你映射到另一個陣列中。
Array.prototype.filter()
過濾某個陣列的資料,擷取到另一個陣列,也是相當常做的事:
現在可以這樣寫:
跟 map() 很像,但不是加工後逐一對照到另一個陣列,而是過濾後擷取部分到另一個陣列。
Array.prototype.some() 與 Array.prototype.every() 的妙用
如果你想用條件式檢查陣列中是否有符合的資料,有的話傳回 true 而不用再繼續下去,可用 some() 省略親手用 if 來判斷的工作:
相反的,可用是否全部符合的 every(),來達到改傳回 false 的工作:
Array.prototype.reduce()
這個難了點,你可能要多研究個幾次才能搞懂,所以第一次看得霧煞煞的話,請不要氣餒~
透過回呼函式來使用陣列,雖然是比較進階的做法,但其實還蠻實用的,因此 ECMAScript 將這樣的演算法抽象成 reduce() 函式來使用。
怎麼用呢?首先來看 reduce() 接受的參數:
reduce(回呼函式格式,初始值)
接著,我們要像 Java 程式語言實作 interface 一樣,為 reduce() 的「回呼函式格式」設計如下參數格式的函式物件:
function(previousValue,currentValue,index,array){};
previousValue 參數用來代表前一次呼叫的結果。
currentValue 參數用來代表這次呼叫時所得到的值。
index 參數用來代表陣列的索引值。
array 參數用來代表陣列。
有了這些語法概念,實際運作的情況,讓我們用範例來了解:
程式執行結果如下:
如果怎麼看就是搞不懂範例結果怎麼來的,請點我參考進一步說明。
透過上面範例了解 reduce(),以後看到類似下面的程式,就知道其實只是拿陣列的資料進行回呼函式來做數學計算而已:
雖然了解 reduce() 運作原理後,其實整個語法很簡單,但不了解的人看到會覺得很困難。因此建議使用 reduce() 時,務必秉持一顆同理心,再三規劃整個函式內部算式的可讀性,盡量讓即使看不懂函式外部語法的人,也能從函式內部傳回值的算式,大致知道這函式在做什麼。
其他 ECMAScript 5 功能,如果我們用了,對方卻看不懂,確實可以諷刺他不夠努力,請他多學點!但 reduce() 真要活用起來,不注意命名慣例與編排風格的話,樣子真的很嚇人~
區域變數
用 var
宣告的變數,是沒有區域性的,明明把變數宣告在 {}
裡面,卻能在 {}
外面存取到資料。想要宣告區域變數,只能建立一個函式,把變數宣告在裡面。
現在有了 let
,JavaScript 會檢查變數的生存範疇,而且不像 var 有容錯機制,let 會像靜態程式語言發出錯誤訊息。
常數
ECMAScript 6 正式將 const
列為標準語法。
const 只適用於單純的變數,無法用在 object literal 裡面的變數,因為這屬於物件的屬性(object attribute),JavaScript 的態度是不需保護(not protected)。
箭頭函式
引進 => 是為了 lambda 運算式,而不是當作 function() 的新寫法!
兩者有許多不一樣的地方,像是 ()=>{} 沒有自己的 this 和 arguments,而是傳入呼叫方的來用,也不能做為建構函式。
所以,雖然 let M.f=()=>{}; 的寫法可以代替 let M.f=function(){};,但執行上略有差異,不建議將舊程式的 funciton() 改成 ()=>,以免出現相容性問題。
新寫的程式碼全面改用 => 倒是沒問題。
匿名函式
相當於:
(function(x,y){
document.write(x+y);
})(111,222);
立即函式
套用匿名函式的語法,做為簡化立即函式的寫法。
更簡潔的 object literal method 寫法
Generator
傳統函式只能傳回一次資料,而 Generator 可以不斷送出資料。ECMAScript 6 使用 function*
語法表示 Generator,並使用 yield
送出資料,而不是 return
。
除此之外,傳統函式傳回值的型態就是 return 傳回資料的型態,而以 yield 送出的資料,則會放在一種串列資料結構裡面,這種資料結構稱為 generator,可以使用 for.. of.. 迭代。
Iterator
ECMAScript 6 引進 object iteral express 機制,讓物件可以使用 for.. of.. 列舉屬性。為此增加了新的資料型態,叫做 Iterator 介面,以辨識那些物件可以列舉(iterable)。
通常我們不會自己實作 Iterator 介面,而是使用 JavaScript 為我們準備好的 API 來享受迭代物件的好處,像是 Set 和 Map。所以你只要知道,現在多了 Iterator 資料型態,遇到需要傳入 Iterator 介面的物件為參數時會用到。
可以做為 Iterator 的物件,呼叫 toString() 會顯示 [object xxx Iterator]。
for.. of.. 強化迴圈
過去用 for in 迴圈的話,取得的是索引值 0、1、2,for of 迴圈可以取得鍵值。
需要說明的是,鍵值不全然等於資料。以上面範例取得的 AAA 資料來說明,其實取得的資料是 AAA:'AAA'
這種「鍵值對(key-vluae pair)」型式,而且取得的是前面的 key 值(AAA),並不是後面的 value 資料('AAA')。只是若由 JavaScript 自動產生鍵值對格式的資料時,key 的名稱和 value 一樣而已。
類別
相當於:
function A(x,y)
{
this.h=x;
this.w=y;
A.prototype.f=function(){
document.write(this.h*this.w);
};
}
使用 class
描述的類別,如果沒有使用 new
來產生物件,會發出錯誤,解決過去直接覆寫函式的瑕疵。
解構賦值
基本用法
相當於:
let a,b,c;
a='AAA';
b='BBB';
c='CCC';
帶預設值
相當於:
let a,b='BBB';
a='AAA';
整個放入迭代器
用於鍵值對
[]
符號是照索引順序指定資料,{}
符號是根據鍵值指定資料。
因此上面的範例,其實可以寫成這樣:
只要找得到 key 就能將 value 填進去,順序怎麼排不重要,這提供了我們更全面的資料結構賦值方案!
由於 JavaScript 的物件其實就是一種鍵值對的集合,因此也可將這個語法應用在物件身上,哪天藉此發明出更精妙的物件寫法也說不定。(目前則是以這種手法寫出來的物件像鬼畫符一樣根本看不懂是什麼)
模塊
ECMAScript 的模組系統相當複雜,有多種 export 語法、也有多種 import 語法:
export let 變數,變數,…
export let 變數=資料,…
export function 函式(){}
export class 類別{}
export { 變數,變數,… }
export { 變數 as 別名,… }
export const { name1,name2:bar }=o
export default 運算式
export default function() { … }
export default function 函式() { … }
export { 功能 as default,… }
export*from '模組'
export*as 別名 from '模組'
export { 功能,功能,… } from '模組'
export { 功能 as 別名,… } from '模組'
export { default,… } from '模組'
import 預設 from '模組'
import*as 命名空間 from '模組'
import { 功能 } from '模組'
import { 功能 as 別名 } from '模組'
import { 功能,功能 } from '模組'
import { foo,bar } from 'module-name/path/to/specific/un-exported/file'
import { 功能,功能 as 別名,… } from '模組'
import 預設,{ 功能 [,[…] ] } from '模組'
import 預設,*as 命名空間 from '模組'
import '模組'
var 命名空間=import('模組')
模組程式功能會自動啟用 strict mode 規則,並套上更多安全性限制,整個規定相當龜毛,剛接觸模組的人,設計時要勤於測試,不要像寫一般 *.js 那樣,只顧著寫,很少執行程式,否則你會不知道問題出在哪。
default 是只有一個 export 的模組,這種模組在 import 時不需要 {} 符號括住功能項目…不是很吸引人的功能。
分號
過去 ECMAScript 3 的程式寫法長得像 Java 和 C 語言,因此使用 ;
做為程式結尾。到了 ECMAScript 2015 開始長得像 Python,因此不再鼓勵使用 ;
表示敘述結束。
純粹只是寫法風格的改變,ECMAScript 2015 對於段落的規範還是跟以前一樣:「每一行程式就是一個敘述,可以不用加分號。」所以 () 和 [] 為開頭的程式依然存在沒有分行的問題,這時再把分號加在前面。
寫 JavaScript 該不該用分號結尾,一直是這語言爭論最兇的話題。對此 Brendan Eich 曾在 2012 年發文《The infernal semicolon》說:
ASI is (formally speaking) a syntactic error correction procedure. If you start to code as if it were a universal significant-newline rule,you will get into trouble.
當初不加分號的設計,只是一種容錯機制,並不是語法規則。因此要加分號才對,不加的話要「容錯」啊!
...be careful not to use ASI as if it gave JS significant newlines.
更明確表示不要使用自動分號插入!
無論使不使用分號,都要尊重別人的技術觀點和風格喜好!ECMAScript 是可塑性高的程式語言,各種程式語言的使用者,都能用自己習慣的寫法設計 JavaScript 程式,才是這門語言的基本精神。
async 和 await
async 用來建立 Promise 非同步函式,await 用來在非同步函式中,等候並接收 Promise 的行為和結果。有這兩個語法,我們可以不寫 Promise 和 then() 而做到同樣的事~
async
以前要製作非同步函式,得自己在函式裡面 return 一個 Promise 物件出來:
現在可以用 async
宣告一個自己會傳回 Promise 的函式:
await
在 async function 內部,可以使用 await
等候並接收另一個 async 函式或 Promise 的結果:
上面的 f2() 相當於:
剩餘屬性、其它
物件屬性可用 ...
符號表示多個,能在函式參數傳入不定對象屬性的物件。
RegExp 支援 ?<>
(named capture groups),s
匹配參數(dotALL flag),以及 \D
(lookbehind assertions) 和 \P
(Unicode property escapes)。
在 Template literals 使用 \u
和 \x
能正常執行功能,而不是產生執行期錯誤。