七、解構(gòu)
7.1 解構(gòu)的實(shí)用
在 ECMAScript 5 或更早的版本中,從對(duì)象或數(shù)組中獲取特定的數(shù)據(jù)并賦值給本地變量需要書(shū)寫(xiě)很多并且相似的代碼。例如:
let options = {
repeat: true,
save: false
};
// 從對(duì)象中提取數(shù)據(jù)
let repeat = options.repeat,
save = options.save;
這段代碼反復(fù)地提取在 options 上存儲(chǔ)地屬性值并將它們傳遞給同名的本地變量。雖然這些看起來(lái)不是那么復(fù)雜,不過(guò)想象一下如果你的一大批變量有著相同的需求,你就只能一個(gè)一個(gè)地賦值。而且,如果你需要從對(duì)象內(nèi)部嵌套的結(jié)構(gòu)來(lái)查找想要的數(shù)據(jù),你極有可能為了一小塊數(shù)據(jù)而訪問(wèn)了整個(gè)數(shù)據(jù)結(jié)構(gòu)。
這也是 ECMAScript 6 給對(duì)象和數(shù)組添加解構(gòu)的原因。當(dāng)你想要把數(shù)據(jù)結(jié)構(gòu)分解為更小的部分時(shí),從這些部分中提取數(shù)據(jù)會(huì)更容易些。很多語(yǔ)言都能使用精簡(jiǎn)的語(yǔ)法來(lái)實(shí)現(xiàn)解構(gòu)操作。ECMAScript 6 解構(gòu)的實(shí)際語(yǔ)法或許你已經(jīng)非常熟悉:對(duì)象和數(shù)組字面量。
7.2 對(duì)象解構(gòu)
7.2.1 對(duì)象解構(gòu)的基本形式
對(duì)象結(jié)構(gòu)的語(yǔ)法就是在賦值語(yǔ)句的左側(cè)使用類似對(duì)象字面量的結(jié)構(gòu)。
let node = {
type: "Identifier",
name: "foo"
};
//這里就相當(dāng)于聲明了兩個(gè)變量: type = node.type; name:node.name
let { type, name } = node;
console.log(type); // "Identifier"
console.log(name); // "foo"
在上面的結(jié)構(gòu)中必須要初始化。否則會(huì)出現(xiàn)語(yǔ)法錯(cuò)誤。
// 語(yǔ)法錯(cuò)誤!
var { type, name };
// 語(yǔ)法錯(cuò)誤!
let { type, name };
// 語(yǔ)法錯(cuò)誤!
const { type, name };
7.2.2 解構(gòu)賦值表達(dá)式
如果聲明的變量想改變他們的值,也可以使用解構(gòu)表達(dá)式。
<script type="text/javascript">
let node = {
type: "Identifier",
name: "foo"
},
type = "Literal",
name = 5;
//注意:此處必須要在圓括號(hào)內(nèi)才能使用解構(gòu)表達(dá)式
({type, name} = node);
console.log(type); // "Identifier"
console.log(name); // "foo""
</script>
7.2.3 對(duì)象解構(gòu)時(shí)的默認(rèn)值
如果賦值號(hào)右邊的對(duì)象中沒(méi)有與左邊變量同名的屬性,則左邊的變量會(huì)是 undefined
let node = {
type: "Identifier",
name: "foo"
};
//因?yàn)閚ode中沒(méi)有叫value的屬性,所以valued的值將會(huì)是undefined
let { type, name, value } = node;
console.log(type); // "Identifier"
console.log(name); // "foo"
console.log(value); // undefined
不過(guò)我們也可以手動(dòng)指定他的默認(rèn)值。(這個(gè)和函數(shù)的參數(shù)默認(rèn)值很像)
<script type="text/javascript">
let node = {
type: "Identifier",
name: "foo"
};
//手動(dòng)添加value的默認(rèn)值為3
let { type, name, value = 3} = node;
console.log(type); // "Identifier"
console.log(name); // "foo"
console.log(value); // 3
</script>
7.2.4 賦值給不同的變量名
在前面的操作中,都是把對(duì)象的屬性值,賦值給同名變量。
其實(shí)也可以賦值給不同名的變量。
<script type="text/javascript">
let node = {
type: "Identifier",
name: "foo"
};
// localType才是要定義的新的變量。 type是node的屬性
let {type: localType, name: localName} = node;
console.log(localType); // "Identifier"
console.log(localName); // "foo"
</script>
注意:冒號(hào)后面才是要定義的新的變量,這個(gè)和我們的對(duì)象字面量不太一樣!
這個(gè)地方也可以使用默認(rèn)值。
let node = {
type: "Identifier"
};
let { type: localType, name: localName = "bar" } = node;
console.log(localType); // "Identifier"
console.log(localName); // "bar"
7.3 數(shù)組解構(gòu)
7.3.1 數(shù)組解構(gòu)基本語(yǔ)法
數(shù)據(jù)解構(gòu)的語(yǔ)法和對(duì)象解構(gòu)看起來(lái)類似,只是將對(duì)象字面量替換成了數(shù)組字面量,而且解構(gòu)操作的是數(shù)組內(nèi)部的位置(索引)而不是對(duì)象中的命名屬性,例如:
let colors = [ "red", "green", "blue" ];
let [ firstColor, secondColor ] = colors;
console.log(firstColor); // "red"
console.log(secondColor); // "green"
如果只想取數(shù)組中的某一項(xiàng),則可以不用命名。
let colors = [ "red", "green", "blue" ];
//只取數(shù)組中的第三項(xiàng)。
let [ , , thirdColor ] = colors;
console.log(thirdColor); // "blue"
7.3.2 解構(gòu)表達(dá)式
你可以想要賦值的情況下使用數(shù)組的解構(gòu)賦值表達(dá)式,但是和對(duì)象解構(gòu)不同,沒(méi)必要將它們包含在圓括號(hào)中,例如:
let colors = [ "red", "green", "blue" ],
firstColor = "black",
secondColor = "purple";
[ firstColor, secondColor ] = colors; //可以不用加括號(hào)。當(dāng)然添加也不犯法
console.log(firstColor); // "red"
console.log(secondColor); // "green"
數(shù)組解構(gòu)表達(dá)式有一個(gè)很常用的地方,就是交換兩個(gè)變量的值。在以前一般定義一個(gè)第三方變量進(jìn)行交換,例如下面的代碼:
<script type="text/javascript">
let a = 3,
b = 4,
temp;
temp = a;
a = b;
b = temp;
console.log(a);
console.log(b)
</script>
那么在ES6中完全可以拋棄第三方變量這種方式,使用我們的數(shù)組解構(gòu)表達(dá)式
<script type="text/javascript">
let a = 3,
b = 4;
//左側(cè)和前面的案例是一樣的,右側(cè)是一個(gè)新創(chuàng)建的數(shù)組字面量。
[a, b] = [b, a];
console.log(a);
console.log(b)
</script>
八、新的基本類型:Symbol
以前我們有5種基本數(shù)據(jù)類型:Number、String、Boolean、Null、Undefined
ES6新增了一種新的數(shù)據(jù)類型:Symbol
在ES5之前我們都沒(méi)辦法創(chuàng)建私有變量,只能想辦法去封裝。symbol 來(lái)創(chuàng)建私有成員,這也是 JavaScript 開(kāi)發(fā)者長(zhǎng)久以來(lái)期待的一項(xiàng)特性。
8.1 創(chuàng)建Symbol
Symbol在基本數(shù)據(jù)類型中是比較特別的。我們以前的都可以用字面量去創(chuàng)建基本數(shù)據(jù)類型的數(shù)據(jù),但是Symbol卻不可以使用字面量的是形式去創(chuàng)建。
我們可以使用symbol全局函數(shù)來(lái)創(chuàng)建Symbol。
<script type="text/javascript">
let firstName = Symbol(); //創(chuàng)建一個(gè)Symbol
let person = {};
person[firstName] = "張三";
console.log(person[firstName]); // "張三"
</script>
說(shuō)明:上面的代碼中,firstName 作為 symbol 類型被創(chuàng)建并賦值給 person 對(duì)象以作其屬性。每次訪問(wèn)這個(gè)屬性時(shí)必須使用該 symbol 。
在創(chuàng)建Symbol的時(shí)候,也可以傳入字符串,這個(gè)字符串也僅僅是在調(diào)試輸出的時(shí)候方便,實(shí)際沒(méi)有啥用處。
<script type="text/javascript">
var s1 = Symbol("abc");
var s2 = Symbol("abc");
console.log(s1 == s2); //false
</script>
注意:任意兩個(gè)Symbol都不會(huì)相等,即使創(chuàng)建他們的時(shí)候使用了相同的參數(shù)。
8.2 識(shí)別Symbol
既然 symbol 是基本類型,你可以使用 typeof 操作符來(lái)判斷變量是否為 symbol 。ECMAScript 6 拓展了 typeof 使其操作 symbol 時(shí)返回 "symbol"。例如:
let symbol = Symbol();
console.log(typeof symbol); // "symbol"
8.3 Symbol作為屬性名
由于每一個(gè)Symbol值都是不相等的,這意味著Symbol值可以作為標(biāo)識(shí)符,用于對(duì)象的屬性名,就能保證不會(huì)出現(xiàn)同名的屬性。這對(duì)于一個(gè)對(duì)象由多個(gè)模塊構(gòu)成的情況非常有用,能防止某一個(gè)鍵被不小心改寫(xiě)或覆蓋。
var mySymbol = Symbol();
// 第一種寫(xiě)法
var a = {};
a[mySymbol] = 'Hello!';
// 第二種寫(xiě)法
var a = {
[mySymbol]: 'Hello!'
}
以上兩種寫(xiě)法都是相同的結(jié)果
注意:
symbol作為對(duì)象的屬性的時(shí)候,只能使用 [ ] 去訪問(wèn),不能使用點(diǎn)去訪問(wèn)。
symbol作為對(duì)象的屬性名使用的時(shí)候,該屬性還是公開(kāi)屬性,不是私有屬性。但是這個(gè)時(shí)候使用for... in和for...of時(shí)無(wú)法遍歷到這個(gè)symbol屬性的。
8.4 Symbol屬性名的遍歷
Symbol 作為屬性名,該屬性不會(huì)出現(xiàn)在for...in循環(huán)中,也不會(huì)被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。但是,它也不是私有屬性,有一個(gè)Object.getOwnPropertySymbols方法,可以獲取指定對(duì)象的所有 Symbol 屬性名。
看下面的代碼
<script type="text/javascript">
var obj = {};
var a = Symbol('a');
var b = Symbol('b');
obj[a] = 'Hello';
obj[b] = 'World';
// 返回obj對(duì)象所有Symbol類型的屬性名組成的數(shù)組。
var objectSymbols = Object.getOwnPropertySymbols(obj);
console.log(objectSymbols) //[Symbol(a), Symbol(b)]
</script>
看下面的代碼
var obj = {};
var foo = Symbol("foo");
obj[foo] = "lisi";
for (var i in obj) {
console.log(i); // 無(wú)輸出 。 因?yàn)楸闅v不到Symbol型的屬性
}
Object.getOwnPropertyNames(obj);// [] 只能拿到非Symbol類型的屬性
Object.getOwnPropertySymbols(obj) //[Symbol(foo)]
還有一個(gè)新API可以拿到所有類型的屬性,包括常規(guī)和Symbol型的。
Reflect.ownKeys
let obj = {
[Symbol('my_key')]: 1,
enum: 2,
nonEnum: 3
};
Reflect.ownKeys(obj);// ["enum", "nonEnum", Symbol(my_key)]
說(shuō)明:
- 由于以 Symbol 值作為名稱的屬性,不會(huì)被常規(guī)方法遍歷得到。我們可以利用這個(gè)特性,為對(duì)象定義一些非私有的、但又希望只用于內(nèi)部的方法。
8.5 Symbol.for(字符串)和Symbol.keyFor(symbol類型的值)
一、Symbol.for(字符串參數(shù)):在全局環(huán)境中搜索 以該字符串作為參數(shù)的Symbol值,如果搜到則返回這個(gè)sybol,如果搜不到則創(chuàng)建一個(gè)Symbol,并把它注冊(cè)在全局環(huán)境中。
<script type="text/javascript">
//第一次搜不到,則新創(chuàng)建一個(gè)返回,并在全局環(huán)境(window)中注冊(cè)
var a = Symbol.for("foo");
//第二次搜到上次創(chuàng)建的
var b = Symbol.for("foo");
console.log(a === b); //因?yàn)閮纱嗡训降氖峭粋€(gè)Symbol,所以此處是true
</script>
Symbol.for()和Symbol()都可以創(chuàng)建Symbol類型的數(shù)據(jù)。
二者區(qū)別:
- Symbol.for()對(duì)同樣的字符串,每次得到結(jié)果肯定是一樣的。因?yàn)槎际菑娜汁h(huán)境中搜索。
- Symbol()則不會(huì)有搜索的過(guò)程,每次都是一個(gè)全新的不同的symbol,而且也不會(huì)向全局環(huán)境中注冊(cè)。
看下面的代碼
<script type="text/javascript">
var a = Symbol("foo");
var b = Symbol.for("foo");
console.log(a == b); //false
</script>
二、Symbol.keyFor(symbol):返回一個(gè)已經(jīng)全局注冊(cè)的symbol的"key"。
<script type="text/javascript">
var a = Symbol("foo");
var b = Symbol.for("foo");
console.log(Symbol.keyFor(a)); // undefined. 因?yàn)閍沒(méi)有向全局環(huán)境中注冊(cè),所以是undefinded
console.log(Symbol.keyFor(b)); // foo
</script>
九、Set數(shù)據(jù)結(jié)構(gòu)
JavaScript 在絕大部分歷史時(shí)期內(nèi)只有一種集合類型,那就是數(shù)組。數(shù)組在 JavaScript 中的使用方式和其它語(yǔ)言很相似,但是其它集合類型的缺乏導(dǎo)致數(shù)組也經(jīng)常被當(dāng)作隊(duì)列(queues)和棧(stacks)來(lái)使用。
因?yàn)閿?shù)組的索引只能是數(shù)字類型,當(dāng)開(kāi)發(fā)者覺(jué)得非數(shù)字類型的索引是必要的時(shí)候會(huì)使用非數(shù)組對(duì)象。這項(xiàng)用法促進(jìn)了以非類數(shù)組對(duì)象為基礎(chǔ)的 set 和 map 集合類型的實(shí)現(xiàn)。
Set是類似數(shù)組的一種結(jié)構(gòu),可以存儲(chǔ)數(shù)據(jù),與數(shù)組的區(qū)別主要是 Set中的元素不能重復(fù),而數(shù)組中的元素可以重復(fù)。
一句話總結(jié):Set類型是一個(gè)包含無(wú)重復(fù)元素的有序列表
9.1 創(chuàng)建Set和并添加元素
Set本身是一個(gè)構(gòu)造函數(shù)。
<script type="text/javascript">
//創(chuàng)建Set數(shù)據(jù)結(jié)構(gòu)對(duì)象。
var s = new Set();
//調(diào)用set對(duì)象的add方法,向set中添加元素
s.add("a");
s.add("c");
s.add("b");
//set的size屬性可以獲取set中元素的個(gè)數(shù)
console.log(s.size)
</script>
9.2 Set中不能添加重復(fù)元素
<script type="text/javascript">
var s = new Set();
s.add("a");
s.add("c");
s.add("b");
s.add("a"); //重復(fù),所以添加失敗。注意這個(gè)地方并不會(huì)保存。
console.log(s.size); // 長(zhǎng)度是3
</script>
看下面的代碼:
<script type="text/javascript">
var s = new Set();
s.add(5);
s.add("5");
console.log(s.size); // 長(zhǎng)度是2
</script>
在上面的代碼中,數(shù)字5和字符串5都會(huì)添加成功。為什么呢?
Set是使用什么機(jī)制來(lái)判斷兩個(gè)元素是否相等的呢?
是通過(guò)我們前面說(shuō)過(guò)的 Object.is(a, b) 來(lái)判斷兩個(gè)元素是否相等。
<script type="text/javascript">
var s = new Set();
s.add(+0);
s.add(-0); //重復(fù)添加不進(jìn)去
s.add(NaN);
s.add(NaN); //重復(fù)添加不進(jìn)去
s.add([]);
s.add([]); //兩個(gè)空數(shù)組不相等,所以可以添加進(jìn)去
s.add({});
s.add({}); // 兩個(gè)空對(duì)象也不重復(fù),所以也可以添加進(jìn)去
console.log(s.size); // 長(zhǎng)度是6
</script>
9.3 使用數(shù)組初始化Set
<script type="text/javascript">
//使用數(shù)組中的元素來(lái)初始化Set,當(dāng)然碰到重復(fù)的也不會(huì)添加進(jìn)去。
var s = new Set([2, 3, 2, 2, 4]);
console.log(s.size)
</script>
9.4 判斷一個(gè)值是否在Set中
使用Set的 has() 方法可以判斷一個(gè)值是否在這個(gè)set中。
<script type="text/javascript">
let set = new Set();
set.add(5);
set.add("5");
console.log(set.has(5)); // true
console.log(set.has(6)); // false
</script>
9.5 移除Set中的元素
delete(要?jiǎng)h除的值) :刪除單個(gè)值
clear():清空所有的值
<script type="text/javascript">
let set = new Set();
set.add(5);
set.add("5");
console.log(set.has(5)); // true
set.delete(5);
console.log(set.has(5)); // false
console.log(set.size); // 1
set.clear();
console.log(set.has("5")); // false
console.log(set.size); // 0
</script>
9.6 遍歷Set
數(shù)組有個(gè)方法forEach可以遍歷數(shù)組。
- Set也有forEach可以遍歷Set。
使用Set的forEach遍歷時(shí)的回調(diào)函數(shù)有三個(gè)參數(shù):
function (value, key, ownerSet){
}
參數(shù)1:遍歷到的元素的值
參數(shù)2:對(duì)set集合來(lái)說(shuō),參數(shù)2的值和參數(shù)1的值是完全一樣的。
參數(shù)3:這個(gè) ==set== 自己
<script type="text/javascript">
let set = new Set(["a", "c", "b", 9]);
set.forEach(function (v, k, s) {
console.log(v + " " + (v === k) + " " + (s === set)); // 永遠(yuǎn)是true
})
</script>
- for…of也可以遍歷set。
for(var v of set){
console.log(v)
}
9.7 將Set轉(zhuǎn)換為數(shù)組
將數(shù)組轉(zhuǎn)換為Set相當(dāng)容易,你只需要在創(chuàng)建Set集合時(shí)把數(shù)組作為參數(shù)傳遞進(jìn)去即可。
把Set轉(zhuǎn)換為數(shù)組使用前面講到的擴(kuò)展運(yùn)算符也很容易
<script type="text/javascript">
let set = new Set([1, 2, 3, 3, 3, 4, 5]),
arr = [...set]; //使用擴(kuò)展運(yùn)算符。那么新的數(shù)組中已經(jīng)沒(méi)有了重復(fù)元素。注意,此處對(duì)set并沒(méi)有什么影響
console.log(arr); // [1,2,3,4,5]
</script>
這種情況在需要去數(shù)組中重復(fù)元素的時(shí)候非常好用。
<script type="text/javascript">
function eliminateDuplicates(items) {
return [...new Set(items)];
}
let numbers = [1, 2, 3, 3, 3, 4, 5, 5, 2, 1, 1],
//返回的是新的沒(méi)有重復(fù)元素的數(shù)組。
noDuplicates = eliminateDuplicates(numbers);
console.log(noDuplicates); // [1,2,3,4,5]
</script>
Set提供了處理一系列值的方式,不過(guò)如果想給這些值添加一些附加數(shù)據(jù)則顯得力不從心,所以又提供了一種新的數(shù)據(jù)結(jié)構(gòu):Map