特別說明骄恶,為便于查閱咖熟,文章轉(zhuǎn)自https://github.com/getify/You-Dont-Know-JS
ES5 Getter/Setter
技術(shù)上講做鹰,ES5定義了getter/setter字面形式枷莉,但是看起來它們沒有被太多地使用伤柄,這主要是由于缺乏轉(zhuǎn)譯器來處理這種新的語法(其實(shí),它是ES5中加入的唯一的主要新語法)更振。所以雖然它不是一個(gè)ES6的新特性鲫趁,我們也將簡單地復(fù)習(xí)一下這種形式斯嚎,因?yàn)樗赡軙?huì)隨著ES6的向前發(fā)展而變得有用得多。
考慮如下代碼:
var o = {
__id: 10,
get id() { return this.__id++; },
set id(v) { this.__id = v; }
}
o.id; // 10
o.id; // 11
o.id = 20;
o.id; // 20
// 而:
o.__id; // 21
o.__id; // 還是 —— 21!
這些getter和setter字面形式也可以出現(xiàn)在類中孝扛;參見第三章列吼。
警告: 可能不太明顯,但是setter字面量必須恰好有一個(gè)被聲明的參數(shù)苦始;省略它或羅列其他的參數(shù)都是不合法的語法寞钥。這個(gè)單獨(dú)的必須參數(shù) 可以 使用解構(gòu)和默認(rèn)值(例如,set id({ id: v = 0 }) { .. }
)陌选,但是收集/剩余...
是不允許的(set id(...v) { .. }
)理郑。
計(jì)算型屬性名
你可能曾經(jīng)遇到過像下面的代碼段那樣的情況,你的一個(gè)或多個(gè)屬性名來自于某種表達(dá)式咨油,因此你不能將它們放在對(duì)象字面量中:
var prefix = "user_";
var o = {
baz: function(..){ .. }
};
o[ prefix + "foo" ] = function(..){ .. };
o[ prefix + "bar" ] = function(..){ .. };
..
ES6為對(duì)象字面定義增加了一種語法您炉,它允許你指定一個(gè)應(yīng)當(dāng)被計(jì)算的表達(dá)式,其結(jié)果就是被賦值屬性名役电∽簦考慮如下代碼:
var prefix = "user_";
var o = {
baz: function(..){ .. },
[ prefix + "foo" ]: function(..){ .. },
[ prefix + "bar" ]: function(..){ .. }
..
};
任何合法的表達(dá)式都可以出現(xiàn)在位于對(duì)象字面定義的屬性名位置的[ .. ]
內(nèi)部。
很有可能法瑟,計(jì)算型屬性名最經(jīng)常與Symbol
(我們將在本章稍后的“Symbol”中講解)一起使用冀膝,比如:
var o = {
[Symbol.toStringTag]: "really cool thing",
..
};
Symbol.toStringTag
是一個(gè)特殊的內(nèi)建值,我們使用[ .. ]
語法求值得到霎挟,所以我們可以將值"really cool thing"
賦值給這個(gè)特殊的屬性名窝剖。
計(jì)算型屬性名還可以作為簡約方法或簡約generator的名稱出現(xiàn):
var o = {
["f" + "oo"]() { .. } // 計(jì)算型簡約方法
*["b" + "ar"]() { .. } // 計(jì)算型簡約generator
};
設(shè)置[[Prototype]]
我們不會(huì)在這里講解原型的細(xì)節(jié),所以關(guān)于它的更多信息酥夭,參見本系列的 this與對(duì)象原型赐纱。
有時(shí)候在你聲明對(duì)象字面量的同時(shí)給它的[[Prototype]]
賦值很有用。下面的代碼在一段時(shí)期內(nèi)曾經(jīng)是許多JS引擎的一種非標(biāo)準(zhǔn)擴(kuò)展熬北,但是在ES6中得到了標(biāo)準(zhǔn)化:
var o1 = {
// ..
};
var o2 = {
__proto__: o1,
// ..
};
o2
是用一個(gè)對(duì)象字面量聲明的疙描,但它也被[[Prototype]]
鏈接到了o1
。這里的__proto__
屬性名還可以是一個(gè)字符串"__proto__"
讶隐,但是要注意它 不能 是一個(gè)計(jì)算型屬性名的結(jié)果(參見前一節(jié))起胰。
客氣點(diǎn)兒說,__proto__
是有爭議的整份。在ES6中待错,它看起來是一個(gè)最終被很勉強(qiáng)地標(biāo)準(zhǔn)化了的,幾十年前的自主擴(kuò)展功能烈评。實(shí)際上,它屬于ES6的“Annex B”讲冠,這一部分羅列了JS感覺它僅僅為了兼容性的原因瓜客,而不得不標(biāo)準(zhǔn)化的東西。
警告: 雖然我勉強(qiáng)贊同在一個(gè)對(duì)象字面定義中將__proto__
作為一個(gè)鍵,但我絕對(duì)不贊同在對(duì)象屬性形式中使用它谱仪,就像o.__proto__
玻熙。這種形式既是一個(gè)getter也是一個(gè)setter(同樣也是為了兼容性的原因),但絕對(duì)存在更好的選擇疯攒。更多信息參見本系列的 this與對(duì)象原型嗦随。
對(duì)于給一個(gè)既存的對(duì)象設(shè)置[[Prototype]]
,你可以使用ES6的工具Object.setPrototypeOf(..)
敬尺∶赌幔考慮如下代碼:
var o1 = {
// ..
};
var o2 = {
// ..
};
Object.setPrototypeOf( o2, o1 );
注意: 我們將在第六章中再次討論Object
∩巴蹋“Object.setPrototypeOf(..)
靜態(tài)函數(shù)”提供了關(guān)于Object.setPrototypeOf(..)
的額外細(xì)節(jié)署恍。另外參見“Object.assign(..)
靜態(tài)函數(shù)”來了解另一種將o2
原型關(guān)聯(lián)到o1
的形式。
對(duì)象super
super
通常被認(rèn)為是僅與類有關(guān)蜻直。然而盯质,由于JS對(duì)象僅有原型而沒有類的性質(zhì),super
是同樣有效的概而,而且在普通對(duì)象的簡約方法中行為幾乎一樣呼巷。
考慮如下代碼:
var o1 = {
foo() {
console.log( "o1:foo" );
}
};
var o2 = {
foo() {
super.foo();
console.log( "o2:foo" );
}
};
Object.setPrototypeOf( o2, o1 );
o2.foo(); // o1:foo
// o2:foo
警告: super
僅在簡約方法中允許使用,而不允許在普通的函數(shù)表達(dá)式屬性中到腥。而且它還僅允許使用super.XXX
形式(屬性/方法訪問)朵逝,而不是super()
形式蔚袍。
在方法o2.foo()
中的super
引用被靜態(tài)地鎖定在了o2
乡范,而且明確地說是o2
的[[Prototype]]
。這里的super
基本上是Object.getPrototypeOf(o2)
—— 顯然被解析為o1
—— 這就是他如何找到并調(diào)用o1.foo()
的啤咽。
關(guān)于super
的完整細(xì)節(jié)晋辆,參見第三章的“類”。
模板字面量
在這一節(jié)的最開始宇整,我將不得不呼喚這個(gè)ES6特性的極其……誤導(dǎo)人的名稱瓶佳,這要看在你的經(jīng)驗(yàn)中 模板(template) 一詞的含義是什么。
許多開發(fā)者認(rèn)為模板是一段可復(fù)用的鳞青,可重繪的文本霸饲,就像大多數(shù)模板引擎(Mustache,Handlebars臂拓,等等)提供的能力那樣厚脉。ES6中使用的 模板 一詞暗示著相似的東西,就像一種聲明可以被重繪的內(nèi)聯(lián)模板字面量的方法胶惰。然而傻工,這根本不是考慮這個(gè)特性的正確方式。
所以,在我們繼續(xù)之前中捆,我把它重命名為它本應(yīng)被稱呼的名字:插值型字符串字面量(或者略稱為 插值型字面量)鸯匹。
你已經(jīng)十分清楚地知道了如何使用"
或'
分隔符來聲明字符串字面量,而且你還知道它們不是(像有些語言中擁有的)內(nèi)容將被解析為插值表達(dá)式的 智能字符串泄伪。
但是殴蓬,ES6引入了一種新型的字符串字面量,使用反引號(hào)`
作為分隔符蟋滴。這些字符串字面量允許嵌入基本的字符串插值表達(dá)式科雳,之后這些表達(dá)式自動(dòng)地被解析和求值。
這是老式的前ES6方式:
var name = "Kyle";
var greeting = "Hello " + name + "!";
console.log( greeting ); // "Hello Kyle!"
console.log( typeof greeting ); // "string"
現(xiàn)在脓杉,考慮這種新的ES6方式:
var name = "Kyle";
var greeting = `Hello ${name}!`;
console.log( greeting ); // "Hello Kyle!"
console.log( typeof greeting ); // "string"
如你所見糟秘,我們?cè)谝幌盗斜环g為字符串字面量的字符周圍使用了`..`
,但是${..}
形式中的任何表達(dá)式都將立即內(nèi)聯(lián)地被解析和求值球散。稱呼這樣的解析和求值的高大上名詞就是 插值(interpolation)(比模板要準(zhǔn)確多了)尿赚。
被插值的字符串字面量表達(dá)式的結(jié)果只是一個(gè)老式的普通字符串,賦值給變量greeting
蕉堰。
警告: typeof greeting == "string"
展示了為什么不將這些實(shí)體考慮為特殊的模板值很重要凌净,因?yàn)槟悴荒軐⑦@種字面量的未求值形式賦值給某些東西并復(fù)用它。`..`
字符串字面量在某種意義上更像是IIFE屋讶,因?yàn)樗詣?dòng)內(nèi)聯(lián)地被求值冰寻。`..`
字符串字面量的結(jié)果只不過是一個(gè)簡單的字符串。
插值型字符串字面量的一個(gè)真正的好處是他們?cè)试S被分割為多行:
var text =
`Now is the time for all good men
to come to the aid of their
country!`;
console.log( text );
// Now is the time for all good men
// to come to the aid of their
// country!
在插值型字符串字面量中的換行將會(huì)被保留在字符串值中皿渗。
除非在字面量值中作為明確的轉(zhuǎn)義序列出現(xiàn)斩芭,回車字符\r
(編碼點(diǎn)U+000D
)的值或者回車+換行序列\r\n
(編碼點(diǎn)U+000D
和U+000A
)的值都會(huì)被泛化為一個(gè)換行字符\n
(編碼點(diǎn)U+000A
)。但不要擔(dān)心乐疆;這種泛化很少見而且很可能僅會(huì)在你將文本拷貝粘貼到JS文件中時(shí)才會(huì)發(fā)生划乖。
插值表達(dá)式
在一個(gè)插值型字符串字面量中,任何合法的表達(dá)式都被允許出現(xiàn)在${..}
內(nèi)部挤土,包括函數(shù)調(diào)用琴庵,內(nèi)聯(lián)函數(shù)表達(dá)式調(diào)用,甚至是另一個(gè)插值型字符串字面量仰美!
考慮如下代碼:
function upper(s) {
return s.toUpperCase();
}
var who = "reader";
var text =
`A very ${upper( "warm" )} welcome
to all of you ${upper( `${who}s` )}!`;
console.log( text );
// A very WARM welcome
// to all of you READERS!
當(dāng)我們組合變量who
與字符串s
時(shí)迷殿, 相對(duì)于who + "s"
,這里的內(nèi)部插值型字符串字面量`${who}s`
更方便一些咖杂。有些情況下嵌套的插值型字符串字面量是有用的庆寺,但是如果你發(fā)現(xiàn)自己做這樣的事情太頻繁,或者發(fā)現(xiàn)你自己嵌套了好幾層時(shí)翰苫,你就要小心一些止邮。
如果確實(shí)有這樣情況这橙,你的字符串你值生產(chǎn)過程很可能可以從某些抽象中獲益。
警告: 作為一個(gè)忠告导披,使用這樣的新發(fā)現(xiàn)的力量時(shí)要非常小心你代碼的可讀性屈扎。就像默認(rèn)值表達(dá)式和解構(gòu)賦值表達(dá)式一樣,僅僅因?yàn)槟?能 做某些事情撩匕,并不意味著你 應(yīng)該 做這些事情鹰晨。在使用新的ES6技巧時(shí)千萬不要做過了頭,使你的代碼比你或者你的其他隊(duì)友聰明止毕。
表達(dá)式作用域
關(guān)于作用域的一個(gè)快速提醒是它用于解析表達(dá)式中的變量時(shí)模蜡。我早先提到過一個(gè)插值型字符串字面量與IIFE有些相像,事實(shí)上這也可以考慮為作用域行為的一種解釋扁凛。
考慮如下代碼:
function foo(str) {
var name = "foo";
console.log( str );
}
function bar() {
var name = "bar";
foo( `Hello from ${name}!` );
}
var name = "global";
bar(); // "Hello from bar!"
在函數(shù)bar()
內(nèi)部忍疾,字符串字面量`..`
被表達(dá)的那一刻,可供它查找的作用域發(fā)現(xiàn)變量的name
的值為"bar"
谨朝。既不是全局的name
也不是foo(..)
的name
卤妒。換句話說,一個(gè)插值型字符串字面量在它出現(xiàn)的地方是詞法作用域的字币,而不是任何方式的動(dòng)態(tài)作用域则披。
標(biāo)簽型模板字面量
再次為了合理性而重命名這個(gè)特性:標(biāo)簽型字符串字面量。
老實(shí)說洗出,這是一個(gè)ES6提供的更酷的特性士复。它可能看起來有點(diǎn)兒奇怪,而且也許一開始看起來一般不那么實(shí)用翩活。但一旦你花些時(shí)間在它上面阱洪,標(biāo)簽型字符串字面量的用處可能會(huì)令你驚訝。
例如:
function foo(strings, ...values) {
console.log( strings );
console.log( values );
}
var desc = "awesome";
foo`Everything is ${desc}!`;
// [ "Everything is ", "!"]
// [ "awesome" ]
讓我們花點(diǎn)兒時(shí)間考慮一下前面的代碼段中發(fā)生了什么隅茎。首先澄峰,跳出來的最刺眼的東西就是foo`Everything...`;
嫉沽。它看起來不像是任何我們?cè)?jīng)見過的東西辟犀。不是嗎?
它實(shí)質(zhì)上是一種不需要( .. )
的特殊函數(shù)調(diào)用绸硕。標(biāo)簽 —— 在字符串字面量`..`
之前的foo
部分 —— 是一個(gè)應(yīng)當(dāng)被調(diào)用的函數(shù)的值堂竟。實(shí)際上,它可以是返回函數(shù)的任何表達(dá)式玻佩,甚至是一個(gè)返回另一個(gè)函數(shù)的函數(shù)調(diào)用出嘹,就像:
function bar() {
return function foo(strings, ...values) {
console.log( strings );
console.log( values );
}
}
var desc = "awesome";
bar()`Everything is ${desc}!`;
// [ "Everything is ", "!"]
// [ "awesome" ]
但是當(dāng)作為一個(gè)字符串字面量的標(biāo)簽時(shí),函數(shù)foo(..)
被傳入了什么咬崔?
第一個(gè)參數(shù)值 —— 我們稱它為strings
—— 是一個(gè)所有普通字符串的數(shù)組(所有被插值的表達(dá)式之間的東西)税稼。我們?cè)?code>strings數(shù)組中得到兩個(gè)值:"Everything is "
和"!"
烦秩。
之后為了我們示例的方便,我們使用...
收集/剩余操作符(見本章早先的“擴(kuò)散/剩余”部分)將所有后續(xù)的參數(shù)值收集到一個(gè)稱為values
的數(shù)組中郎仆,雖說你本來當(dāng)然可以把它們留作參數(shù)strings
后面單獨(dú)的命名參數(shù)只祠。
被收集進(jìn)我們的values
數(shù)組中的參數(shù)值,就是在字符串字面量中發(fā)現(xiàn)的扰肌,已經(jīng)被求過值的插值表達(dá)式的結(jié)果抛寝。所以在我們的例子中values
里唯一的元素顯然就是awesome
。
你可以將這兩個(gè)數(shù)組考慮為:在values
中的值原本是你拼接在stings
的值之間的分隔符曙旭,而且如果你將所有的東西連接在一起盗舰,你就會(huì)得到完整的插值字符串值。
一個(gè)標(biāo)簽型字符串字面量像是一個(gè)在插值表達(dá)式被求值之后桂躏,但是在最終的字符串被編譯之前的處理步驟钻趋,允許你在從字面量中產(chǎn)生字符串的過程中進(jìn)行更多的控制。
一般來說剂习,一個(gè)字符串字面量標(biāo)簽函數(shù)(在前面的代碼段中是foo(..)
)應(yīng)當(dāng)計(jì)算一個(gè)恰當(dāng)?shù)淖址挡⒎祷厮妫阅憧梢允褂脴?biāo)簽型字符串字面量作為一個(gè)未打標(biāo)簽的字符串字面量來使用:
function tag(strings, ...values) {
return strings.reduce( function(s,v,idx){
return s + (idx > 0 ? values[idx-1] : "") + v;
}, "" );
}
var desc = "awesome";
var text = tag`Everything is ${desc}!`;
console.log( text ); // Everything is awesome!
在這個(gè)代碼段中,tag(..)
是一個(gè)直通操作进倍,因?yàn)樗粚?shí)施任何特殊的修改土至,而只是使用reduce(..)
來循環(huán)遍歷,并像一個(gè)未打標(biāo)簽的字符串字面量一樣猾昆,將strings
和values
拼接/穿插在一起陶因。
那么實(shí)際的用法是什么?有許多高級(jí)的用法超出了我們要在這里討論的范圍垂蜗。但這里有一個(gè)格式化美元數(shù)字的簡單想法(有些像基本的本地化):
function dollabillsyall(strings, ...values) {
return strings.reduce( function(s,v,idx){
if (idx > 0) {
if (typeof values[idx-1] == "number") {
// 看楷扬,也使用插值性字符串字面量!
s += `$${values[idx-1].toFixed( 2 )}`;
}
else {
s += values[idx-1];
}
}
return s + v;
}, "" );
}
var amt1 = 11.99,
amt2 = amt1 * 1.08,
name = "Kyle";
var text = dollabillsyall
`Thanks for your purchase, ${name}! Your
product cost was ${amt1}, which with tax
comes out to ${amt2}.`
console.log( text );
// Thanks for your purchase, Kyle! Your
// product cost was $11.99, which with tax
// comes out to $12.95.
如果在values
數(shù)組中遇到一個(gè)number
值贴见,我們就在它前面放一個(gè)"$"
并用toFixed(2)
將它格式化為小數(shù)點(diǎn)后兩位有效烘苹。否則,我們就不碰這個(gè)值而讓它直通過去片部。
原始字符串
在前一個(gè)代碼段中镣衡,我們的標(biāo)簽函數(shù)接受的第一個(gè)參數(shù)值稱為strings
,是一個(gè)數(shù)組档悠。但是有一點(diǎn)兒額外的數(shù)據(jù)被包含了進(jìn)來:所有字符串的原始未處理版本廊鸥。你可以使用.raw
屬性訪問這些原始字符串值,就像這樣:
function showraw(strings, ...values) {
console.log( strings );
console.log( strings.raw );
}
showraw`Hello\nWorld`;
// [ "Hello
// World" ]
// [ "Hello\nWorld" ]
原始版本的值保留了原始的轉(zhuǎn)義序列\n
(\
和n
是兩個(gè)分離的字符)辖所,但處理過的版本認(rèn)為它是一個(gè)單獨(dú)的換行符惰说。但是,早先提到的行終結(jié)符泛化操作缘回,是對(duì)兩個(gè)值都實(shí)施的吆视。
ES6帶來了一個(gè)內(nèi)建函數(shù)典挑,它可以用做字符串字面量的標(biāo)簽:String.raw(..)
。它簡單地直通strings
值的原始版本:
console.log( `Hello\nWorld` );
// Hello
// World
console.log( String.raw`Hello\nWorld` );
// Hello\nWorld
String.raw`Hello\nWorld`.length;
// 12
字符串字面量標(biāo)簽的其他用法包括國際化啦吧,本地化搔弄,和許多其他的特殊處理。
箭頭函數(shù)
我們?cè)诒菊略缦冉佑|了函數(shù)中this
綁定的復(fù)雜性丰滑,而且在本系列的 this與對(duì)象原型 中也以相當(dāng)?shù)钠v解過顾犹。理解普通函數(shù)中基于this
的編程帶來的挫折是很重要的,因?yàn)檫@是ES6的新=>
箭頭函數(shù)的主要?jiǎng)訖C(jī)褒墨。
作為與普通函數(shù)的比較炫刷,我們首先來展示一下箭頭函數(shù)看起來什么樣:
function foo(x,y) {
return x + y;
}
// 對(duì)比
var foo = (x,y) => x + y;
箭頭函數(shù)的定義由一個(gè)參數(shù)列表(零個(gè)或多個(gè)參數(shù),如果參數(shù)不是只有一個(gè)郁妈,需要有一個(gè)( .. )
包圍這些參數(shù))組成浑玛,緊跟著是一個(gè)=>
符號(hào),然后是一個(gè)函數(shù)體噩咪。
所以顾彰,在前面的代碼段中,箭頭函數(shù)只是(x,y) => x + y
這一部分胃碾,而這個(gè)函數(shù)的引用剛好被賦值給了變量foo
涨享。
函數(shù)體僅在含有多于一個(gè)表達(dá)式,或者由一個(gè)非表達(dá)式語句組成時(shí)才需要用{ .. }
括起來仆百。如果僅含有一個(gè)表達(dá)式厕隧,而且你省略了外圍的{ .. }
,那么在這個(gè)表達(dá)式前面就會(huì)有一個(gè)隱含的return
俄周,就像前面的代碼段中展示的那樣吁讨。
這里是一些其他種類的箭頭函數(shù):
var f1 = () => 12;
var f2 = x => x * 2;
var f3 = (x,y) => {
var z = x * 2 + y;
y++;
x *= 3;
return (x + y + z) / 2;
};
箭頭函數(shù) 總是 函數(shù)表達(dá)式;不存在箭頭函數(shù)聲明峦朗。而且很明顯它們都是匿名函數(shù)表達(dá)式 —— 它們沒有可以用于遞歸或者事件綁定/解除的命名引用 —— 但在第七章的“函數(shù)名”中將會(huì)講解為了調(diào)試的目的而存在的ES6函數(shù)名接口規(guī)則建丧。
注意: 普通函數(shù)參數(shù)的所有功能對(duì)于箭頭函數(shù)都是可用的,包括默認(rèn)值波势,解構(gòu)翎朱,剩余參數(shù),等等艰亮。
箭頭函數(shù)擁有漂亮闭翩,簡短的語法,這使得它們?cè)诒砻嫔峡雌饋韺?duì)于編寫簡潔代碼很有吸引力迄埃。確實(shí),幾乎所有關(guān)于ES6的文獻(xiàn)(除了這個(gè)系列中的書目)看起來都立即將箭頭函數(shù)僅僅認(rèn)作“新函數(shù)”兑障。
這說明在關(guān)于箭頭函數(shù)的討論中侄非,幾乎所有的例子都是簡短的單語句工具蕉汪,比如那些作為回調(diào)傳遞給各種工具的箭頭函數(shù)。例如:
var a = [1,2,3,4,5];
a = a.map( v => v * 2 );
console.log( a ); // [2,4,6,8,10]
在這些情況下逞怨,你的內(nèi)聯(lián)函數(shù)表達(dá)式很適合這種在一個(gè)單獨(dú)語句中快速計(jì)算并返回結(jié)果的模式者疤,對(duì)于更繁冗的function
關(guān)鍵字和語法來說箭頭函數(shù)確實(shí)看起來是一個(gè)很吸人,而且輕量的替代品叠赦。
大多數(shù)人看著這樣簡潔的例子都傾向于發(fā)出“哦……驹马!啊……!”的感嘆除秀,就像我想象中你剛剛做的那樣糯累!
然而我要警示你的是,在我看來册踩,使用箭頭函數(shù)的語法代替普通的泳姐,多語句函數(shù),特別是那些可以被自然地表達(dá)為函數(shù)聲明的函數(shù)暂吉,是某種誤用胖秒。
回憶本章早前的字符串字面量標(biāo)簽函數(shù)dollabillsyall(..)
—— 讓我們將它改為使用=>
語法:
var dollabillsyall = (strings, ...values) =>
strings.reduce( (s,v,idx) => {
if (idx > 0) {
if (typeof values[idx-1] == "number") {
// look, also using interpolated
// string literals!
s += `$${values[idx-1].toFixed( 2 )}`;
}
else {
s += values[idx-1];
}
}
return s + v;
}, "" );
在這個(gè)例子中,我做的唯一修改是刪除了function
慕的,return
阎肝,和一些{ .. }
,然后插入了=>
和一個(gè)var
肮街。這是對(duì)代碼可讀性的重大改進(jìn)嗎盗痒?呵呵。
實(shí)際上我會(huì)爭論低散,缺少return
和外部的{ .. }
在某種程度上模糊了這樣的事實(shí):reduce(..)
調(diào)用是函數(shù)dollabillsyall(..)
中唯一的語句俯邓,而且它的結(jié)果是這個(gè)調(diào)用的預(yù)期結(jié)果。另外熔号,那些受過訓(xùn)練而習(xí)慣于在代碼中搜索function
關(guān)鍵字來尋找作用域邊界的眼睛稽鞭,現(xiàn)在需要搜索=>
標(biāo)志,在密集的代碼中這絕對(duì)會(huì)更加困難引镊。
雖然不是一個(gè)硬性規(guī)則朦蕴,但是我要說從=>
箭頭函數(shù)轉(zhuǎn)換得來的可讀性,與被轉(zhuǎn)換的函數(shù)長度成反比弟头。函數(shù)越長吩抓,=>
能幫的忙越少;函數(shù)越短赴恨,=>
的閃光之處就越多疹娶。
我覺得這樣做更明智也更合理:在你需要短的內(nèi)聯(lián)函數(shù)表達(dá)式的地方采用=>
,但保持你的一般長度的主函數(shù)原封不動(dòng)伦连。
不只是簡短的語法雨饺,而是this
曾經(jīng)集中在=>
上的大多數(shù)注意力都是它通過在你的代碼中除去function
钳垮,return
,和{ .. }
來節(jié)省那些寶貴的擊鍵额港。
但是至此我們一直忽略了一個(gè)重要的細(xì)節(jié)饺窿。我在這一節(jié)最開始的時(shí)候說過,=>
函數(shù)與this
綁定行為密切相關(guān)移斩。事實(shí)上肚医,=>
箭頭函數(shù) 主要的設(shè)計(jì)目的 就是以一種特定的方式改變this
的行為,解決在this
敏感的編碼中的一個(gè)痛點(diǎn)向瓷。
節(jié)省擊鍵是掩人耳目的東西肠套,至多是一個(gè)誤導(dǎo)人的配角。
讓我們重溫本章早前的另一個(gè)例子:
var controller = {
makeRequest: function(..){
var self = this;
btn.addEventListener( "click", function(){
// ..
self.makeRequest(..);
}, false );
}
};
我們使用了黑科技var self = this
风罩,然后引用了self.makeRequest(..)
糠排,因?yàn)樵谖覀儌鬟f給addEventListener(..)
的回調(diào)函數(shù)內(nèi)部,this
綁定將與makeRequest(..)
本身中的this
綁定不同超升。換句話說入宦,因?yàn)?code>this綁定是動(dòng)態(tài)的,我們通過self
變量退回到了可預(yù)測(cè)的詞法作用域室琢。
在這其中我們終于可以看到=>
箭頭函數(shù)主要的設(shè)計(jì)特性了乾闰。在箭頭函數(shù)內(nèi)部,this
綁定不是動(dòng)態(tài)的盈滴,而是詞法的涯肩。在前一個(gè)代碼段中,如果我們?cè)诨卣{(diào)里使用一個(gè)箭頭函數(shù)巢钓,this
將會(huì)不出所料地成為我們希望它成為的東西病苗。
考慮如下代碼:
var controller = {
makeRequest: function(..){
btn.addEventListener( "click", () => {
// ..
this.makeRequest(..);
}, false );
}
};
前面代碼段的箭頭函數(shù)中的詞法this
現(xiàn)在指向的值與外圍的makeRequest(..)
函數(shù)相同。換句話說症汹,=>
是var self = this
的語法上的替代品硫朦。
在var self = this
(或者,另一種選擇是背镇,.bind(this)
調(diào)用)通骋д梗可以幫忙的情況下,=>
箭頭函數(shù)是一個(gè)基于相同原則的很好的替代操作瞒斩。聽起來很棒破婆,是吧?
沒那么簡單胸囱。
如果=>
取代var self = this
或.bind(this)
可以工作祷舀,那么猜猜=>
用于一個(gè) 不需要 var self = this
就能工作的this
敏感的函數(shù)會(huì)發(fā)生么?你可能會(huì)猜到它將會(huì)把事情搞砸。沒錯(cuò)蔑鹦。
考慮如下代碼:
var controller = {
makeRequest: (..) => {
// ..
this.helper(..);
},
helper: (..) => {
// ..
}
};
controller.makeRequest(..);
雖然我們以controller.makeRequest(..)
的方式進(jìn)行了調(diào)用夺克,但是this.helper
引用失敗了箕宙,因?yàn)檫@里的this
沒有像平常那樣指向controller
嚎朽。那么它指向哪里?它通過詞法繼承了外圍的作用域中的this
柬帕。在前面的代碼段中哟忍,它是全局作用域,this
指向了全局作用域陷寝。呃锅很。
除了詞法的this
以外,箭頭函數(shù)還擁有詞法的arguments
—— 它們沒有自己的arguments
數(shù)組凤跑,而是從它們的上層繼承下來 —— 同樣還有詞法的super
和new.target
(參見第三章的“類”)爆安。
所以,關(guān)于=>
在什么情況下合適或不合適仔引,我們現(xiàn)在可以推論出一組更加微妙的規(guī)則:
- 如果你有一個(gè)簡短的扔仓,單語句內(nèi)聯(lián)函數(shù)表達(dá)式,它唯一的語句是某個(gè)計(jì)算后的值的
return
語句咖耘,并且 這個(gè)函數(shù)沒有在它內(nèi)部制造一個(gè)this
引用翘簇,并且 沒有自引用(遞歸,事件綁定/解除)儿倒,并且 你合理地預(yù)期這個(gè)函數(shù)絕不會(huì)變得需要this
引用或自引用版保,那么你就可能安全地將它重構(gòu)為一個(gè)=>
箭頭函數(shù)蚕泽。 - 如果你有一個(gè)內(nèi)部函數(shù)表達(dá)式吊骤,它依賴于外圍函數(shù)的
var self = this
黑科技或者.bind(this)
調(diào)用來確保正確的this
綁定丽惭,那么這個(gè)內(nèi)部函數(shù)表達(dá)式就可能安全地變?yōu)橐粋€(gè)=>
箭頭函數(shù)行您。 - 如果你有一個(gè)內(nèi)部函數(shù)表達(dá)式瞧挤,它依賴于外圍函數(shù)的類似于
var args = Array.prototype.slice.call(arguments)
這樣的東西來制造一個(gè)arguments
的詞法拷貝龄砰,那么這個(gè)內(nèi)部函數(shù)就可能安全地變?yōu)橐粋€(gè)=>
箭頭函數(shù)躺率。 - 對(duì)于其他的所有東西 —— 普通函數(shù)聲明恋捆,較長的多語句函數(shù)表達(dá)式溉瓶,需要詞法名稱標(biāo)識(shí)符進(jìn)行自引用(遞歸等)的函數(shù)急鳄,和任何其他不符合前述性質(zhì)的函數(shù) —— 你就可能應(yīng)當(dāng)避免
=>
函數(shù)語法。
底線:=>
與this
堰酿,arguments
疾宏,和super
的詞法綁定有關(guān)。它們是ES6為了修正一些常見的問題而被有意設(shè)計(jì)的特性触创,而不是為了修正bug坎藐,怪異的代碼,或者錯(cuò)誤。
不要相信任何說=>
主要是岩馍,或者幾乎是碉咆,為了減少幾下?lián)翩I的炒作。無論你是省下還是浪費(fèi)了這幾下?lián)翩I蛀恩,你都應(yīng)當(dāng)確切地知道你打入的每個(gè)字母是為了做什么疫铜。
提示: 如果你有一個(gè)函數(shù),由于上述各種清楚的原因而不適合成為一個(gè)=>
箭頭函數(shù)双谆,但同時(shí)它又被聲明為一個(gè)對(duì)象字面量的一部分壳咕,那么回想一下本章早先的“簡約方法”,它有簡短函數(shù)語法的另一種選擇顽馋。
對(duì)于如何/為何選用一個(gè)箭頭函數(shù)谓厘,如果你喜歡一個(gè)可視化的決策圖的話:
for..of
循環(huán)
伴隨著我們熟知的JavaScriptfor
和for..in
循環(huán),ES6增加了一個(gè)for..of
循環(huán)寸谜,它循環(huán)遍歷一組由一個(gè) 迭代器(iterator) 產(chǎn)生的值竟稳。
你使用for..of
循環(huán)遍歷的值必須是一個(gè) 可迭代對(duì)象(iterable),或者它必須是一個(gè)可以被強(qiáng)制轉(zhuǎn)換/封箱(參見本系列的 類型與文法)為一個(gè)可迭代對(duì)象的值熊痴。一個(gè)可迭代對(duì)象只不過是一個(gè)可以生成迭代器的對(duì)象他爸,然后由循環(huán)使用這個(gè)迭代器。
讓我們比較for..of
與for..in
來展示它們的區(qū)別:
var a = ["a","b","c","d","e"];
for (var idx in a) {
console.log( idx );
}
// 0 1 2 3 4
for (var val of a) {
console.log( val );
}
// "a" "b" "c" "d" "e"
如你所見愁拭,for..in
循環(huán)遍歷數(shù)組a
中的鍵/索引讲逛,而for.of
循環(huán)遍歷a
中的值。
這是前面代碼段中for..of
的前ES6版本:
var a = ["a","b","c","d","e"],
k = Object.keys( a );
for (var val, i = 0; i < k.length; i++) {
val = a[ k[i] ];
console.log( val );
}
// "a" "b" "c" "d" "e"
而這是一個(gè)ES6版本的非for..of
等價(jià)物岭埠,它同時(shí)展示了手動(dòng)迭代一個(gè)迭代器(見第三章的“迭代器”):
var a = ["a","b","c","d","e"];
for (var val, ret, it = a[Symbol.iterator]();
(ret = it.next()) && !ret.done;
) {
val = ret.value;
console.log( val );
}
// "a" "b" "c" "d" "e"
在幕后盏混,for..of
循環(huán)向可迭代對(duì)象要來一個(gè)迭代器(使用內(nèi)建的Symbol.iterator
;參見第七章的“通用Symbols”)惜论,然后反復(fù)調(diào)用這個(gè)迭代器并將它產(chǎn)生的值賦值給循環(huán)迭代的變量许赃。
在JavaScript標(biāo)準(zhǔn)的內(nèi)建值中,默認(rèn)為可迭代對(duì)象的(或提供可迭代能力的)有:
- 數(shù)組
- 字符串
- Generators(見第三章)
- 集合/類型化數(shù)組(見第五章)
警告: 普通對(duì)象默認(rèn)是不適用于for..of
循環(huán)的馆类。因?yàn)樗麄儧]有默認(rèn)的迭代器混聊,這是有意為之的,不是一個(gè)錯(cuò)誤乾巧。但是句喜,我們不會(huì)進(jìn)一步探究這其中微妙的原因。在第三章的“迭代器”中沟于,我們將看到如何為我們自己的對(duì)象定義迭代器咳胃,這允許for..of
遍歷任何對(duì)象來得到我們定義的一組值。
這是如何遍歷一個(gè)基本類型的字符串中的字符:
for (var c of "hello") {
console.log( c );
}
// "h" "e" "l" "l" "o"
基本類型字符串"hello"
被強(qiáng)制轉(zhuǎn)換/封箱為等價(jià)的String
對(duì)象包裝器旷太,它是默認(rèn)就是一個(gè)可迭代對(duì)象展懈。
在for (XYZ of ABC)..
中销睁,XYZ
子句既可以是一個(gè)賦值表達(dá)式也可以是一個(gè)聲明,這與for
和for..in
中相同的子句一模一樣存崖。所以你可以做這樣的事情:
var o = {};
for (o.a of [1,2,3]) {
console.log( o.a );
}
// 1 2 3
for ({x: o.a} of [ {x: 1}, {x: 2}, {x: 3} ]) {
console.log( o.a );
}
// 1 2 3
與其他的循環(huán)一樣冻记,使用break
,continue
来惧,return
(如果是在一個(gè)函數(shù)中)冗栗,以及拋出異常,for..of
循環(huán)可以被提前終止违寞。在任何這些情況下贞瞒,迭代器的return(..)
函數(shù)(如果存在的話)都會(huì)被自動(dòng)調(diào)用偶房,以便讓迭代器進(jìn)行必要的清理工作趁曼。
注意: 可迭代對(duì)象與迭代器的完整內(nèi)容參見第三章的“迭代器”。
正則表達(dá)式擴(kuò)展
讓我們承認(rèn)吧:長久以來在JS中正則表達(dá)式都沒怎么改變過棕洋。所以一件很棒的事情是挡闰,在ES6中它們終于學(xué)會(huì)了一些新招數(shù)。我們將在這里簡要地講解一下新增的功能掰盘,但是正則表達(dá)式整體的話題是如此厚重摄悯,以至于如果你需要復(fù)習(xí)一下的話你需要找一些關(guān)于它的專門章節(jié)/書籍(有許多!)愧捕。
Unicode標(biāo)志
我們將在本章稍后的“Unicode”一節(jié)中講解關(guān)于Unicode的更多細(xì)節(jié)奢驯。在此,我們將僅僅簡要地看一下ES6+正則表達(dá)式的新u
標(biāo)志次绘,它使這個(gè)正則表達(dá)式的Unicode匹配成為可能瘪阁。
JavaScript字符串通常被解釋為16位字符的序列,它們對(duì)應(yīng)于 基本多文種平面(Basic Multilingual Plane (BMP)) (http://en.wikipedia.org/wiki/Plane_%28Unicode%29)中的字符邮偎。但是有許多UTF-16字符在這個(gè)范圍以外管跺,而且字符串可能含有這些多字節(jié)字符。
在ES6之前禾进,正則表達(dá)式只能基于BMP字符進(jìn)行匹配豁跑,這意味著在匹配時(shí)那些擴(kuò)展字符被看作是兩個(gè)分離的字符。這通常不理想泻云。
所以艇拍,在ES6中,u
標(biāo)志告訴正則表達(dá)式使用Unicode(UTF-16)字符的解釋方式來處理字符串宠纯,這樣一來一個(gè)擴(kuò)展的字符將作為一個(gè)單獨(dú)的實(shí)體被匹配卸夕。
警告: 盡管名字的暗示是這樣,但是“UTF-16”并不嚴(yán)格地意味著16位≌鹘罚現(xiàn)代的Unicode使用21位娇哆,而且像UTF-8和UTF-16這樣的標(biāo)準(zhǔn)大體上是指有多少位用于表示一個(gè)字符。
一個(gè)例子(直接從ES6語言規(guī)范中拿來的): ?? (G大調(diào)音樂符號(hào))是Unicode代碼點(diǎn)U+1D11E(0x1D11E)。
如果這個(gè)字符出現(xiàn)在一個(gè)正則表達(dá)式范例中(比如/??/
)碍讨,標(biāo)準(zhǔn)的BMP解釋方式將認(rèn)為它是需要被匹配的兩個(gè)字符(0xD834和0xDD1E)治力。但是ES6新的Unicode敏感模式意味著/??/u
(或者Unicode的轉(zhuǎn)義形式/\u{1D11E}/u
)將會(huì)把"??"
作為一個(gè)單獨(dú)的字符在一個(gè)字符串中進(jìn)行匹配。
你可能想知道為什么這很重要勃黍。在非Unicode的BMP模式下宵统,這個(gè)正則表達(dá)式范例被看作兩個(gè)分離的字符,但它仍然可以在一個(gè)含有"??"
字符的字符串中找到匹配覆获,如果你試一下就會(huì)看到:
/??/.test( "??-clef" ); // true
重要的是匹配的長度马澈。例如:
/^.-clef/ .test( "??-clef" ); // false
/^.-clef/u.test( "??-clef" ); // true
這個(gè)范例中的^.-clef
說要在普通的"-clef"
文本前面只匹配一個(gè)單獨(dú)的字符。在標(biāo)準(zhǔn)的BMP模式下弄息,這個(gè)匹配會(huì)失斎唷(因?yàn)槭莾蓚€(gè)字符),但是在Unicode模式標(biāo)志位u
打開的情況下摹量,這個(gè)匹配會(huì)成功(一個(gè)字符)涤伐。
另外一個(gè)重要的注意點(diǎn)是,u
使像+
和*
這樣的量詞實(shí)施于作為一個(gè)單獨(dú)字符的整個(gè)Unicode代碼點(diǎn)缨称,而不僅僅是字符的 低端替代符(也就是符號(hào)最右邊的一半)凝果。對(duì)于出現(xiàn)在字符類中的Unicode字符也是一樣,比如/[??-??]/u
睦尽。
注意: 還有許多關(guān)于u
在正則表達(dá)式中行為的細(xì)節(jié)器净,對(duì)此Mathias Bynens(https://twitter.com/mathias)撰寫了大量的作品(https://mathiasbynens.be/notes/es6-unicode-regex)。