上一篇《讀 ES6 — 字符串赞庶、數(shù)值你辣、正則的擴(kuò)展》 將新增 API
做了一些梳理,隔離了一些復(fù)雜而低頻的知識(shí)點(diǎn)尘执,從有限的篇幅分析舍哄,可以窺探到 JavaScript 內(nèi)部代碼組織更趨于合理、而本身 API
則變得更加易用和強(qiáng)大誊锭。
本篇繼續(xù)沿著上篇的分析方向表悬,來整理下 ES6
基本類型中的數(shù)組
、對(duì)象
新增特性丧靡。(函數(shù)
稍特別蟆沫,將單獨(dú)來梳理)
因?yàn)樗鼈冊(cè)?JavaScript 語(yǔ)言中的地位無比重要,無論是 ES5
還是 ES6
中都應(yīng)該優(yōu)先掌握它們温治。本篇著重梳理 ES6
相對(duì)于 ES5
的增量知識(shí)饭庞,但是有些增量使用起來仍然很費(fèi)勁,用到一個(gè)知識(shí)點(diǎn)恐怕先得弄清一大片才行——好比對(duì)朋友說了一個(gè)謊(大家應(yīng)該都有這樣干過吧......)熬荆,就得用好幾個(gè)謊才能圓回來舟山。
比如當(dāng)你開始用起 Array.from()
你可能會(huì)遇到 數(shù)組的空位的概念
,然后猛然一驚還有這事,那豈不是我曾經(jīng)有段程序用錯(cuò)了埋了 bug? 比如函數(shù)的尾調(diào)用優(yōu)化累盗,是什么鬼呀寒矿?還比如對(duì)象 Object
搞了一大堆 getOwnPropertyDescriptor()
、setPrototypeOf()
若债、getPrototypeOf()
這些看著 prototype
就頭疼的方法......
確實(shí)符相,這些東西有點(diǎn)繁瑣,不過沒關(guān)系蠢琳,如之前的理念我們暫且先把它們隔離起來啊终,關(guān)在鐵籠子里,等咱先打完小怪傲须,在來收拾它們蓝牲。(怎么感覺和一款叫 “軒轅劍...” 的游戲劇情有點(diǎn)相似)。另外躏碳,就是很多 API
可能初期有點(diǎn)抗拒搞旭,只要用起來了就覺得太 TM 順手絲滑了。
數(shù)組的擴(kuò)展
一如既往菇绵,啰嗦完畢開始進(jìn)入正題肄渗。數(shù)組新增的特性有:
- 新增方法(包括在命名空間
Array
上和數(shù)組實(shí)例上) - spread 運(yùn)算符
- 數(shù)組空位的概念
新增方法
數(shù)組實(shí)例的新增方法:
在數(shù)組內(nèi)自我覆蓋: copyWithin()
見好就收: find() 和 findIndex()
是否包含了某項(xiàng): includes()
自我填充: fill()
花式遍歷: entries()、keys() 和 values()
以及命名空間 Array
上的方法:
數(shù)組產(chǎn)自近親: Array.from()
化零為整: Array.of()
新增方法都很簡(jiǎn)單咬最,和上一篇總結(jié)的特征規(guī)律是相似的翎嫡。比如封裝高頻的寫法:
// ES5 數(shù)組是否存在某項(xiàng)
var isExist = [1, NaN, 3, 6, 8].indexOf(6) !== -1;
// => 這樣寫總感覺不夠直觀
// => 經(jīng)常還要查 `indexOf` 的 `O` 是大寫還是小寫有木有!
// => 因?yàn)?`typeof` 的是小寫永乌!
// 莫名其妙 `NaN` 的存在性判斷不出來
[1, NaN, 3, 6, 8].indexOf(NaN);
// => -1
// ES6 數(shù)組是否存在某項(xiàng)就沒有上述的問題
let isExist = [1, NaN, 3, 6, 8].includes(NaN)
另外比較重要的特征就是:
- 幾乎都是純函數(shù)
- 更偏向聲明式編程
- API 的行為統(tǒng)一明確
首先惑申,為什么說幾乎都是 “純函數(shù)”(相關(guān)知識(shí)若有需要,可以閱讀本人的另一篇專題文章《走向 JavaScript 函數(shù)式編程》)翅雏? 新增的方法都滿足相同輸入恒有相同輸出圈驼,但是還有一點(diǎn)點(diǎn) “副作用”,就是改變了外部變量即當(dāng)前數(shù)組本身望几。但總的來說绩脆,比起 ES5
,已向 “確定性” API
邁出了一大步橄抹。
于是靴迫,將稍有 “副作用” 的方法改成 “純函數(shù)”,是非常容易的:
// 帶副作用的 fill 方法會(huì)改變數(shù)組 arr
arr.fill(val, start, end);
// 很容易封裝成純函數(shù)楼誓,并且實(shí)現(xiàn)一定的柯里化
function fill(arr) {
let inner_arr = arr.slice(0);
return function (val, start, end) {
return inner_arr.fill(val, start, end);
}
}
其次玉锌,偏向于聲明式編程。ES6
看起來是要圍剿 for
循環(huán)疟羹,讓它們少出頭露面的辦法就是提供新的 API
來隱藏它們主守。
比如禀倔,在一次 “尋找 NaN
” 的活動(dòng)中,這樣的調(diào)用是不是更 “聲明式”丸逸,更絲滑順暢蹋艺?
//在數(shù)組中尋找 NaN
let index = [1, 2, 3, NaN, 6, 8].findIndex(Number.isNaN);
// => 3
let item = [1, 2, 3, NaN, 6, 8].find(Number.isNaN);
// => NaN
至于統(tǒng)一而確定的行為剃袍,在上例中 includes
以及 Array.from()
都有體現(xiàn)黄刚。對(duì)于一個(gè)重量級(jí)的語(yǔ)言,尤其是欲意 “征服全宇宙” 的 JavaScript 來說民效,定當(dāng)以極為挑剔的眼光審視它憔维,而 ES6
已經(jīng)做了很多。
spread 運(yùn)算符
spread 運(yùn)算符(擴(kuò)展運(yùn)算符)是三個(gè)點(diǎn)(...
)畏邢。這個(gè)新增項(xiàng)非常能讓人接受业扒,而且它已經(jīng)蔓延到了除數(shù)字本身外的 函數(shù)參數(shù)
(類似數(shù)組)、對(duì)象
舒萎、字符串
等等類型上程储。
以一個(gè)簡(jiǎn)單的例子,看看用 ...
書寫帶來的良好的閱讀體驗(yàn):
// 合并數(shù)組臂寝,通過 concat() 連接起來
var compose = first_arr.concat(second_arr).concat(third_arr);
//毫無雜質(zhì)的 ...
let compose = [...first_arr, ...second_arr, ...third_arr];
數(shù)組空位
最后數(shù)組的空位概念章鲤,本文不打算去說明。知識(shí)點(diǎn)本身比較簡(jiǎn)單咆贬,但是牽扯到太多的驗(yàn)證,比較難梳理,感覺像是揮不走的蒼蠅认罩。所以衫冻,最好的辦法是在編程實(shí)踐中再去 “拍” 它。
對(duì)象的擴(kuò)展
其實(shí)對(duì)象的擴(kuò)展并不復(fù)雜眷蜈,歸結(jié)起來差不多以下內(nèi)容:
- 為了更好的
賦值運(yùn)算
- 屬性的簡(jiǎn)寫
- 擴(kuò)展運(yùn)算符
- Object 命名空間的新增方法
- 常用方法
- 對(duì)象的 prototype 方法
- 屬性的描述方法
為了更好的賦值運(yùn)算
屬性的簡(jiǎn)寫沪哺、擴(kuò)展運(yùn)算符以及與此緊密相關(guān)的解構(gòu)賦值,可以說為舊的 JavaScript 開創(chuàng)了一批新的賦值運(yùn)算
方式酌儒,讓 賦值運(yùn)算
擺脫了一板一眼的 =
號(hào)運(yùn)算的方式辜妓。
var lang = {name: 'ECMA2015', shortName: 'ES6'};
//ES5 寫法比較冗余
var name = lang.name;
var shortName = lang.shortName;
//ES6 寫法更加簡(jiǎn)明
let {name, shortName} = lang;
對(duì)一個(gè) ES6
模塊的導(dǎo)入 (import
) 和導(dǎo)出 (export
) ,也能充分凸顯這種 賦值運(yùn)算
的便利性今豆。
// a.js
const version = '1.0.0';
let fn1 = () => {};
let fn2 = () => {};
export { version, fn1, fn2 }; //注:為了節(jié)約空間嫌拣,就寫成一行了
//b.js
import { version, fn1 } from './a';
// 如果不能解構(gòu)賦值
import a from './a';
let version = a.verion;
let fn1 = a.fn1;
屬性的簡(jiǎn)寫
上述 賦值運(yùn)算
,和一個(gè)符號(hào)有關(guān)呆躲,那就是 ES6
的大括號(hào){}
异逐,與 ES5
不同的是,ES6
的{}
不僅能開辟一塊作用域插掂,又能進(jìn)行 模式匹配
運(yùn)算灰瞻,有如自帶魔法一般腥例。
先來看看一個(gè)有意思的例子:將任意一個(gè)變量變成對(duì)象。
// ES5 需要獲取形參名
function var2obj(x) {
var obj = {};
var str_fn = var2obk.toString();
var key = str_fn.split(')')[0].split('(')[1];
obj[key] = x;
return obj;
}
var x = 'unkown';
var myObj = var2obj(x);
// => {x: 'unkown'};
更為完整的情形酝润,請(qǐng)參考 這里 燎竖。但在 ES6
的 {}
眼里,完全是另一番景象要销。甚至可以通過這個(gè)方式构回,輕易的獲取到所有形參名。(此處不去延伸)
function var2obj (x) {
return {x};
}
var x = 'unkown';
var myObj = var2obj(x);
// => {x: 'unkown'};
let y = 'yes';
var anotherObj = var2obj(y);
// => {y: 'yes'};
大括號(hào) {}
自帶運(yùn)算魔法疏咐。解析時(shí)纤掸,能將 {x}
一分為二,自動(dòng)展開為 {x: x}
浑塞。反之借跪,將對(duì)象的鍵值合成一個(gè)變量項(xiàng),寫在 {}
中酌壕,就是屬性的簡(jiǎn)寫掏愁。
// 簡(jiǎn)寫形式
let {name, age} = {name: 'jeremy', age: 18};
// 展開形式
let {name: name, age: age} = {name: 'jeremy', age: 18};
簡(jiǎn)寫對(duì)象的解構(gòu)賦值,可以看做是先轉(zhuǎn)化成上述的 “展開形式” 卵牍,然后再開始匹配賦值的果港。
因此,解構(gòu)賦值 =
號(hào)左邊的任何鍵名辽慕,都必須來自 =
號(hào)右邊對(duì)象中的某個(gè)元素京腥,否則將無法識(shí)別(undefined
)。換句話說溅蛉,=
號(hào)右邊對(duì)象能夠訪問到的屬性公浪,都是可以被解構(gòu)和賦值給 =
號(hào)左邊的,包括它原型鏈上的屬性船侧。
function Corder (name) {
this.name = name;
}
Corder.prototype.age = 18;
let {name, age} = new Corder('jeremy');
// => age 18
擴(kuò)展運(yùn)算符
對(duì)象也有擴(kuò)展運(yùn)算符 (...
)欠气,和數(shù)組的是類似的,簡(jiǎn)單理解就是剝離了一層大括號(hào) {}
镜撩,將對(duì)象鍵值對(duì)直接暴露出來预柒。
擴(kuò)展運(yùn)算并不難,但是有一個(gè)和解構(gòu)賦值不同的特征袁梗,是它不會(huì)將原型鏈上的屬性宜鸯、方法暴露出來。準(zhǔn)確的說遮怜,擴(kuò)展運(yùn)算符是取出對(duì)象自身(不包括其原型鏈上)的所有可遍歷屬性淋袖。
let {...aCoder} = new Corder('jeremy');
console.log(aCoder);
// => {name: ''jeremy'}
let aCoder = Object.assign({}, new Corder('jeremy'));
// => {name: ''jeremy'}
可見,...
和 Object.assign
都沒有將原型鏈上的 age
取出來锯梁。
對(duì)了即碗,這里提到了 取出對(duì)象自身(不包括其原型鏈上)的所有可遍歷屬性
焰情,不禁腦袋中又蹦出一個(gè)問題:到底哪些運(yùn)算或方法,只獲取到對(duì)象自身的可遍歷的屬性剥懒?又有哪些是可以獲取到對(duì)象原型鏈上的屬性呢内舟?
面對(duì) ES6
不勝枚舉的新增特性,信息量的暴增初橘,往往讓人煩躁不堪验游。情緒性的鄙夷油然而生:看吧,就為了解決些小問題壁却,卻弄出這么一大堆東西來批狱,有意思嗎裸准!
Object 的新增方法
信息量暴增展东,確有其事。不過本文意在梳理炒俱,解決的問題就是從這些繁雜的信息中盐肃,提取容易的、有利的為自己所用权悟,其他晦澀麻煩的暫且一并鎖在鐵籠子里砸王。
從總體上看,ES6
的新增的特性并非沒有瑕疵(筆者實(shí)際編程中也曾遇到過峦阁,以后的篇幅有機(jī)會(huì)再提)谦铃,而且很多粒度很小,辨識(shí)起來的確很麻煩榔昔。但單單從剛才的提問來說驹闰,遍歷自身屬性還是原型鏈屬性,ES6
并沒有給我們制造麻煩撒会。
第一類在 Object
命名空間上新增方法:
- Object.assign(target[, source1, source2, ...])
- Object.keys(obj)
- Object.values(obj)
- Object.entries(obj)
- Object.is(a, b)
- Object.getOwnPropertyDescriptors(obj)
除了 Object.is(a, b)
之外嘹朗,它們都是處理屬性自身的可遍歷的屬性的方法。Own
是專屬自有屬性的特定命名诵肛,所以帶 Own
的 API
只遍歷到自有屬性就很好識(shí)別了屹培,包括 ES5
的 obj.hasOwnProperty()
就是這個(gè)規(guī)則。此外怔檩,前文的 ...
擴(kuò)展運(yùn)算符是也歸類于此褪秀。
第二類在 Object
命名空間上新增方法:
- Object.setPrototypeOf(obj, proto)
- Object.getPrototypeOf(obj)
這兩個(gè)方法是直接對(duì)原型鏈對(duì)象的 set
和 get
,雖不用于遍歷薛训,但屬于和原型鏈直接打交道的方法媒吗。真正能遍歷到原型鏈的還是 ES5
已有的 for in
循環(huán)。
到此许蓖,問題就非常明確了蝴猪,仍然只有極少數(shù)的 API
可以遍歷或者直接操作原型鏈调衰,其他絕大多數(shù)新增 API
,都只明確的限定在對(duì)象自有屬性的遍歷上自阱。
屬性的描述方法
對(duì)象的每個(gè)屬性都有一個(gè)描述對(duì)象(Descriptor)嚎莉。屬性的描述對(duì)象在 ES5
就具備了,但當(dāng)初很少接觸到這個(gè)概念沛豌。具體上趋箩,它包含以下項(xiàng)目:
{
value: 'attr', //屬性值
writable: true, // 可寫
enumerable: true, //可枚舉可遍歷
configurable: true //可配置
}
描述對(duì)象
之于屬性
,好比于原子細(xì)分成用若干個(gè)質(zhì)子加派、中子來描述叫确。描述對(duì)象
用來對(duì)外界開放處置屬性的權(quán)限,這在設(shè)計(jì)健壯的 API
是非常有用的芍锦。
為此而新增的方法有:
- Object.getOwnPropertyDescriptors(obj)
- Object.getOwnPropertyDescriptor(obj, key)
上文曾提過竹勉,該方法只會(huì)遍歷到對(duì)象自身屬性。至于其他方面娄琉,延伸暫無必要次乓。
ES6 之于遍歷
很多時(shí)候,提到獲取數(shù)組孽水、對(duì)象中的元素時(shí)候票腰,總會(huì)說來個(gè) for
循環(huán),來遍歷一下女气。數(shù)組杏慰、對(duì)象大部分的消費(fèi)方式就是通過遍歷。類似的炼鞠,字符串也有遍歷缘滥,甚至 Generator + yield
也是對(duì)狀態(tài)的遍歷〈亟粒看過或用過這么多 ES6
的 API
完域,不知您是否注意到,作為如此共性的 遍歷
這事兒瘩将,ES6
其實(shí)開放了一個(gè)底層概念:Iterator
遍歷器吟税。
讀過 underscore.js
源碼的同學(xué),不難發(fā)現(xiàn)它在 遍歷
問題上也是采用了統(tǒng)一的 口徑
——即用數(shù)組的 for i++
方式姿现,對(duì)象的遍歷 for in
最終也是落腳在數(shù)組遍歷上肠仪。
這兩個(gè)問題擺在一起,能為我們?cè)O(shè)計(jì) API
提供怎樣的啟示呢备典?信息量略大异旧,容我想好再說,哈哈~
最后提佣, Iterator
遍歷器這個(gè)偏底層的概念吮蛹,本文還是老套路荤崇,把它先關(guān)在籠子里,以后再來拜會(huì)潮针。