一、ES6簡介
? 歷時將近6年的時間來制定的新 ECMAScript 標準 ECMAScript 6(亦稱 ECMAScript Harmony,簡稱 ES6)終于在 2015 年 6 月正式發(fā)布。自從上一個標準版本 ES5 在 2009 年發(fā)布以后,ES6 就一直以新語法、新特性的優(yōu)越性吸引著眾多 JavaScript 開發(fā)者,驅(qū)使他們積極嘗鮮。
? 由于ES6是在2015年發(fā)布的,所以也叫ES2015。
? 以后ESCMAScript標準一年一更新,統(tǒng)一使用年份命名:ES2016、ES2017、....
下面開始介紹ES6常用的一些新特性:
二、塊級作用域綁定
在ES5之前,不存在塊級作用域,在編程的時候很多時候會帶來很多的不便,ES6新增了塊級作用域,補足了這方面的缺陷。
塊級聲明指的是該聲明的變量無法被代碼塊外部訪問。塊作用域,又被稱為詞法作用域(lexical scopes),可以在如下的條件下創(chuàng)建:
- 函數(shù)內(nèi)部
- 在代碼塊(即 { })內(nèi)部
塊級作用域是很多類C語言的工作機制,ECMAScript 6 引入塊級聲明的目的是增強 JavaScript 的靈活性,同時又能與其它編程語言保持一致。
2.1 let聲明
使用let聲明變量的語法和使用var聲明的語法是一樣的。但是let聲明的變量的作用域會限制在當前的代碼塊中。這是let與var的最大區(qū)別。
<script type="text/javascript">
let a = 10;
if(a > 5){
console.log(b); //用let聲明的變量沒有聲明提前這一特性,所以此處也訪問不到(報錯)
let b = 20;
console.log(b);
}
console.log(b); //由于b是在if塊中使用let聲明的,所以此處無法訪問到。(報錯)
</script>
注意:
- 用 let 聲明的變量具有塊級作用域,只能在聲明的塊中訪問,在塊外面無法訪問
- 用let聲明的變量也沒有聲明提前這一特性。
- 在同一個塊中,let聲明的變量也不能重復聲明。
- 在聲明變量的時候盡量使用let,慢慢的拋棄var
2.2 const聲明(Constant Declarations)
在 ES6 使用const來聲明的變量稱之為常量。這意味著它們不能再次被賦值。由于這個原因,所有的 const 聲明的變量都必須在聲明處初始化。const聲明的常量和let變量一樣也是具有塊級作用域的特性。
<script type="text/javascript">
var a = 20;
if (true) {
const b = 20;
b = 30; //錯誤! 常量不能重新賦值
const c; //錯誤! 常量聲明的同時必須賦值。
}
</script>
注意:
- const的特性除了聲明的是常量為,其他與let一樣。
- 在let和const聲明前的這段區(qū)域稱之為暫存性死區(qū)(The Temporal Dead Zone —TDZ)。
- 使用let和const聲明的變量和常量不再是window的屬性。 也就是說通過window.a是無法訪問到的。
2.3 循環(huán)中的塊級綁定
使用var聲明的循環(huán)變量在循環(huán)結(jié)束后仍然可以訪問到。 使用let聲明的循環(huán)變量,在循環(huán)結(jié)束之后會立即銷毀。
<script type="text/javascript">
for(let i = 0; i < 3; i++){ // 循環(huán)結(jié)束之后會立即銷毀 i
console.log(i);
}
console.log(i); //此處無法訪問到 i 。
</script>
2.4 循環(huán)中的函數(shù)
看下面的代碼,是輸出10個10,而不是0,1,2,...
<script type="text/javascript">
var funcs = [];
for (var i = 0; i < 10; i++) {
funcs.push(function () {
console.log(i);
});
}
funcs.forEach(function (func) {
func(); // 輸出 "10" 共10次
});
</script>
解決辦法需要使用函數(shù)的自執(zhí)行特性。
var funcs = [];
for (var i = 0; i < 10; i++) {
funcs.push((function(value) {
return function() {
console.log(value);
}
}(i)));
}
funcs.forEach(function(func) {
func(); // 輸出 0,1,2 ... 9
});
如果使用let聲明變量,則完全可以避免前面的問題。 這是ES6規(guī)范中專門定義的特性。在for … in和for ... of循環(huán)中也適用
<script type="text/javascript">
var funcs = [];
for (let i = 0; i < 10; i++) {
funcs.push(function () {
console.log(i);
});
}
funcs.forEach(function (func) {
func(); // 輸出 0,1,2 ... 9
})
</script>
說明:
- let 聲明使得每次迭代都會創(chuàng)建一個變量 i,所以循環(huán)內(nèi)部創(chuàng)建的函數(shù)會獲得各自的變量 i 的拷貝。每份拷貝都會在每次迭代的開始被創(chuàng)建并被賦值。
三、函數(shù)的新增特性
3.1 帶默認參數(shù)的函數(shù)
JavaScript函數(shù)的最大的一個特點就是在傳遞參數(shù)的時候,參數(shù)的個數(shù)不受限制的。為了健壯性考慮,一般在函數(shù)內(nèi)部需要做一些默認值的處理。
function makeRequest(url, timeout, callback) {
timeout = timeout || 2000;
callback = callback || function() {};
}
其實上面的默認值方法有個bug:當timeout是0的時候也會當做假值來處理,從而給賦值默認值2000.
ES6從語言層面面上增加了 默認值的 支持。看下面的代碼:
//這個函數(shù)如果只傳入第一個參數(shù),后面兩個不傳入,則會使用默認值。如果后面兩個也傳入了參數(shù),則不會使用默認值。
function makeRequest(url, timeout = 2000, callback = function() {}) {
// 其余代碼
}
3.2 默認參數(shù)對 arguments 對象的影響
在非嚴格模式下,arguments總是能反映出命名參數(shù)的變化。看下面的代碼:
<script type="text/javascript">
function foo(a, b) {
//非嚴格模式
console.log(arguments[0] === a); //true
console.log(arguments[1] === b); //true
a = 10;
b = 20;
console.log(arguments[0] === a); //true
console.log(arguments[1] === b); //true
}
foo(1, 2);
</script>
在ES5的嚴格模式下,arguments只反映參數(shù)的初始值,而不再反映命名參數(shù)的變化!
<script type="text/javascript">
function foo(a, b) {
//嚴格模式
"use strict"
console.log(arguments[0] === a); //true
console.log(arguments[1] === b); //true
a = 10;
b = 20;
console.log(arguments[0] === a); //false。 修改a的值不會影響到arguments[0]的值
console.log(arguments[1] === b); //false
}
foo(1, 2);
</script>
當使用ES6參數(shù)默認值的時候,不管是否是在嚴格模式下,都和ES5的嚴格模式相同。看下面的代碼:
<script type="text/javascript">
function foo(a, b = 30) {
console.log(arguments[0] === a); //true
console.log(arguments[1] === b); //true
a = 10;
b = 20;
console.log(arguments[0] === a); //false。 由于b使用了默認值。雖然a沒有使用默認值,但是仍然表現(xiàn)的和嚴格模式一樣。
console.log(arguments[1] === b); //false。 b使用了默認值,所以表現(xiàn)的和嚴格模式一樣。
}
foo(1, 2);
</script>
注意:如果這樣調(diào)用foo(1),則 a == 1, b == 30, arguments[0] == 1, arguments[1] == undefined。也就是說默認值并不會賦值給arguments參數(shù)。
3.3 默認參數(shù)表達式 (Default Parameter Expressions)
參數(shù)的默認值,也可以是一個表達式或者函數(shù)調(diào)用等。看下面的代碼
<script type="text/javascript">
function getValue() {
return 5;
}
function add(first, second = getValue()) { //表示使用getValue這個函數(shù)的返回值作為second的默認值。
return first + second;
}
console.log(add(1, 1)); // 2. 調(diào)用add函數(shù)的時候,傳入了第二個參數(shù),則以傳入的參數(shù)為準。
console.log(add(1)); // 6。 調(diào)用add函數(shù)的時候,沒有傳入第二個參數(shù),則會調(diào)用getValue函數(shù)。
</script>
有一點需要要注意:getValue()只會在調(diào)用add且不傳入第二個參數(shù)的時候才會去調(diào)用。不是在解析階段調(diào)用的。
<script type="text/javascript">
let value = 5;
function getValue() {
return value++;
}
function add(first, second = getValue()) { //
return first + second;
}
console.log(add(1, 1)); // 2
console.log(add(1)); // 6。
console.log(add(1)); // 7
console.log(add(1)); // 8
</script>
由于默認值可以表達式,所以我們甚至可以使用前面的參數(shù)作為后面參數(shù)的默認值。
function add(first, second = first) { // 使用第一個參數(shù)作為第二個參數(shù)的默認值
return first + second;
}
注意:可以把前面的參數(shù)作為后面參數(shù)的默認值,但是不能把后面的參數(shù)作為第一個參數(shù)的默認值。這可以前面說的let和const的暫存性死區(qū)一個意思。
function add(first = second, second)) { // 這種寫法是錯誤的
return first + second;
}
3.4 未命名參數(shù)問題
Javascript并不限制傳入的參數(shù)的數(shù)量。在調(diào)用函數(shù)的時候,傳入的實參的個數(shù)超過形參的個數(shù)的時候,超過的部分就成為了未命名參數(shù)。在ES5之前,我們一般可以通過arguments對象來獲取到未命名參數(shù)的值。但是羅顯繁瑣。
<script type="text/javascript">
function foo(a) {
console.log(a);
console.log(arguments[1]) //取得傳入的多余的參數(shù)。
}
foo(2, 3);
</script>
ES6,提供了一種更加優(yōu)雅處理未命名參數(shù)的問題:剩余參數(shù)( Rest Parameters )
語法:function a(a, … b){ }
剩余參數(shù)使用三個點( … )和變量名來表示。
<script type="text/javascript">
function foo(a, ...b) {
console.log(a);
console.log(b instanceof Array); //true .多余的參數(shù)都被放入了b中。b其實就是一個數(shù)組。
}
foo(2, 3, 4, 6);
</script>
注意:
- 函數(shù)最多只能有一個剩余參數(shù)b。而且這個剩余參數(shù)必須位于參數(shù)列表的最后位置。
- 雖然有了剩余參數(shù),但是arguments仍然存在,但是arguments完全無視了剩余參數(shù)的存在。
- 剩余參數(shù)是在函數(shù)聲明的時候出現(xiàn)的。
3.5 函數(shù)中的擴展運算符
例如:Math中的max函數(shù)可以返回任意多個參數(shù)中的最大值。但是如果這些參數(shù)在一個數(shù)組中,則沒有辦法直接傳入。以前通用的做法是使用applay方法。
看下面的代碼:
<script type="text/javascript">
let values = [25, 50, 75, 100]
console.log(Math.max.apply(Math, values)); // 100
</script>
上面這種方法雖然可行,但是總是不是那么直觀。
使用ES6提供的擴展運算符可以很容易的解決這個問題。在數(shù)組前加前綴 … (三個點)。
<script type="text/javascript">
let values = [25, 50, 75, 100]
console.log(Math.max(...values)); //使用擴展運算符。相當于拆解了數(shù)組了。
console.log(Math.max(...values, 200)); //也可以使用擴展運算符和參數(shù)的混用,則這個時候就有 5 個數(shù)參與比較了。
</script>
注意:剩余參數(shù)和擴展運算符都是 使用三個點作為前綴。但是他們使用的位置是不一樣的。
- ****剩余參數(shù)是用在函數(shù)的聲明的時候的參數(shù)列表中,而且必須在參數(shù)列表的后面
- 擴展運算符是用在函數(shù)調(diào)用的時候作為實參來傳遞的,在實參中的位置沒有限制。
四、全新的函數(shù):箭頭函數(shù)(=>)
ECMAScript 6 最有意思的部分之一就是箭頭函數(shù)。正如其名,箭頭函數(shù)由 “箭頭”(=>)這種新的語法來定義。
其實在別的語言中早就有了這種語法結(jié)構(gòu),不過他們叫拉姆達表達式。
4.1 箭頭函數(shù)語法
基本語法如下:
(形參列表)=>{
//函數(shù)體
}
箭頭函數(shù)可以賦值給變量,也可以像匿名函數(shù)一樣直接作為參數(shù)傳遞。
- 示例1:
<script type="text/javascript">
var sum = (num1, num2) =>{
return num1 + num2;
}
console.log(sum(3, 4));
//前面的箭頭函數(shù)等同于下面的傳統(tǒng)函數(shù)
var add = function (num1, num2) {
return num1 + num2;
}
console.log(add(2, 4))
</script>
如果函數(shù)體內(nèi)只有一行代碼,則包裹函數(shù)體的 大括號 ({ })完全可以省略。如果有return,return關(guān)鍵字也可以省略。
如果函數(shù)體內(nèi)有多條語句,則 {} 不能省略。
- 示例2:
<script type="text/javascript">
var sum = (num1, num2) => num1 + num2;
console.log(sum(5, 4));
//前面的箭頭函數(shù)等同于下面的傳統(tǒng)函數(shù)
var add = function (num1, num2) {
return num1 + num2;
}
console.log(add(2, 4));
//如果這一行代碼是沒有返回值的,則方法的返回自也是undefined
var foo = (num1, num2) => console.log("aaa");
console.log(foo(3,4)); //這個地方的返回值就是undefined
</script>
如果箭頭函數(shù)只有一個參數(shù),則包裹參數(shù)的小括號可以省略。其余情況下都不可以省略。當然如果不傳入?yún)?shù)也不可以省略
- 示例3:
<script type="text/javascript">
var foo = a=> a+3; //因為只有一個參數(shù),所以()可以省略
console.log(foo(4)); // 7
</script>
如果想直接返回一個js對象,而且還不想添加傳統(tǒng)的大括號和return,則必須給整個對象添加一個小括號 ()
- 示例4:
<script type="text/javascript">
var foo = ()=>({name:"lisi", age:30});
console.log(foo());
//等同于下面的;
var foo1 = ()=>{
return {
name:"lisi",
age : 30
};
}
</script>
4.2 使用箭頭函數(shù)實現(xiàn)函數(shù)自執(zhí)行
<script type="text/javascript">
var person = (name => {
return {
name: name,
age: 30
}
}
)("zs");
console.log(person);
</script>
4.3 箭頭函數(shù)中無this綁定(No this Binding)
在ES5之前this的綁定是個比較麻煩的問題,稍不注意就達不到自己想要的效果。因為this的綁定和定義位置無關(guān),只和調(diào)用方式有關(guān)。
在箭頭函數(shù)中則沒有這樣的問題,在箭頭函數(shù)中,this和定義時的作用域相關(guān),不用考慮調(diào)用方式
箭頭函數(shù)沒有 this 綁定,意味著 this 只能通過查找作用域鏈來確定。如果箭頭函數(shù)被另一個不包含箭頭函數(shù)的函數(shù)囊括,那么 this 的值和該函數(shù)中的 this 相等,否則 this 的值為 window。
<script type="text/javascript">
var PageHandler = {
id: "123456",
init: function () {
document.addEventListener("click",
event => this.doSomething(event.type), false); // 在此處this的和init函數(shù)內(nèi)的this相同。
},
doSomething: function (type) {
console.log("Handling " + type + " for " + this.id);
}
};
PageHandler.init();
</script>
看下面的一段代碼:
<script type="text/javascript">
var p = {
foo:()=>console.log(this) //此處this為window
}
p.foo(); //輸出為 window對象。 并不是我想要的。所以在定義對象的方法的時候應(yīng)該避免使用箭頭函數(shù)。
//箭頭函數(shù)一般用在傳遞參數(shù),或者在函數(shù)內(nèi)部聲明函數(shù)的時候使用。
</script>
說明:
- 箭頭函數(shù)作為一個使用完就扔的函數(shù),不能作為構(gòu)造函數(shù)使用。也就是不能使用new 的方式來使用箭頭函數(shù)。
- 由于箭頭函數(shù)中的this與函數(shù)的作用域相關(guān),所以不能使用call、apply、bind來重新綁定this。但是雖然this不能重新綁定,但是還是可以使用call和apply方法去執(zhí)行箭頭函數(shù)的。
4.4 無arguments綁定
雖然箭頭函數(shù)沒有自己的arguments對象,但是在箭頭函數(shù)內(nèi)部還是可以使用它外部函數(shù)的arguments對象的。
<script type="text/javascript">
function foo() {
//這里的arguments是foo函數(shù)的arguments對象。箭頭函數(shù)自己是沒有 arguments 對象的。
return ()=>arguments[0]; //箭頭函數(shù)的返回值是foo函數(shù)的第一個參數(shù)
}
var arrow = foo(4, 5);
console.log(arrow()); // 4
</script>
五、對象功能的擴展
在JavaScript中,幾乎所有的類型都是對象,所以使用好對象,對提示JavaScript的性能很重要。
ECMAScript 6 給對象的各個方面,從簡單的語法擴展到操作與交互,都做了改進。
5.1 對象類別
ECMAScript 6 規(guī)范明確定義了每種對象類別。理解該術(shù)語對于從整體上認識該門語言顯得十分重要。對象類別包括:
- 普通對象(ordinary object)擁有 JavaScript 對象所有的默認行為。
- 特異對象(exotic object)的某些內(nèi)部行為和默認的有所差異。
- 標準對象(standard object)是 ECMAScript 6 中定義的對象,例如 Array, Date 等,它們既可能是普通也可能是特異對象。
- 內(nèi)置對象(built-in object)指 JavaScript 執(zhí)行環(huán)境開始運行時已存在的對象。標準對象均為內(nèi)置對象。
5.2 對象字面量的語法擴展
5.2.1 簡寫的屬性初始化
<script type="text/javascript">
function createPerson(name, age) {
//返回一個對象:屬性名和參數(shù)名相同。
return {
name:name,
age:age
}
}
console.log(createPerson("lisi", 30)); // {name:"lisi", age:30}
//在ES6中,上面的寫法可以簡化成如下形式
</script>
在ES6中,上面的寫法可以簡化成如下的形式:
<script type="text/javascript">
function createPerson(name, age) {
//返回一個對象:屬性名和參數(shù)名相同。
return {
name, //當對象屬性名和本地變量名相同時,可以省略冒號和值
age
}
}
console.log(createPerson("lisi", 30)); // {name:"lisi", age:30}
</script>
當對象字面量中的屬性只有屬性名的時候,JavaScript 引擎會在該作用域內(nèi)尋找是否有和屬性同名的變量。在本例中,本地變量 name 的值被賦給了對象字面量中的 name 屬性。
該項擴展使得對象字面量的初始化變得簡明的同時也消除了命名錯誤。對象屬性被同名變量賦值在 JavaScript 中是一種普遍的編程模式,所以這項擴展的添加非常受歡迎。
5.2.2 簡寫的方法聲明
<script type="text/javascript">
var person = {
name:'lisi',
sayHell:function () {
console.log("我的名字是:" + this.name);
}
}
person.sayHell()
</script>
在ES6中,上面的寫法可以簡化成如下的形式:
<script type="text/javascript">
var person = {
name:'李四',
sayHell() {
console.log("我的名字是:" + this.name);
}
}
person.sayHell()
</script>
省略了冒號和function看起來更簡潔
5.2.3 在字面量中動態(tài)計算屬性名
在ES5之前,如果屬性名是個變量或者需要動態(tài)計算,則只能通過 對象.[變量名] 的方式去訪問。而且這種動態(tài)計算屬性名的方式 在字面量中 是無法使用的。
<script type="text/javascript">
var p = {
name : '李四',
age : 20
}
var attName = 'name';
console.log(p[attName]) //這里 attName表示的是一個變量名。
</script>
而下面的方式使用時沒有辦法訪問到attName這個變量的。
<script type="text/javascript">
var attName = 'name';
var p = {
attName : '李四', // 這里的attName是屬性名,相當于各級p定義了屬性名叫 attName的屬性。
age : 20
}
console.log(p[attName]) // undefined
</script>
在ES6中,把屬性名用[ ]括起來,則括號中就可以引用提前定義的變量。
<script type="text/javascript">
var attName = 'name';
var p = {
[attName] : '李四', // 引用了變量attName。相當于添加了一個屬性名為name的屬性
age : 20
}
console.log(p[attName]) // 李四
</script>
5.3 新增的方法
ECMAScript 從第五版開始避免在 Object.prototype 上添加新的全局函數(shù)或方法,轉(zhuǎn)而去考慮具體的對象類型如數(shù)組)應(yīng)該有什么方法。當某些方法不適合這些具體類型時就將它們添加到全局 Object 上 。
ECMAScript 6 在全局 Object 上添加了幾個新的方法來輕松地完成一些特定任務(wù)。
5.3.1 Object.is()
在 JavaSciprt 中當你想比較兩個值時,你極有可能使用比較操作符(==)或嚴格比較操作符(===)。許多開發(fā)者為了避免在比較的過程中發(fā)生強制類型轉(zhuǎn)換,更傾向于后者。但即使是嚴格等于操作符,它也不是萬能的。例如,它認為 +0 和 -0 是相等的,雖然它們在 JavaScript 引擎中表示的方式不同。同樣 NaN === NaN 會返回 false,所以必須使用 isNaN() 函數(shù)才能判斷 NaN 。
ECMAScript 6 引入了 Object.is() 方法來補償嚴格等于操作符怪異行為的過失。該函數(shù)接受兩個參數(shù)并在它們相等的返回 true 。只有兩者在類型和值都相同的情況下才會判為相等。如下所示:
console.log(+0 == -0); // true
console.log(+0 === -0); // true
console.log(Object.is(+0, -0)); // false
console.log(NaN == NaN); // false
console.log(NaN === NaN); // false
console.log(Object.is(NaN, NaN)); // true
console.log(5 == 5); // true
console.log(5 == "5"); // true
console.log(5 === 5); // true
console.log(5 === "5"); // false
console.log(Object.is(5, 5)); // true
console.log(Object.is(5, "5")); // false
很多情況下 Object.is() 的表現(xiàn)和 === 是相同的。它們之間的區(qū)別是前者 認為 +0 和 -0 不相等而 NaN 和 NaN 則是相同的。不過棄用后者是完全沒有必要的。何時選擇 Object.is() 與 == 或 === 取決于代碼的實際情況。
5.3.2 Object.assign()
使用assign主要是為了簡化對象的混入(mixin)。混入是指的在一個對象中引用另一個對象的屬性或方法。
assing可以把一個對象的屬性和訪問完整的轉(zhuǎn)copy到另外一個對象中。
<script type="text/javascript">
var p = {
name : "lisi",
age : 20,
friends : ['張三', '李四']
}
var p1 = {};
Object.assign(p1, p); //則p1中就有了與p相同的屬性和方法. p1是接受者,p是提供者
console.log(p1);
//這種copy是淺copy,也就是說如果屬性值是對象的話,只是copy的對象的地址值(引用)
console.log(p1.friends == p.friends); //true p1和p的friends同事指向了同一個數(shù)組。
p.friends.push("王五");
console.log(p1.friends); //['張三', '李四', '王五']
</script>
assign方法可以接受任意多的提供者。意味著后面提供者的同名屬性和覆蓋前面提供者的屬性值。
<script type="text/javascript">
var p = {
name : "lisi",
age : 20,
friends : ['張三', '李四']
}
var p1 = {
name : 'zs',
}
var p2 = {};
Object.assign(p2, p, p1); //p和p1都是提供者
console.log(p2.name); // zs
</script>
六、字符串功能的增強
6.1 查找子字符串
在以前在字符串中查找字符串的時候,都是使用indexOf方法。
ES6新增了三個方法來查找字符串。
- includes() 方法會在給定文本存在于字符串中的任意位置時返回 true,否則返回 false 。
- startsWith() 方法會在給定文本出現(xiàn)在字符串開頭時返回 true,否則返回 false 。
- endsWith() 方法會在給定文本出現(xiàn)在字符串末尾時返回 true,否則返回 false 。
每個方法都接收兩個參數(shù):需要搜索的文本和可選的起始索引值。當提供第二個參數(shù)后,includes() 和 startsWith() 會以該索引為起始點進行匹配,而 endsWith() 將字符串的長度與參數(shù)值相減并將得到的值作為檢索的起始點。若第二個參數(shù)未提供,includes() 和 startsWith() 會從字符串的起始中開始檢索,endsWith() 則是從字符串的末尾。實際上,第二個參數(shù)減少了需要檢索的字符串的總量。以下是使用這些方法的演示:
var msg = "Hello world!";
console.log(msg.startsWith("Hello")); // true
console.log(msg.endsWith("!")); // true
console.log(msg.includes("o")); // true
console.log(msg.startsWith("o")); // false
console.log(msg.endsWith("world!")); // true
console.log(msg.includes("x")); // false
console.log(msg.startsWith("o", 4)); // true
console.log(msg.endsWith("o", 8)); // true
console.log(msg.includes("o", 8)); // false
6.2 repeat方法
ECMAScript 6 還向字符串添加了 repeat() 方法,它接受一個數(shù)字參數(shù)作為字符串的重復次數(shù)。該方法返回一個重復包含初始字符串的新字符串,重復次數(shù)等于參數(shù)。例如:
console.log("x".repeat(3)); // "xxx"
console.log("hello".repeat(2)); // "hellohello"
console.log("abc".repeat(4)); // "abcabcabcabc"
6.3 字符串模板字面量
模板字面量是 ECMAScript 6 針對 JavaScript 直到 ECMAScript 5 依然缺失的如下功能的回應(yīng):
- 多行字符串 針對多行字符串的形式概念(formal concept)。
- 基本的字符串格式化 將字符串中的變量置換為值的能力。
- 轉(zhuǎn)義 HTML 能將字符串進行轉(zhuǎn)義并使其安全地插入到 HTML 的能力。
模板字面量以一種全新的表現(xiàn)形式解決了這些問題而不需要向 JavaScript 已有的字符串添加額外的功能。
6.3.1 基本語法
使用一對反引號 ``(tab正上方的按鍵)來表示模板字面量。
let message = `Hello world!`; //使用模板字面量創(chuàng)建了一個字符串
console.log(message); // "Hello world!"
console.log(typeof message); // "string"
console.log(message.length); // 12
注意:如果模板字符串中使用到了反引號,則應(yīng)該轉(zhuǎn)義。但是單雙引號不需要轉(zhuǎn)義
6.3.2 多行字符串
在ES5之前JavaScript是不支持多行字符串的。(但是在以前的版本中有一個大家都認為是bug的方式可以寫出多行字符串,就是在尾部添加一個反斜杠 \)
<body>
<script type="text/javascript">
var s = "abc \
aaaaaa";
console.log(s); //但是輸出的結(jié)果中不包括換行
</script>
</body>
但是在ES6中字符串的模板字面量輕松的解決了多行字符串的問題,而且沒有任何新的語法
<script type="text/javascript">
var s = `abc
aaaaa
dsalfja
dfadfja`;
console.log(s);
</script>
但是要注意: 反引號中的所有空格和縮進都是有效字符。
6.3.3 字符串置換
置換允許你將 JavaScript 表達式嵌入到模板字面量中并將其結(jié)果作為輸出字符串中的一部分。
語法:${變量名、表達式、任意運算、方法調(diào)用等}
可以嵌入任何有效的JavaScript代碼
<script type="text/javascript">
var name = "李四";
var msg = `歡迎你${name}同學`;
console.log(msg)
</script>
6.3.4 模板標簽
6.3.4.1 什么是模板標簽
模板字面量真正的強大之處來源于模板標簽。一個模板標簽可以被轉(zhuǎn)換為模板字面量并作為最終值返回。標簽在模板的頭部,即左 ` 字符之前指定,如下所示:
let message = myTag`Hello world`;
在上面的代碼中,myTag就是模板標簽。
myTag其實是一個函數(shù),這個函數(shù)會被調(diào)用來處理這個模板字符串。
6.3.4.2 定義模板標簽
一個標簽僅代表一個函數(shù),他接受需要處理的模板字面量。標簽分別接收模板字面量中的片段,且必須將它們組合以得出結(jié)果。函數(shù)的首個參數(shù)為包含普通 JavaScript 字符串的數(shù)組。余下的參數(shù)為每次置換的對應(yīng)值。
標簽函數(shù)一般使用剩余參數(shù)來定義,以便輕松地處理數(shù)據(jù)。如下:
<script type="text/javascript">
let name = '張三',
age = 20,
message = show`我來給大家介紹${name}的年齡是${age}.`;
/*
應(yīng)該定義一個函數(shù)show:
參數(shù)1:一個字符串數(shù)組。在本例中包含三個元素。
0:"我來給大家介紹"
1:"的年齡是"
2:"."
參數(shù)2和參數(shù)3:表示需要置換的字符串的值。
*/
function show(stringArr, value1, value2) {
console.log(stringArr); //
console.log(value1); // 張三
console.log(value2); // 20
return "abc";
}
console.log(message); //abc
</script>
為了簡化書寫,一般把Value1和Value2寫成剩余字符串的形式
function show(stringArr, ...values){
}
七、解構(gòu)
7.1 解構(gòu)的實用性
在 ECMAScript 5 或更早的版本中,從對象或數(shù)組中獲取特定的數(shù)據(jù)并賦值給本地變量需要書寫很多并且相似的代碼。例如:
let options = {
repeat: true,
save: false
};
// 從對象中提取數(shù)據(jù)
let repeat = options.repeat,
save = options.save;
這段代碼反復地提取在 options 上存儲地屬性值并將它們傳遞給同名的本地變量。雖然這些看起來不是那么復雜,不過想象一下如果你的一大批變量有著相同的需求,你就只能一個一個地賦值。而且,如果你需要從對象內(nèi)部嵌套的結(jié)構(gòu)來查找想要的數(shù)據(jù),你極有可能為了一小塊數(shù)據(jù)而訪問了整個數(shù)據(jù)結(jié)構(gòu)。
這也是 ECMAScript 6 給對象和數(shù)組添加解構(gòu)的原因。當你想要把數(shù)據(jù)結(jié)構(gòu)分解為更小的部分時,從這些部分中提取數(shù)據(jù)會更容易些。很多語言都能使用精簡的語法來實現(xiàn)解構(gòu)操作。ECMAScript 6 解構(gòu)的實際語法或許你已經(jīng)非常熟悉:對象和數(shù)組字面量。
7.2 對象解構(gòu)
7.2.1 對象解構(gòu)的基本形式
對象結(jié)構(gòu)的語法就是在賦值語句的左側(cè)使用類似對象字面量的結(jié)構(gòu)。
let node = {
type: "Identifier",
name: "foo"
};
//這里就相當于聲明了兩個變量: type = node.type; name:node.name
let { type, name } = node;
console.log(type); // "Identifier"
console.log(name); // "foo"
在上面的結(jié)構(gòu)中必須要初始化。否則會出現(xiàn)語法錯誤。
// 語法錯誤!
var { type, name };
// 語法錯誤!
let { type, name };
// 語法錯誤!
const { type, name };
7.2.2 解構(gòu)賦值表達式
如果聲明的變量想改變他們的值,也可以使用解構(gòu)表達式。
<script type="text/javascript">
let node = {
type: "Identifier",
name: "foo"
},
type = "Literal",
name = 5;
//注意:此處必須要在圓括號內(nèi)才能使用解構(gòu)表達式
({type, name} = node);
console.log(type); // "Identifier"
console.log(name); // "foo""
</script>
7.2.3 對象解構(gòu)時的默認值
如果賦值號右邊的對象中沒有與左邊變量同名的屬性,則左邊的變量會是 undefined
let node = {
type: "Identifier",
name: "foo"
};
//因為node中沒有叫value的屬性,所以valued的值將會是undefined
let { type, name, value } = node;
console.log(type); // "Identifier"
console.log(name); // "foo"
console.log(value); // undefined
不過我們也可以手動指定他的默認值。(這個和函數(shù)的參數(shù)默認值很像)
<script type="text/javascript">
let node = {
type: "Identifier",
name: "foo"
};
//手動添加value的默認值為3
let { type, name, value = 3} = node;
console.log(type); // "Identifier"
console.log(name); // "foo"
console.log(value); // 3
</script>
7.2.4 賦值給不同的變量名
在前面的操作中,都是把對象的屬性值,賦值給同名變量。
其實也可以賦值給不同名的變量。
<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>
注意:冒號后面才是要定義的新的變量,這個可以我們的對象字面量不太一樣!
這個地方也可以使用默認值。
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)基本語法
數(shù)據(jù)解構(gòu)的語法和對象解構(gòu)看起來類似,只是將對象字面量替換成了數(shù)組字面量,而且解構(gòu)操作的是數(shù)組內(nèi)部的位置(索引)而不是對象中的命名屬性,例如:
let colors = [ "red", "green", "blue" ];
let [ firstColor, secondColor ] = colors;
console.log(firstColor); // "red"
console.log(secondColor); // "green"
如果只想取數(shù)組中的某一項,則可以不用命名。
let colors = [ "red", "green", "blue" ];
//只取數(shù)組中的第三項。
let [ , , thirdColor ] = colors;
console.log(thirdColor); // "blue"
7.3.2 解構(gòu)表達式
你可以想要賦值的情況下使用數(shù)組的解構(gòu)賦值表達式,但是和對象解構(gòu)不同,沒必要將它們包含在圓括號中,例如:
let colors = [ "red", "green", "blue" ],
firstColor = "black",
secondColor = "purple";
[ firstColor, secondColor ] = colors; //可以不用加括號。當然添加也不犯法
console.log(firstColor); // "red"
console.log(secondColor); // "green"
數(shù)組解構(gòu)表達式有一個很常用的地方,就是交換兩個變量的值。在以前一般定義一個第三方變量進行交換,例如下面的代碼:
<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)表達式
<script type="text/javascript">
let a = 3,
b = 4;
//左側(cè)和前面的案例是一樣的,右側(cè)是一個新創(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之前我們都沒辦法創(chuàng)建私有變量,只能想辦法去封裝。symbol 來創(chuàng)建私有成員,這也是 JavaScript 開發(fā)者長久以來期待的一項特性。
8.1 創(chuàng)建Symbol
Symbol在基本數(shù)據(jù)類型中是比較特別的。我們以前的都可以用字面量去創(chuàng)建基本數(shù)據(jù)類型的數(shù)據(jù),但是Symbol卻不可以使用字面量的是形式去創(chuàng)建。
我們可以使用symbol全局函數(shù)來創(chuàng)建Symbol。
<script type="text/javascript">
let firstName = Symbol(); //創(chuàng)建一個Symbol
let person = {};
person[firstName] = "張三";
console.log(person[firstName]); // "張三"
</script>
說明:上面的代碼中,firstName 作為 symbol 類型被創(chuàng)建并賦值給 person 對象以作其屬性。每次訪問這個屬性時必須使用該 symbol 。
在創(chuàng)建Symbol的時候,也可以傳入字符串,這個字符串也僅僅是在調(diào)試輸出的時候方便,實際沒有啥用處。
<script type="text/javascript">
var s1 = Symbol("abc");
var s2 = Symbol("abc");
console.log(s1 == s2); //false
</script>
注意:任意兩個Symbol都不會相等,即使創(chuàng)建他們的時候使用了相同的參數(shù)。
8.2 識別Symbol
既然 symbol 是基礎(chǔ)類型,你可以使用 typeof 操作符來判斷變量是否為 symbol 。ECMAScript 6 拓展了 typeof 使其操作 symbol 時返回 "symbol"。例如:
let symbol = Symbol();
console.log(typeof symbol); // "symbol"
8.3 Symbol作為屬性名
? 由于每一個Symbol值都是不相等的,這意味著Symbol值可以作為標識符,用于對象的屬性名,就能保證不會出現(xiàn)同名的屬性。這對于一個對象由多個模塊構(gòu)成的情況非常有用,能防止某一個鍵被不小心改寫或覆蓋。
var mySymbol = Symbol();
// 第一種寫法
var a = {};
a[mySymbol] = 'Hello!';
// 第二種寫法
var a = {
[mySymbol]: 'Hello!'
}
以上兩種寫法都是相同的結(jié)果
注意:
symbol作為對象的屬性的時候,只能使用 [ ] 去訪問,不能使用點去訪問。
-
symbol作為對象的屬性名使用的時候,該屬性還是公開屬性,不是私有屬性。但是這個時候使用for... in和for...of
時無法遍歷到這個symbol屬性的。
8.4 Symbol屬性名的遍歷
? Symbol 作為屬性名,該屬性不會出現(xiàn)在for...in、for...of循環(huán)中,也不會被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。但是,它也不是私有屬性,有一個Object.getOwnPropertySymbols方法,可以獲取指定對象的所有 Symbol 屬性名。
看下面的代碼
<script type="text/javascript">
var obj = {};
var a = Symbol('a');
var b = Symbol('b');
obj[a] = 'Hello';
obj[b] = 'World';
// 返回obj對象所有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); // 無輸出 。 因為遍歷不到Symbol型的屬性
}
Object.getOwnPropertyNames(obj);// [] 只能拿到非Symbol類型的屬性
Object.getOwnPropertySymbols(obj) //[Symbol(foo)]
還有一個新API可以拿到所有類型的屬性,包括常規(guī)和Symbol型的。
Reflect.ownKeys
let obj = {
[Symbol('my_key')]: 1,
enum: 2,
nonEnum: 3
};
Reflect.ownKeys(obj);// ["enum", "nonEnum", Symbol(my_key)]
說明:
- 由于以 Symbol 值作為名稱的屬性,不會被常規(guī)方法遍歷得到。我們可以利用這個特性,為對象定義一些非私有的、但又希望只用于內(nèi)部的方法。
8.5 Symbol.for(字符串)和Symbol.keyFor(symbol類型的值)
一、Symbol.for(字符串參數(shù)):在全局環(huán)境中搜索 以該字符串作為參數(shù)的Symbol值,如果搜到則返回這個sybol,如果搜不到則創(chuàng)建一個Symbol,并把它注冊在全局環(huán)境中。
<script type="text/javascript">
//第一次搜不到,則新創(chuàng)建一個返回,并在全局環(huán)境(window)中注冊
var a = Symbol.for("foo");
//第二次搜到上次創(chuàng)建的
var b = Symbol.for("foo");
console.log(a === b); //因為兩次搜到的是同一個Symbol,所以此處是true
</script>
Symbol.for()和Symbol()都可以創(chuàng)建Symbol類型的數(shù)據(jù)。
二者區(qū)別:
- Symbol.for()對同樣的字符串,每次得到結(jié)果肯定是一樣的。因為都是從全局環(huán)境中搜索。
- Symbol()則不會有搜索的過程,每次都是一個全新的不同的symbol,而且也不會向全局環(huán)境中注冊。
看下面的代碼
<script type="text/javascript">
var a = Symbol("foo");
var b = Symbol.for("foo");
console.log(a == b); //false
</script>
二、Symbol.keyFor(symbol):返回一個已經(jīng)注冊的symbol的"key"。
<script type="text/javascript">
var a = Symbol("foo");
var b = Symbol.for("foo");
console.log(Symbol.keyFor(a)); // undefined. 因為a沒有想全局環(huán)境中登記,所以是undefinded
console.log(Symbol.keyFor(b)); // foo
</script>
九、Set數(shù)據(jù)結(jié)構(gòu)
? JavaScript 在絕大部分歷史時期內(nèi)只有一種集合類型,那就是數(shù)組。數(shù)組在 JavaScript 中的使用方式和其它語言很相似,但是其它集合類型的缺乏導致數(shù)組也經(jīng)常被當作隊列(queues)和棧(stacks)來使用。
? 因為數(shù)組的索引只能是數(shù)字類型,當開發(fā)者覺得非數(shù)字類型的索引是必要的時候會使用非數(shù)組對象。這項用法促進了以非類數(shù)組對象為基礎(chǔ)的 set 和 map 集合類型的實現(xiàn)。
Set是類似數(shù)組的一種結(jié)構(gòu),可以存儲數(shù)據(jù),與數(shù)組的區(qū)別主要是 Set中的元素不能重復,而數(shù)組中的元素可以重復。
一句話總結(jié):Set類型是一個包含無重復元素的有序列表
9.1 創(chuàng)建Set和并添加元素
Set本身是一個構(gòu)造函數(shù)。
<script type="text/javascript">
//創(chuàng)建Set數(shù)據(jù)結(jié)構(gòu)對象。
var s = new Set();
//調(diào)用set對象的add方法,向set中添加元素
s.add("a");
s.add("c");
s.add("b");
//set的size屬性可以獲取set中元素的個數(shù)
console.log(s.size)
</script>
9.2 Set中不能添加重復元素
<script type="text/javascript">
var s = new Set();
s.add("a");
s.add("c");
s.add("b");
s.add("a"); //重復,所以添加失敗。注意這個地方并不會保存。
console.log(s.size); // 長度是3
</script>
看下面的代碼:
<script type="text/javascript">
var s = new Set();
s.add(5);
s.add("5");
console.log(s.size); // 長度是2
</script>
? 在上面的代碼中,數(shù)字5和字符串5都會添加成功。為什么呢?
Set是使用什么機制來判斷兩個元素是否相等的呢?
是通過我們前面說過的 Object.is(a, b) 來判斷兩個元素是否相等。
回憶一下:這個方法除了 +0和-0、NaN和NaN認為相等,其余和三個 === 是完全一樣的。
<script type="text/javascript">
var s = new Set();
s.add(+0);
s.add(-0); //重復添加不進去
s.add(NaN);
s.add(NaN); //重復添加不進去
s.add([]);
s.add([]); //兩個空數(shù)組不相等,所以可以添加進去
s.add({});
s.add({}); // 兩個空對象也不重復,所以也可以添加進去
console.log(s.size); // 長度是6
</script>
9.3 使用數(shù)組初始化Set
<script type="text/javascript">
//使用數(shù)組中的元素來初始化Set,當然碰到重復的也不會添加進去。
var s = new Set([2, 3, 2, 2, 4]);
console.log(s.size)
</script>
9.4 判斷一個值是否在Set中
使用Set的 has() 方法可以判斷一個值是否在這個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(要刪除的值) :刪除單個值
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ù)組有個方法forEach可以遍歷數(shù)組。
- Set也有forEach可以遍歷Set。
使用Set的forEach遍歷時的回調(diào)函數(shù)有三個參數(shù):
function (value, key, ownerSet){
}
參數(shù)1:遍歷到的元素的值
參數(shù)2:對set集合來說,參數(shù)2的值和參數(shù)1的值是完全一樣的。
參數(shù)3:這個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)); // 永遠是true
})
</script>
- for…of也可以遍歷set。
for(var v of set){
console.log(v)
}
9.7 將Set轉(zhuǎn)換為數(shù)組
將數(shù)組轉(zhuǎn)換為Set相當容易,你只需要在創(chuàng)建Set集合時把數(shù)組作為參數(shù)傳遞進去即可。
把Set轉(zhuǎn)換為數(shù)組使用前面講到的擴展運算符也很容易
<script type="text/javascript">
let set = new Set([1, 2, 3, 3, 3, 4, 5]),
arr = [...set]; //使用擴展運算符。那么新的數(shù)組中已經(jīng)沒有了重復元素。注意,此對set并沒有什么影響
console.log(arr); // [1,2,3,4,5]
</script>
這種情況在需要去數(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],
//返回的是新的沒有重復元素的數(shù)組。
noDuplicates = eliminateDuplicates(numbers);
console.log(noDuplicates); // [1,2,3,4,5]
</script>
Set提供了處理一系列值的方式,不過如果想給這些值添加一些附加數(shù)據(jù)則顯得力不從心,所以又提供了一種新的數(shù)據(jù)結(jié)構(gòu):Map
十、Map數(shù)據(jù)結(jié)構(gòu)
? ECMAScript 6 中的 map 類型包含一組有序的鍵值對,其中鍵和值可以是任何類型。
? 鍵的比較結(jié)果由 Object.is() 來決定,所以你可以同時使用 5 和 "5" 做為鍵來存儲,因為它們是不同的類型。
? 這和使用對象屬性做為值的方法大相徑庭,因為 對象的屬性會被強制轉(zhuǎn)換為字符串類型。
10.1 創(chuàng)建Map對象和Map的基本的存取操作
- Map創(chuàng)建也是使用Map構(gòu)造函數(shù)
- 向Map存儲鍵值對使用set(key, value);方法
- 可以使用get(key),來獲取指定key對應(yīng)的value
<script type="text/javascript">
var map = new Map();
map.set("a", "lisi");
map.set("b", "zhangsan");
map.set("b", "zhangsan222"); // 第二次添加,新的value會替換掉舊的
console.log(map.get("a"));
console.log(map.get("b")); //zhangsan222
console.log(map.get("c")); //undefined.如果key不存在,則返回undefined
console.log(map.size); //2
</script>
10.2 Map與Set類似的3個方法
- has(key) - 判斷給定的 key 是否在 map 中存在
- delete(key) - 移除 map 中的 key 及對應(yīng)的值
- clear() - 移除 map 中所有的鍵值對
10.3 初始化Map
創(chuàng)建Map的時候也可以像Set一樣傳入數(shù)組。但是傳入的數(shù)組中必須有兩個元素,這個兩個元素分別是一個數(shù)組。
也就是傳入的實際是一個二維數(shù)組!
<script type="text/javascript">
//map接受一個二維數(shù)組
var map = new Map([
//每一個數(shù)組中,第一個是是map的可以,第二個是map的value。如果只有第一個,則值是undefined
["name", "lisi"],
["age", 20],
["sex", "nan"]
]);
console.log(map.size);
console.log(map.get("name"))
</script>
10.4 Map的forEach方法
<script type="text/javascript">
var map = new Map([
["name", "李四"],
["age", 20],
["sex", "nan"]
]);
/*
回調(diào)函數(shù)有函數(shù):
參數(shù)1:鍵值對的value
參數(shù)2:鍵值對的key
參數(shù)3:map對象本身
*/
map.forEach(function (value, key, ownMap) {
console.log(`key=${key} ,vlue=${value}`);
console.log(this);
})
</script>
十一、迭代器和for...of循環(huán)
11.1 循環(huán)問題
var colors = ["red", "green", "blue"];
for (var i = 0, len = colors.length; i < len; i++) {
console.log(colors[i]);
}
上面的代碼寫起來簡單,但是實際使用的過程中,我們需求自己去控制變量,如果有嵌套的情況下,還要控制多個變量,很容易出錯。
迭代器就是為了解決這個問題的。
11.2 什么是迭代器
? 迭代器只是帶有特殊接口(方法)的對象。所有迭代器對象都帶有 next() 方法并返回一個包含兩個屬性的結(jié)果對象。這些屬性分別是 value 和 done,前者代表下一個位置的值,后者在沒有更多值可供迭代的時候為 true 。迭代器帶有一個內(nèi)部指針,來指向集合中某個值的位置。當 next() 方法調(diào)用后,指針下一位置的值會被返回。
? 若你在末尾的值被返回之后繼續(xù)調(diào)用 next(),那么返回的 done 屬性值為 true,value 的值則由迭代器設(shè)定。該值并不屬于數(shù)據(jù)集,而是專門為數(shù)據(jù)關(guān)聯(lián)的附加信息,如若該信息并未指定則返回 undefined 。迭代器返回的值和函數(shù)返回值有些類似,因為兩者都是返回給調(diào)用者信息的最終手段。
我們可以用ES5之前的知識手動創(chuàng)建一個迭代器:
function createIterator(items) {
var i = 0;
return {
next: function() {
var done = (i >= items.length);
var value = !done ? items[i++] : undefined;
return {
done: done,
value: value
};
}
};
}
//創(chuàng)建一個可以在指定數(shù)組上面迭代的迭代器對象。
var iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
// for all further calls
console.log(iterator.next()); // "{ value: undefined, done: true }"
從以上的示例來看,根據(jù) ECMAScript 6 規(guī)范模擬實現(xiàn)的迭代器還是有些復雜。
幸運的是,ECMAScript 6 還提供了生成器,使得迭代器對象的創(chuàng)建容易了許多。
11.3 生成器函數(shù)
生成器函數(shù)就是返回迭代器的函數(shù)!
生成器函數(shù)由 function 關(guān)鍵字和之后的星號(*)標識,同時還能使用新的 yield 關(guān)鍵字。
看下面代碼:
<script type="text/javascript">
//生成器函數(shù)。 注意中間的 * 不能丟
function * createIterator() {
//每個yield的后面的值表示我們迭代到的值。 yield也定義了我們迭代的順序。
yield 3;
yield 4;
yield 2;
}
var it = createIterator();
console.log(it.next().value); // 2
console.log(it.next().value); // 4
console.log(it.next().value); // 2
console.log(it.next().value); //undefined
</script>
迭代器函數(shù)也是函數(shù),所以他可以像正常的函數(shù)一樣調(diào)用,但是生成器函數(shù)會自動返回一個迭代器對象。
每調(diào)用一次迭代器的next方法,如果碰到y(tǒng)ield都會返回一個迭代到的一個對象,然后停止繼續(xù)執(zhí)行,直到下次調(diào)用next方法,會從上次停止的地方繼續(xù)執(zhí)行。
//這個迭代器函數(shù)返回的迭代器可以迭代傳入的數(shù)組中的所有元素。
function *createIterator(items) {
for (let i = 0; i < items.length; i++) {
//每調(diào)用一次next,碰到y(tǒng)ild程序就會停止,并返回迭代到的對象 {value : items[i], done : true}
yield items[i];
}
}
let iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
// 進一步調(diào)用
console.log(iterator.next()); // "{ value: undefined, done: true }"
注意:
- yield 關(guān)鍵字只能 直接用在生成器內(nèi)部 。在其它地方甚至是生成器內(nèi)部的函數(shù)中使用都會拋出語法錯誤。
11.4 生成器函數(shù)表達式
你可以使用函數(shù)表達式來創(chuàng)建生成器,只需在 function 關(guān)鍵字和圓括號之間添加星號(*)。例如:
let createIterator = function *(items) {
for (let i = 0; i < items.length; i++) {
yield items[i];
}
};
let iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
// 進一步調(diào)用
console.log(iterator.next()); // "{ value: undefined, done: true }"
注意:無法使用箭頭函數(shù)來創(chuàng)建生成器。
11.5 可迭代類型和for-of迭代循環(huán)
迭代器的主要工作就是迭代數(shù)據(jù),但是不是所有的數(shù)據(jù)都是可以迭代的。
? 與迭代器緊密相關(guān)的是,可迭代類型是指那些包含 Symbol.iterator 屬性的對象。
? 該 symbol 類型定義了返回迭代器的函數(shù)。在 ECMAScript 6 中,所有的集合對象(數(shù)組,set 和 map)與字符串都是可迭代類型,因此它們都有默認的迭代器。可迭代類型是為了 ECMAScript6 新添加的 for-of 循環(huán)而設(shè)計的。
? 換句話說,默認情況下只有 數(shù)組、set、Map和字符串才可以使用迭代器去迭代。 (也就可以使用for...of了)
? for…of循環(huán)只迭代出來的元素,根本不管索引!不管索引!不管索引!重要的問題重復三遍!
使用 for…of 迭代數(shù)組:
<script type="text/javascript">
var arr = ["a", "c", "b", "d"];
for(var item of arr){
console.log(item)
}
</script>
使用 for…of 迭代Set:
<script type="text/javascript">
var set = new Set(["a", "c", "b", "d"]);
for(var item of set){
console.log(item)
}
</script>
使用 for…of 迭代Map:
<script type="text/javascript">
var map = new Map([["name", "lisi"],["sex", "男"],["age", 20]]);
map.set("aaa", "bbb")
for(var item of map){
console.log(item); //注意:這里迭代到的是由key和value組成的數(shù)組。
}
</script>
使用for … of迭代字符串
<script type="text/javascript">
var s = "abcd";
for(let c of s){
console.log(c)
}
</script>
注意:for...of 只能迭代可以迭代的對象,對于非可迭代對象使用for...of會拋出異常
?
說明:以數(shù)組為例。
? for-of 循環(huán)首先會調(diào)用 values 數(shù)組的 Symbol.iterator 方法來獲取迭代器(Symbol.iterator 方法由幕后的 JavaScript 引擎調(diào)用)。之后再調(diào)用 iterator.next() 并將結(jié)果對象中的 value 屬性值,即 1,2,3,依次賦給 num 變量。當檢測到結(jié)果對象中的 done 為 true,循環(huán)會退出,所以 num 不會被賦值為 undefined 。
? 如果你只想簡單的迭代數(shù)組或集合中的元素,那么 for-of 循環(huán)比 for 要更好。for-of 一般不容易出錯,因為要追蹤的條件更少。所以還是把 for 循環(huán)留給復雜控制條件的需求吧。
11.6 訪問可迭代類型的默認迭代器
Symbol.iterator是可迭代類型的一個方法,調(diào)用這個方法就可以獲取到他的默認迭代器。
<script type="text/javascript">
let s = "abcd";
let it = s[Symbol.iterator](); //調(diào)用字符串的Symbol.iterator方法
console.log(it.next()); //返回迭代器迭代到的第一個對象
</script>
因為Symbol可以返回一個對象的默認迭代器,所以我們可以使用它來判斷一個對象是否可迭代
<script type="text/javascript">
function isIterable(object) {
return typeof object[Symbol.iterator] === "function";
}
console.log(isIterable([1, 2, 3])); // true
console.log(isIterable("Hello")); // true
console.log(isIterable(new Map())); // true
console.log(isIterable(new Set())); // true
console.log(isIterable({"name":"李四"})); // false。普通對象不可迭代
</script>
11.7 自定義可迭代類型
開發(fā)者自定義的對象默認是不可迭代類型,但是你可以為它們創(chuàng)建 Symbol.iterator 屬性并指定一個生成器來使這個對象可迭代。例如:
let collection = {
items: [],
*[Symbol.iterator]() {
for (let item of this.items) {
yield item;
}
}
};
collection.items.push(1);
collection.items.push(2);
collection.items.push(3);
for (let x of collection) {
console.log(x);
}
十二、類
和大多數(shù)面向?qū)ο蟮恼Z言(object-oriented programming language)不同,JavaScript 在誕生之初并不支持使用類和傳統(tǒng)的類繼承并作為主要的定義方式來創(chuàng)建相似或關(guān)聯(lián)的對象。
這很令開發(fā)者困惑,而且在早于 ECMAScript 1 到 ECMAScript 5 這段時期,很多庫都創(chuàng)建了一些實用工具(utility)來讓 JavaScript 從表層上支持類。
盡管一些 JavaScript 開發(fā)者強烈主張該語言不需要類,但由于大量的庫都對類做了實現(xiàn),ECMAScript 6 也順勢將其引入。
12.1 ES5之前的模擬的類
? 在 ECMAScript 5 或更早的版本中,JavaScript 沒有類。和類這個概念及行為最接近的是創(chuàng)建一個構(gòu)造函數(shù)并在構(gòu)造函數(shù)的原型上添加方法,這種實現(xiàn)也被稱為自定義的類型創(chuàng)建,例如:
function PersonType(name) {
this.name = name;
}
PersonType.prototype.sayName = function() {
console.log(this.name);
};
let person = new PersonType("Nicholas");
person.sayName(); // 輸出 "Nicholas"
console.log(person instanceof PersonType); // true
console.log(person instanceof Object); // true
說明:
前面的PersonType我們以前一直叫做構(gòu)造函數(shù),其實他就是一個類型,因為他確實表示了一種類型。
12.2 ES6中基本的類聲明
在ES6直接借鑒其他語言,引入了類的概念。所以再實現(xiàn)上面那種模擬 的類就容易了很多。
//class關(guān)鍵字必須是小寫。 后面就是跟的類名
class PersonClass {
// 等效于 PersonType 構(gòu)造函數(shù)。
constructor(name) { //這個表示類的構(gòu)造函數(shù)。constuctor也是關(guān)鍵字必須小寫。
this.name = name; //創(chuàng)建屬性。 也叫當前類型的自有屬性。
}
// 等效于 PersonType.prototype.sayName. 這里的sayName使用了我們前面的簡寫的方式。
sayName() {
console.log(this.name);
}
}
let person = new PersonClass("Nicholas");
person.sayName(); // 輸出 "Nicholas"
console.log(person instanceof PersonClass); // true
console.log(person instanceof Object); // true
console.log(typeof PersonClass); // "function"
console.log(typeof PersonClass.prototype.sayName); // "function"
說明:
- 自有屬性:屬性只出現(xiàn)在實例而不是原型上,而且只能由構(gòu)造函數(shù)和方法來創(chuàng)建。在本例中,name 就是自有屬性。我建議 盡可能的將所有自有屬性創(chuàng)建在構(gòu)造函數(shù)中,這樣當查找屬性時可以做到一目了然。
- 類聲明只是上例中自定義類型的語法糖。PersonClass 聲明實際上創(chuàng)建了一個行為和 constructor 方法相同的構(gòu)造函數(shù),這也是 typeof PersonClass 返回 "function" 的原因。sayName() 在本例中作為 PersonClass.prototype 的方法,和上個示例中 sayName() 和 PersonType.prototype 關(guān)系一致。這些相似度允許你混合使用自定義類型和類而不需要糾結(jié)使用方式。
雖然類和以前的使用構(gòu)造函數(shù)+原型的方式很像,但是還是有一些不太相同的地方,而且要牢記
- 類聲明和函數(shù)定義不同,類的聲明是不會被提升的。類聲明的行為和 let 比較相似,所以當執(zhí)行流作用到類聲明之前類會存在于暫存性死區(qū)(temporal dead zone)內(nèi)。
- 類聲明中的代碼自動運行在嚴格模式下,同時沒有任何辦法可以手動切換到非嚴格模式。
- 所有的方法都是不可枚舉的(non-enumerable),這和自定義類型相比是個顯著的差異,因為后者需要使用 Object.defineProperty() 才能定義不可枚舉的方法。
- 所有的方法都不能使用 new 來調(diào)用,因為它們沒有內(nèi)部方法 [[Construct]]。
- 不使用 new 來調(diào)用類構(gòu)造函數(shù)會拋出錯誤。也就是 必須使用new 類() 的方式使用
- 試圖在類的方法內(nèi)部重寫類名的行為會拋出錯誤。(因為在類的內(nèi)部,類名是作為一個常量存在的)
12.2 匿名類表達式
函數(shù)有函數(shù)表達式,類也有類表達式。
類表達式的功能和前面的類的聲明是一樣的。
let PersonClass = class {
// 等效于 PersonType 構(gòu)造函數(shù)
constructor(name) {
this.name = name;
}
// 等效于 PersonType.prototype.sayName
sayName() {
console.log(this.name);
}
};
let person = new PersonClass("Nicholas");
person.sayName(); // 輸出 "Nicholas"
console.log(person instanceof PersonClass); // true
console.log(person instanceof Object); // true
console.log(typeof PersonClass); // "function"
console.log(typeof PersonClass.prototype.sayName); // "function"
12.3 具名類表達式
let PersonClass = class PersonClass2{
// 等效于 PersonType 構(gòu)造函數(shù)
constructor(name) {
this.name = name;
}
// 等效于 PersonType.prototype.sayName
sayName() {
console.log(this.name);
}
};
注意:具名類表達式中PersonClass2這個類名只能在類的內(nèi)部訪問到,在外面是訪問不到的.
12.4 作為一等公民的類型
在JavaScript中,函數(shù)是作為一等公民存在的。(也叫一等函數(shù))。
類也是一等公民。
- 類可以作為參數(shù)傳遞
function createObject(classDef) {
return new classDef();
}
let obj = createObject(class {
sayHi() {
console.log("Hi!");
}
});
obj.sayHi(); // "Hi!"
- 立即調(diào)用類構(gòu)造函數(shù),創(chuàng)建單例
let person = new class {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}("Nicholas");
person.sayName(); // "Nicholas"
12.5 動態(tài)計算類成員的命名
類的成員,也可以像我們前面的對象的屬性一樣可以動態(tài)計算.( 使用[ ] 來計算)
let methodName = "sayName";
class PersonClass {
constructor(name) {
this.name = name;
}
[methodName]() {
console.log(this.name);
}
}
let me = new PersonClass("Nicholas");
me.sayName(); // "Nicholas"
12.6 靜態(tài)成員
在ES5中,我們可以直接給構(gòu)造函數(shù)添加屬性或方法來模擬靜態(tài)成員。
function PersonType(name) {
this.name = name;
}
// 靜態(tài)方法。 直接添加到構(gòu)造方法上。 (其實是把構(gòu)造函數(shù)當做一個普通的對象來用。)
PersonType.create = function(name) {
return new PersonType(name);
};
// 實例方法
PersonType.prototype.sayName = function() {
console.log(this.name);
};
var person = PersonType.create("Nicholas");
在上面的create方法在其他語言中一般都是作為靜態(tài)方法來使用的。
下面高能,請注意:
ECMAScript 6 的類通過在方法之前使用正式的 static 關(guān)鍵字簡化了靜態(tài)方法的創(chuàng)建。例如,下例中的類和上例相比是等效的:
class PersonClass {
// 等效于 PersonType 構(gòu)造函數(shù)
constructor(name) {
this.name = name;
}
// 等效于 PersonType.prototype.sayName
sayName() {
console.log(this.name);
}
// 等效于 PersonType.create。
static create(name) {
return new PersonClass(name);
}
}
let person = PersonClass.create("Nicholas");
注意:靜態(tài)成員通過實例對象不能訪問,只能通過類名訪問!!!
通過和ES5模擬靜態(tài)方法的例子你應(yīng)該知道為啥了吧
12.7 ES6中的繼承
在ES6之前要完成繼承,需要寫很多的代碼。看下面的繼承的例子:
<script type="text/javascript">
function Father(name) {
this.name = name;
}
Father.prototype.sayName = function () {
console.log(this.name);
}
function Son(name,age) {
Father.call(this, name);
this.age = age;
}
Son.prototype = new Father();
Son.prototype.constructor = Son;
Son.prototype.sayAge = function () {
console.log(this.age);
}
var son1 = new Son("兒子", 20);
son1.sayAge(); //20
son1.sayName(); //兒子
</script>
12.7.1 繼承的基本寫法
如果在ES6通過類的方式完成繼承就簡單了很多。
需要用到一個新的關(guān)鍵字:extends
<script type="text/javascript">
class Father{
constructor(name){
this.name = name;
}
sayName(){
console.log(this.name);
}
}
class Son extends Father{ //extents后面跟表示要繼承的類型
constructor(name, age){
super(name); //相當于以前的:Father.call(this, name);
this.age = age;
}
//子類獨有的方法
sayAge(){
console.log(this.age);
}
}
var son1 = new Son("李四", 30);
son1.sayAge();
son1.sayName();
console.log(son1 instanceof Son); // true
console.log(son1 instanceof Father); //true
</script>
這種繼承方法,和我們前面提到的構(gòu)造函數(shù)+原型的繼承方式本質(zhì)是一樣的。但是寫起來更簡單,可讀性也更好。
關(guān)于super的使用,有幾點需要注意:
- 你只能在派生類中使用 super(),否則(沒有使用 extends 的類或函數(shù)中使用)一個錯誤會被拋出。
- 你必須在構(gòu)造函數(shù)的起始位置調(diào)用 super(),因為它會初始化 this。任何在 super() 之前訪問 this 的行為都會造成錯誤。也即是說super()必須放在構(gòu)造函數(shù)的首行。
- 在類構(gòu)造函數(shù)中,唯一能避免調(diào)用 super() 的辦法是返回一個對象。
12.7.2 在子類中屏蔽父類的方法
如果在子類中聲明與父類中的同名的方法,則會覆蓋父類的方法。(這種情況在其他語言中稱之為 方法的覆寫、重寫 )
<script type="text/javascript">
class Father{
constructor(name){
this.name = name;
}
sayName(){
console.log(this.name);
}
}
class Son extends Father{ //extents后面跟表示要繼承的類型
constructor(name, age){
super(name); //相當于以前的:Father.call(this, name);
this.age = age;
}
//子類獨有的方法
sayAge(){
console.log(this.age);
}
//子類中的方法會屏蔽到父類中的同名方法。
sayName(){
super.syaName(); //調(diào)用被覆蓋的父類中的方法。
console.log("我是子類的方法,我屏蔽了父類:" + name);
}
}
var son1 = new Son("李四", 30);
son1.sayAge();
son1.sayName();
</script>
如果在子類中又確實需要調(diào)用父類中被覆蓋的方法,可以通過super.方法()來完成。
注意:
- 如果是調(diào)用構(gòu)造方法,則super不要加點,而且必須是在子類構(gòu)造方法的第一行調(diào)用父類的構(gòu)造方法
- 普通方法調(diào)用需要使用super.父類的方法() 來調(diào)用。
12.7.3 靜態(tài)方法也可以繼承
<script type="text/javascript">
class Father{
static foo(){
console.log("我是父類的靜態(tài)方法");
}
}
class Son extends Father{
}
Son.foo(); //子類也繼承了父類的靜態(tài)方法。 這種方式調(diào)用和直接通過父類名調(diào)用時一樣的。
</script>
十三、Moudle
JavaScript 采用 “共享一切” 的代碼加載方式是該語言中最令人迷惑且容易出錯的方面之一。
其它語言使用包(package)的概念來定義代碼的作用范圍,然而在 ECMAScript 6 之前,每個 JavaScript 文件中定義的內(nèi)容都由全局作用域共享。
當 web 應(yīng)用變得復雜并需要書寫更多的 JavaScript 代碼時,上述加載方式會出現(xiàn)命名沖突或安全方面的問題。
ECMAScript 6 的目標之一就是解決作用域的問題并將 JavaScript 應(yīng)用中的代碼整理得更有條理,于是模塊應(yīng)運而生。
很不幸的是:目前,所有的瀏覽器都還不能支持ES6的模塊。只能通過第三方的工具轉(zhuǎn)成ES5的代碼
13.1 什么是模塊
? 模塊是指采取不同于現(xiàn)有加載方式的 JavaScript 文件(與 script 這種傳統(tǒng)的加載模式相對)。這種方式很有必要,因為它和 script 使用不同的語義:
- 模塊中的代碼自動運行在嚴格模式下,并無任何辦法修改為非嚴格模式。
- 模塊中的頂級(top level)變量不會被添加到全局作用域中。它們只存在于各自的模塊中的頂級作用域。
- 模塊頂級作用域中的 this 為 undefined 。
- 模塊不允許存在 HTML 式的注釋(JavaScript 歷史悠久的遺留特性)。
- 模塊必須輸出可被模塊外部代碼使用的相關(guān)內(nèi)容。
- 一個模塊可以引入另外的模塊。
13.2 導出模塊
? 可以使用 export 關(guān)鍵字來對外暴露模塊中的部分代碼。
? 一般情況下,可以在任何變量,函數(shù)或類聲明之前添加這個關(guān)鍵字來輸出它們,
看下面的代碼:
聲明一個文件:a.js 代碼如下
// 輸出變量
export var color = "red";
export let name = "Nicholas";
export const magicNumber = 7;
// 輸出函數(shù)
export function sum(num1, num2) {
return num1 + num1;
}
// 輸出類
export class Rectangle {
constructor(length, width) {
this.length = length;
this.width = width;
}
}
// 該函數(shù)沒有使用export關(guān)鍵字 所以該函數(shù)是模塊私有的。也就是說只能在當前文件訪問,出了這個文件就訪問不到
function subtract(num1, num2) {
return num1 - num2;
}
// 定義一個函數(shù)...
function multiply(num1, num2) {
return num1 * num2;
}
// 可以把這個函數(shù)的引用導出。 和導出函數(shù)是一樣的。
export { multiply };
注意:在上面的代碼中,除了exprot關(guān)鍵字,其他和我們以前的代碼沒有任何不同。
13.3 引入模塊
一旦有了導出內(nèi)容的模塊,則可以在另一個模塊中使用import關(guān)鍵字來獲取他們。
引入模塊的語法:
import { identifier1, identifier2 } from "./a.js";
? import 之后的花括號表示從模塊中引入的綁定。from 關(guān)鍵字表示從哪個模塊引入這些綁定。模塊由一個包含模塊路徑的字符串表示(稱為模塊指示符,module sepcifier)。瀏覽器中的 <script> 元素也使用了這個路徑形式,意味著它必須包含文件擴展名。
大家可以去看看阮一峰老師寫,里面更加詳細。