ES6新語法
前言:
ECMAScript 6.0(以下簡稱ES6)是JavaScript語言的下一代標準,已經在2015年6月正式釋出了。它的目標,是使得JavaScript語言可以用來編寫複雜的大型應用程式,成為企業級開發語言。
ECMAScript的歷史
ES6 從開始制定到最後釋出,整整用了 15 年。
ECMAScript 1.0 是 1997 年釋出的,接下來的兩年,連續釋出了 ECMAScript 2.0(1998 年 6 月)和 ECMAScript 3.0(1999 年 12 月)。3.0 版是一個巨大的成功,在業界得到廣泛支援,成為通行標準,奠定了 JavaScript 語言的基本語法,以後的版本完全繼承。直到今天,初學者一開始學習 JavaScript,其實就是在學 3.0 版的語法。
2000 年,ECMAScript 4.0 開始醞釀。這個版本最後沒有通過,但是它的大部分內容被 ES6 繼承了。因此,ES6 制定的起點其實是** 2000 年**。
為什麼 ES4 沒有通過呢?因為這個版本太激進了,對 ES3 做了徹底升級,導致標準委員會的一些成員不願意接受。ECMA 的第 39 號技術專家委員會(Technical Committee 39,簡稱 TC39)負責制訂 ECMAScript 標準,成員包括 Microsoft、Mozilla、Google 等大公司。
2007 年 10 月,ECMAScript 4.0 版草案發布,本來預計次年 8 月釋出正式版本。但是,各方對於是否通過這個標準,發生了嚴重分歧。以 Yahoo、Microsoft、Google 為首的大公司,反對 JavaScript 的大幅升級,主張小幅改動;以 JavaScript 創造者 Brendan Eich 為首的 Mozilla 公司,則堅持當前的草案。
2008 年 7 月,由於對於下一個版本應該包括哪些功能,各方分歧太大,爭論過於激烈,ECMA 開會決定,中止 ECMAScript 4.0 的開發,將其中涉及現有功能改善的一小部分,釋出為 ECMAScript 3.1,而將其他激進的設想擴大範圍,放入以後的版本,由於會議的氣氛,該版本的專案代號起名為 Harmony(和諧)。會後不久,ECMAScript 3.1 就改名為 ECMAScript 5。
2009 年 12 月,ECMAScript 5.0 版正式釋出。Harmony 專案則一分為二,一些較為可行的設想定名為 JavaScript.next 繼續開發,後來演變成 ECMAScript 6;一些不是很成熟的設想,則被視為 JavaScript.next.next,在更遠的將來再考慮推出。TC39 委員會的總體考慮是,ES5 與 ES3 基本保持相容,較大的語法修正和新功能加入,將由 JavaScript.next 完成。當時,JavaScript.next 指的是 ES6,第六版釋出以後,就指 ES7。TC39 的判斷是,ES5 會在 2013 年的年中成為 JavaScript 開發的主流標準,並在此後五年中一直保持這個位置。
2011 年 6 月,ECMAscript 5.1 版釋出,並且成為 ISO 國際標準(ISO/IEC 16262:2011)。
2013 年 3 月,ECMAScript 6 草案凍結,不再新增新功能。新的功能設想將被放到 ECMAScript 7。
2013 年 12 月,ECMAScript 6 草案發布。然後是 12 個月的討論期,聽取各方反饋。
2015 年 6 月,ECMAScript 6 正式通過,成為國際標準。從 2000 年算起,這時已經過去了 15 年。
等待了多年,總算等到了ES6,下面介紹一部分的新語法,本文介紹的語法如下:
1. 塊級作用域
由於在ES5之前,沒有塊級作用域,有些時候很不方便,所以ES6新增了塊級作用域。
塊級宣告是指該宣告的變數無法被程式碼塊外部訪問。塊作用域,又被稱為詞法作用域(lexical scopes),可以在如下的條件下建立:
- 在函式內部定義
- 在程式碼塊(即 {} )內部定義
塊級作用域是很多類C語言的工作機制,ECMAScript 6 引入塊級宣告的目的是增強 JavaScript 的靈活性,同時又能與其它程式語言保持一致.
1.1 Let宣告
let定義塊級作用域變數
1.沒有變數的提升,必須先宣告後使用.
2.let宣告的變數,不能與前面的let,var,conset宣告的變數重名
//全域性執行上下文
{
//區域性執行上下文
//console.log(a)//此處瀏覽器報錯未宣告
let a = 10;//let定義只能在當前上下文使用
var b = "abc";//全域性作用域變數
console.log(a);//10
//let a = 10//瀏覽器提示錯誤 Uncaught SyntaxError: Identifier 'a' has already been declared
console.log(b);//bac
}
console.log(b);//abc
// console.log(a);//瀏覽器提示錯誤
複製程式碼
1.2 Const宣告
const 定義只讀變數
1.const宣告變數的同時必須賦值,const宣告的變數必須初始化,一旦初始化完畢就不允許修改
2.const宣告變數也是一個塊級作用域變數
3.const宣告的變數沒有“變數的提升”,必須先宣告後使用
4.const宣告的變數不能與前面的let,var,const宣告的變數重名
{
const con = "abc"
console.log(con)//也只能在當前執行上下文中使用
}
//console.log(con)//con is not defined 瀏覽器提示錯誤,con未宣告
複製程式碼
1.3 迴圈中的塊級繫結
使用var宣告的迴圈變數在迴圈結束後仍然可以訪問到。 使用let宣告的迴圈變數,在迴圈結束之後會立即銷燬。
for(let i = 0; i < 3; i++){ // 迴圈結束之後會立即銷燬 i
console.log(i);
}
console.log(i); //此處無法訪問到 i 。
複製程式碼
1.4 迴圈中的函式
看下面的程式碼,是輸出10個10,而不是0,1,2,…
var funcs = [];
for (var i = 0; i < 10; i++) {
funcs.push(function () {
console.log(i);
});
}
funcs.forEach(function (func) {
func(); // 輸出 "10" 共10次
});
複製程式碼
解決辦法需要使用函式的自執行特性。
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規範中專門定義的特性。在for … in和for … of迴圈中也適用
var funcs = [];
for (let i = 0; i < 10; i++) {
funcs.push(function () {
console.log(i);
});
}
funcs.forEach(function (func) {
func(); // 輸出 0,1,2 ... 9
})
複製程式碼
說明:let 宣告使得每次迭代都會建立一個變數 i,所以迴圈內部建立的函式會獲得各自的變數 i 的拷貝。每份拷貝都會在每次迭代的開始被建立並被賦值。
2.神奇的解構賦值
2.1 什麼是解構
ES6允許按照一定模式,從陣列和物件中提取值,對變數進行賦值,這被稱為解構(Destructuring)。解構只能用於陣列或物件,所以應該注意,其他原始型別的值都可以轉為相應的物件 ,除了undefined和null本質上。
解構寫法屬於“模式匹配”,只要等號兩邊的模式相同,左邊的變數就會被賦予對應的值。
2.2 物件的解構
2.2.1 物件解構的基本形式
物件結構的語法就是在賦值語句的左側使用類似物件字面量的結構。
let node = {
name: "阿貓",
age:20
};
//這裡就相當於宣告瞭兩個變數: type = node.type; name:node.name
let { name,age } = node;
console.log(name); // "阿貓"
console.log(age); // 20
複製程式碼
在上面的結構中必須要初始化。否則會出現語法錯誤。
var { name,age };// 語法錯誤!
let { name,age };// 語法錯誤!
const { name,age };// 語法錯誤!
複製程式碼
2.2.2 解構賦值表示式
如果宣告的變數想改變他們的值,也可以使用解構表示式。
let node = {
name: "阿貓",
age:20
},
name = "阿狗",
age = 5;
//注意:此處必須要在圓括號內才能使用解構表示式
({ name,age } = node);
console.log(name); // "阿貓""
console.log(age); // 20
複製程式碼
2.2.3 物件解構時的預設值
如果賦值號右邊的物件中沒有與左邊變數同名的屬性,則左邊的變數會是 undefined
let node = {
name: "阿貓",
age:20
};
//因為node中沒有叫value的屬性,所以valued的值將會是undefined
let { name, age, value } = node;
console.log(name); // "阿貓"
console.log(age); // 20
console.log(value); // undefined
複製程式碼
不過我們也可以手動指定他的預設值。(這個和函式的引數預設值很像)
let node = {
name: "阿貓",
age:20
};
//手動新增value的預設值為3
let { name, age, value = 3} = node;
console.log(name); // "阿貓"
console.log(age); // 20
console.log(value); // 3
複製程式碼
2.2.4 賦值給不同的變數名
在前面的操作中,都是把物件的屬性值,賦值給同名變數。
其實也可以賦值給不同名的變數。
let node = {
name: "阿貓",
age:20
};
// localName才是要定義的新的變數。 name是node的屬性
let {name: localName, age: localAge} = node;
console.log(localName); // "阿貓"
console.log(localAge); // 20
複製程式碼
注意:冒號後面才是要定義的新的變數,這個可以我們的物件字面量不太一樣!
這個地方也可以使用預設值。
let node = {
type: "Identifier"
};
let { type: localType, name: localName = "bar" } = node;
console.log(localType); // "Identifier"
console.log(localName); // "bar"
複製程式碼
2.3 陣列的解構
2.3.1 陣列解構基本語法
資料解構的語法和物件解構看起來類似,只是將物件字面量替換成了陣列字面量,而且解構操作的是陣列內部的位置(索引)而不是物件中的命名屬性,例如:
let colors = [ "red", "green", "blue" ];
let [ firstColor, secondColor ] = colors;
console.log(firstColor); // "red"
console.log(secondColor); // "green"
如果只想取陣列中的某一項,則可以不用命名。
let colors = [ "red", "green", "blue" ];
//只取陣列中的第三項。
let [ , , thirdColor ] = colors;
console.log(thirdColor); // "blue"
複製程式碼
2.3.2 解構表示式
你可以想要賦值的情況下使用陣列的解構賦值表示式,但是和物件解構不同,沒必要將它們包含在圓括號中,例如:
let colors = [ "red", "green", "blue" ],
firstColor = "black",
secondColor = "purple";
[ firstColor, secondColor ] = colors; //可以不用加括號。當然新增也不犯法
console.log(firstColor); // "red"
console.log(secondColor); // "green"
複製程式碼
陣列解構表示式有一個很常用的地方,就是交換兩個變數的值。在以前一般定義一個第三方變數進行交換,例如下面的程式碼:
let a = 3,
b = 4,
temp;
temp = a;
a = b;
b = temp;
console.log(a);
console.log(b)
複製程式碼
那麼在ES6中完全可以拋棄第三方變數這種方式,使用我們的陣列解構表示式
let a = 3,
b = 4;
//左側和前面的案例是一樣的,右側是一個新建立的陣列字面量。
[a, b] = [b, a];
console.log(a);
console.log(b)
複製程式碼
2.4 解構JSON
var jike = {"name":"tom","age":"23","sex":"男"};
var {name,age,sex}=jike;
console.log(name,age,sex)//tom 23 男
function cal(a,b){
var ret1 = a+b;
var ret2 = a-b;
var ret3 = a*b;
var ret4 = a/b;
return [ret1,ret2,ret3,ret4]
}
var [r1,r2,r3,r4] = cal(10,5);
console.log(r1,r2,r3,r4)//15 5 50 2
複製程式碼
3. 字串功能的增強
3.1 查詢子字串
- includes(要查詢的文字) 判斷字串內是否有此文字
- startsWith(要查詢的文字) 判斷文字是否在字串開頭
- endsWith(要查詢的文字) 判斷文字是否在字串結尾
使用如下:
var msg = "Hello world!";
console.log(msg.startsWith("Hello")); // true
console.log(msg.startsWith("o")); // false
console.log(msg.startsWith("o", 4)); // true
console.log(msg.endsWith("!")); // true
console.log(msg.endsWith("world!")); // true
console.log(msg.endsWith("r", 9)); // true
console.log(msg.includes("o")); // true
console.log(msg.includes("x")); // false
console.log(msg.includes("o", 8)); // false
複製程式碼
- trim():除去字串左右空格的。
- trimLeft():除去字串的左邊空格
- rimRight():除去字串的右邊空
var msg = " Hello "
console.log(msg)//" Hello "
console.log(msg.trim())//"Hello"
console.log(msg.trimLeft())//"Hello "
console.log(msg.trimRight())//" Hello"
複製程式碼
一般配合正規表示式,用的比較的。
- repeat(複製的次數):複製字串
var msg = "abc"
console.log(msg.repeat(3));//abcabcabc
複製程式碼
- padStart(補齊的位數,填充的的字元):用提供的字元,向前補齊位數
var msg = "abc"
console.log(msg.padStart(10,"*"));//*******abc
複製程式碼
- padEnd(補齊的位數,填充的的字元):用提供的字元,向前補齊位數
var msg = "abc"
console.log(msg.padEnd(10,"*"));//abc*******
複製程式碼
3.2 模板字串
模板字面量是 ECMAScript 6 針對 JavaScript 直到 ECMAScript 5 依然缺失的如下功能的迴應:
多行字串 針對多行字串的形式概念(formal concept)。 基本的字串格式化 將字串中的變數置換為值的能力。 轉義 HTML 能將字串進行轉義並使其安全地插入到 HTML 的能力。 模板字面量以一種全新的表現形式解決了這些問題而不需要向 JavaScript 已有的字串新增額外的功能。
3.2.1 基本語法
使用一對反引號 “(tab正上方的按鍵)來表示模板字面量。
let message = `Hello world!`; //使用模板字面量建立了一個字串
console.log(message); // "Hello world!"
console.log(typeof message); // "string"
console.log(message.length); // 12
複製程式碼
注意:如果模板字串中使用到了反引號,則應該轉義。但是單雙引號不需要轉義
3.2.2 多行字串
在ES5之前JavaScript是不支援多行字串的。(但是在以前的版本中有一個大家都認為是bug的方式可以寫出多行字串,就是在尾部新增一個反斜槓 \)
var s = "abc \
aaaaaa";
console.log(s); //但是輸出的結果中不包括換行
複製程式碼
但是在ES6中字串的模板字面量輕鬆的解決了多行字串的問題,而且沒有任何新的語法
var s = `abc
aaaaa
dsalfja
dfadfja`;
console.log(s);
複製程式碼
但是要注意: 反引號中的所有空格和縮排都是有效字元。
4. 陣列的擴充套件
4.1 陣列推導
陣列推導就是直接通過現有陣列生成新陣列的一種簡化寫法,通過for...of結構,允許多重迴圈。注:新陣列會立即在記憶體中生成,這時如果原陣列是一個很大的陣列,將會非常耗費記憶體。
var a1 = [1, 2, 3, 4];
var a2 = [for (i of a1) i * 2];
a2 // [2, 4, 6, 8]
var years = [ 1954, 1974, 1990, 2006, 2010, 2014 ];
[for (year of years) if (year > 2000 && year < 2010) year];
// [ 2006]
複製程式碼
4.2 陣列處理擴充套件方法
Array.from():用於將兩類物件轉為真正的陣列:類似陣列的物件(array-like object)和可遍歷(iterable)的物件,其中包括ES6新增的Set和Map結構。Array.from()還可以接受第二個引數,作用類似於陣列的map方法,用來對每個元素進行處理。
Array.from({ 0: "a", 1: "b", 2: "c", length: 3 });
// [ "a", "b" , "c" ]
Array.from(arrayLike, x => x * x);
// 等同於
Array.from(arrayLike).map(x => x * x);
複製程式碼
Array.of()方法用於將一組值,轉換為陣列。彌補陣列建構函式Array()的不足。
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array(3) // [undefined, undefined, undefined]
複製程式碼
陣列例項的find()用於找出第一個符合條件的陣列元素;陣列例項的findIndex()用於返回第一個符合條件的陣列元素的位置。這兩個方法都可以發現NaN,彌補了IndexOf()的不足。
陣列例項的fill()使用給定值,填充一個陣列。
陣列例項的entries(),keys()和values()用於遍歷陣列,它們都返回一個遍歷器,可以用for...of迴圈進行遍歷。keys()是對鍵名的遍歷、values()是對鍵值的遍歷,entries()是對鍵值對的遍歷。
5. 物件功能的擴充
5.1 建立物件
ES5中建立物件的幾種方法
- 字面量法
- 工廠模式
- 建構函式
- 組合方式 建構函式+原型模式
在ES6中可以使用class建立一個物件
class 類名{
constructor(引數){
this.屬性 = 引數;
}
running{//物件中簡寫方法,省略了function
}
}
複製程式碼
注意:
- lass 是關鍵字,後面緊跟類名,類名首字母大寫,採取的是大駝峰命名法則。類名之後是{}。
- 在{}中,不能直接寫語句,只能寫方法,方法不需要使用關鍵字。
- 方法和方法之間沒有逗號。不是鍵值對
5.2 新增的方法
5.2.1 屬性相關的方法
(1) Obect.getOwnProertyDescriptor();
獲取一個物件中某個屬性的詳細資訊。
var a = 1;
console.log(Object.getOwnPropertyDescriptor(window,"a"))
複製程式碼
瀏覽器列印如下:
{value: 1, writable: true, enumerable: true, configurable: false}
configurable:false
enumerable:true
value:1
writable:true
__proto__:Object
複製程式碼
(2) Object.defineProperty( )
精細化設定一個物件的屬性的
- configurable:是否可刪除
- writable:是否可修改
- enumerale:是否可以列舉
- value:值
var obj = {};
Object.defineProperty(obj,"name",{
configurable:false,
writable:true,
enumerale:false,
value:"阿毛"
})
複製程式碼
瀏覽器列印如下:
> delete obj.name;
< false
> obj.name = "阿貓"
< "阿貓"
> obj.name
< "阿貓"
複製程式碼
(3) Object.defineProperties()
var obj = {};
Object.defineProperties(obj,{
"name":{
configurable:false,
writable:false,
enumerable:false,
value:"阿毛"
},
"age":{
configurable:true,
writable:true,
enumerable:true,
value:"20"
}
});
複製程式碼
瀏覽器列印如下:
> obj
< {age: "20", name: "阿毛"}
> delete obj.name;
< false
> delete obj.age
< true
> obj
< {name: "阿毛"}
複製程式碼
(4) getOwnPropertyNames()
獲取自的屬性,以陣列的格式返回的。
var obj = {
name:"阿毛",
age:20
};
複製程式碼
瀏覽器列印如下:
> Object.getOwnPropertyNames(obj);
< (2) ["name", "age"]
0: "name"
1: "age"
length: 2
__proto__: Array(0)
複製程式碼
(5) Object.keys()
var obj = {
name:"阿毛",
age:20
};
複製程式碼
瀏覽器列印如下:
> Object.getOwnPropertyNames(obj);
< (2) ["name", "age"]
0: "name"
1: "age"
length: 2
__proto__: Array(0)
複製程式碼
使用Object.getOwnPropertyNames()和Object.keys()都可以得到一個物件的屬性名,屬性名是放在一個陣列中的。
對於物件的遍歷目前有三種方式:
- for in
- Object.keys()
- Object.getOwnPropertyNames()
for in : 會輸出自身以及原型鏈上可列舉的屬性。 Object.keys() : 用來獲取物件自身可列舉的屬性鍵 Object.getOwnPropertyNames() : 用來獲取物件自身的全部屬性名
(6) Object.values();
獲取物件的值,放到陣列中。
var obj = {
name:"阿毛",
age:20
};
複製程式碼
瀏覽器列印如下:
> Object.values(obj);
< (2) ["阿毛", 20]
複製程式碼
5.2.2 繼承相關的方法
(1) Object.create()
使用Object.create比較適合對字面量物件的繼承。
(2) getPrototypeOf
getPrtotypeOf 獲取建立這個物件的那個構造器的prototype屬性。
var obj = {
name : "阿毛",
age : 20
}
console.log(Object.getPrototypeOf(obj) == Object.prototype);
console.log(Object.getPrototypeOf([]) == Array.prototype);
複製程式碼
true
true
複製程式碼
5.2.3 防篡改方法
可以對物件進行保護,分成三個級別:
(1) 設定不可擴充: preventExtensions()
Object.preventExtensions()方法讓一個物件變的不可擴充套件,也就是永遠不能再新增新的屬性。如下:
var obj = {
name : "阿貓",
age : 20
}
Object.preventExtensions(obj);
obj.name = "阿狗";
delete obj.age;
obj.sex = "man";
console.log(obj);
複製程式碼
瀏覽器列印:
{ name: '阿狗' }
複製程式碼
(2) 封閉物件:seal()
Object.seal()方法封閉一個物件,阻止新增新屬性並將所有現有屬性標記為不可配置。當前屬性的值只要可寫就可以改變。
var obj = {
name : "阿貓",
age : 20
}
Object.seal(obj);
obj.name = "阿狗";
delete obj.age;
obj.sex = "man";
console.log(obj);
複製程式碼
瀏覽器列印:
{ name: '阿狗', age: 20 }
複製程式碼
(3) 凍結物件: freeze()
Object.freeze() 方法可以凍結一個物件,凍結指的是不能向這個物件新增新的屬性,不能修改其已有屬性的值,不能刪除已有屬性,以及不能修改該物件已有屬性的可列舉性、可配置性、可寫性。該方法返回被凍結的物件。如下:
var obj = {
name : "阿貓",
age : 20
}
Object.freeze(obj);
obj.name = "阿狗";
delete obj.age;
obj.sex = "man";
console.log(obj);
複製程式碼
瀏覽器列印:
{ name: '阿貓', age: 20 }
複製程式碼
也可以使用isExtensible, isSealed, isFrozen來判斷當處處理如個保護狀態,如下:
1.Object.isExtensible() 方法判斷一個物件是否是可擴充套件的(是否可以在它上面新增新的屬性)。
==只有這個返回值預設為true,因為這個方法是判斷是否可以擴充,而預設的是可以==
var obj = {
name : "阿貓",
age : 20
}
console.log(Object.isExtensible(obj));
console.log(Object.isSealed(obj));
console.log(Object.isFrozen(obj));
Object.preventExtensions(obj)
console.log(Object.isExtensible(obj));
console.log(Object.isSealed(obj));
console.log(Object.isFrozen(obj));
複製程式碼
瀏覽器列印如下:
true
false
false
{ name: '阿狗' }
false
false
false
複製程式碼
2. Object.isSealed() 方法判斷一個物件是否被密封。
var obj = {
name : "阿貓",
age : 20
}
Object.seal(obj)
console.log(Object.isExtensible(obj));
console.log(Object.isSealed(obj));
console.log(Object.isFrozen(obj));
複製程式碼
瀏覽器列印如下:
{ name: '阿狗', age: 20 }
false
true
false
複製程式碼
3. Object.isFrozen()方法判斷一個物件是否被凍結。
var obj = {
name : "阿貓",
age : 20
}
Object.freeze(obj)
console.log(Object.isExtensible(obj));
console.log(Object.isSealed(obj));
console.log(Object.isFrozen(obj));
複製程式碼
瀏覽器列印如下:
{ name: '阿貓', age: 20 }
false
true
true
複製程式碼
5.2.4 Object.assign
Object.assign就去用於物件的合併,將源物件的所有的可列舉屬性,複製到目標物件。
程式碼如下:
var obj1 = {};
var obj2 = {
name:"阿貓",
age:20
}
var obj3 = {
name:"阿狗"
}
Object.assign(obj1,obj2,obj3)//obj1是目標物件,obj2、obj3都是源物件
console.log(obj1);
複製程式碼
瀏覽器列印如下:
{ name: '阿狗', age: 20 }
複製程式碼
==注意細節:==
- 第一個引數是目標物件,assign這個方法把其它的引數中的屬性全部加在第一個引數身上。
- 第一個引數在屬性複製過程中,可以會被修改,後面的會覆蓋前面的屬性
- assign這個方法的返回值就是第一個引數的引用,也就是返回了第一個引數。
- aassign這個方法會把原型上面的發展也拷貝了。
- assign不能拷貝繼承過來的屬性
- assign也不拷貝不可列舉的屬性
如下:
var obj1 = {};
var obj2 = {
name : "阿貓"
}
Object.defineProperty(obj2,"age",{
configurable:true,
writable:true,
value:20,
enumerable:false
})
Object.assign(obj1,obj2);
console.log(obj1);
複製程式碼
瀏覽器列印如下:
{ name: '阿貓' }
複製程式碼
- assign是淺複製,如下:
var obj1 = {};
var obj2 = {
name:"阿貓",
friends:["阿狗","阿郎"]
}
Object.assign(obj1,obj2);
console.log(obj1);
obj2.friends.push("阿毛");
console.log(obj1)
複製程式碼
瀏覽器列印如下:
{ name: '阿貓', friends: [ '阿狗', '阿郎' ] }
{ name: '阿貓', friends: [ '阿狗', '阿郎', '阿毛' ] }
複製程式碼
在對源物件的應用型別資料修改後,目標物件也修改了
5.3 物件擴充套件運算子
陣列中的擴充套件運算子:
var arr1 = [1,2,3];
var arr2 = [...arr1]
console.log(arr2);
複製程式碼
作用:用於取出引數物件的所有可遍歷屬性,拷貝當前物件中。
var obj1 = {
name : "阿貓",
age : 20,
friends : ["阿狗,阿毛"]
}
var obj2 = {...obj1}
console.log(obj2);
obj1.friends.push("阿郎")
console.log(obj2);
複製程式碼
效果如下
{ name: '阿貓', age: 20, friends: [ '阿狗,阿毛' ] }
{ name: '阿貓', age: 20, friends: [ '阿狗,阿毛', '阿郎' ] }
複製程式碼
還可以這樣用:
var obj1 = {
name : "阿貓",
}
var obj2 = {
age : 20
}
var obj3 = {...obj1,...obj2}
console.log(obj3);
複製程式碼
列印如下:
{ name: '阿貓', age: 20 }
複製程式碼
6. 函式的新增特性
6.1.1 函式引數的預設值
JavaScript函式的最大的一個特點就是在傳遞引數的時候,引數的個數不受限制的。為了健壯性考慮,一般在函式內部需要做一些預設值的處理。
function makeRequest(url, timeout, callback) {
timeout = timeout || 2000;
callback = callback || function() {};
}
複製程式碼
其實上面的預設值方法有個bug:當timeout是0的時候也會當做假值來處理,從而給賦值預設值2000.
ES6從語言層面面上增加了 預設值的 支援。看下面的程式碼:
//這個函式如果只傳入第一個引數,後面兩個不傳入,則會使用預設值。如果後面兩個也傳入了引數,則不會使用預設值。
function makeRequest(url, timeout = 2000, callback = function() {}) {
// 其餘程式碼
複製程式碼
6.1.2 預設引數對 arguments 物件的影響
在非嚴格模式下,arguments總是能反映出命名引數的變化。看下面的程式碼:
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);
複製程式碼
在ES5的嚴格模式下,arguments只反映引數的初始值,而不再反映命名引數的變化!
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);
複製程式碼
當使用ES6引數預設值的時候,不管是否是在嚴格模式下,都和ES5的嚴格模式相同。看下面的程式碼:
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沒有使用預設值,但是仍然表現的和嚴格模式一樣。
console.log(arguments[1] === b); //false。 b使用了預設值,所以表現的和嚴格模式一樣。
}
foo(1, 2);
複製程式碼
注意:如果這樣呼叫foo(1),則 a == 1, b == 30, arguments[0] == 1, arguments[1] == undefined。也就是說預設值並不會賦值給arguments引數。
6.1.3 預設參數列達式 (Default Parameter Expressions)
引數的預設值,也可以是一個表示式或者函式呼叫等。看下面的程式碼
function getValue() {
return 5;
}
function add(first, second = getValue()) { //表示使用getValue這個函式的返回值作為second的預設值。
return first + second;
}
console.log(add(1, 1)); // 2. 呼叫add函式的時候,傳入了第二個引數,則以傳入的引數為準。
console.log(add(1)); // 6。 呼叫add函式的時候,沒有傳入第二個引數,則會呼叫getValue函式。
複製程式碼
有一點需要要注意:getValue()只會在呼叫add且不傳入第二個引數的時候才會去呼叫。不是在解析階段呼叫的。
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
複製程式碼
由於預設值可以表示式,所以我們甚至可以使用前面的引數作為後面引數的預設值。
function add(first, second = first) { // 使用第一個引數作為第二個引數的預設值
return first + second;
}
複製程式碼
注意:可以把前面的引數作為後面引數的預設值,但是不能把後面的引數作為第一個引數的預設值。這可以前面說的let和const的暫存性死區一個意思。
function add(first = second, second)) { // 這種寫法是錯誤的
return first + second;
}
複製程式碼
6.2 箭頭函式
ECMAScript 6 最有意思的部分之一就是箭頭函式。正如其名,箭頭函式由 “箭頭”(=>)這種新的語法來定義。
其實在別的語言中早就有了這種語法結構,不過他們叫拉姆達表示式。
6.2.1 箭頭函式語法
基本語法如下:
(形參列表)=>{
//函式體
}
複製程式碼
箭頭函式可以賦值給變數,也可以像匿名函式一樣直接作為引數傳遞。
示例1:
var sum = (num1, num2) =>{
return num1 + num2;
}
console.log(sum(3, 4));
//前面的箭頭函式等同於下面的傳統函式
var add = function (num1, num2) {
return num1 + num2;
}
console.log(add(2, 4))
複製程式碼
如果函式體內只有一行程式碼,則包裹函式體的 大括號 ({ })完全可以省略。如果有return,return關鍵字也可以省略。 如果函式體內有多條語句,則 {} 不能省略。
示例2:
var sum = (num1, num2) => num1 + num2;
console.log(sum(5, 4));
//前面的箭頭函式等同於下面的傳統函式
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
複製程式碼
如果箭頭函式只有一個引數,則包裹引數的小括號可以省略。其餘情況下都不可以省略。當然如果不傳入引數也不可以省略
示例3:
var foo = a=> a+3; //因為只有一個引數,所以()可以省略
console.log(foo(4)); // 7
複製程式碼
如果沒有需要傳入的引數,而且還不想新增傳統的大括號和return,則必須給整個物件新增一個小括號 ()
示例4:
var foo = ()=>({name:"lisi", age:30});
console.log(foo());
//等同於下面的;
var foo1 = ()=>{
return {
name:"lisi",
age : 30
};
}
複製程式碼
6.2.2 使用箭頭函式實現函式自執行
var person = (name => {
return {
name: name,
age: 30
}
}
)("zs");
console.log(person);
複製程式碼
6.2.3 箭頭函式中無this繫結
在ES5之前this的繫結是個比較麻煩的問題,稍不注意就達不到自己想要的效果。因為this的繫結和定義位置無關,只和呼叫方式有關。
在箭頭函式中則沒有這樣的問題,在箭頭函式中,this和定義時的作用域相關,不用考慮呼叫方式
箭頭函式沒有 this 繫結,意味著 this 只能通過查詢作用域鏈來確定。如果箭頭函式被另一個不包含箭頭函式的函式囊括,那麼 this 的值和該函式中的 this 相等,否則 this 的值為 window。
var PageHandler = {
id: "123456",
init: function () {
document.addEventListener("click",
event => this.doSomething(event.type), false); // 在此處this的和init函式內的this相同。
},
doSomething: function (type) {
console.log("Handling " + type + " for " + this.id);
}
};
PageHandler.init();
複製程式碼
看下面的一段程式碼:
var p = {
foo:()=>console.log(this) //此處this為window
}
p.foo(); //輸出為 window物件。 並不是我想要的。所以在定義物件的方法的時候應該避免使用箭頭函式。
//箭頭函式一般用在傳遞引數,或者在函式內部宣告函式的時候使用。
複製程式碼
說明:
箭頭函式作為一個使用完就扔的函式,不能作為建構函式使用。也就是不能使用new 的方式來使用箭頭函式。 由於箭頭函式中的this與函式的作用域相關,所以不能使用call、apply、bind來重新繫結this。但是雖然this不能重新繫結,但是還是可以使用call和apply方法去執行箭頭函式的。
6.2.4 無arguments繫結
雖然箭頭函式沒有自己的arguments物件,但是在箭頭函式內部還是可以使用它外部函式的arguments物件的。
function foo() {
//這裡的arguments是foo函式的arguments物件。箭頭函式自己是沒有 arguments 物件的。
return ()=>arguments[0]; //箭頭函式的返回值是foo函式的第一個引數
}
var arrow = foo(4, 5);
console.log(arrow()); // 4
複製程式碼
7. 新增資料結構
7.1 Set資料結構
set和資料差不多,也是一種集合,區別在於:它裡面的值都是唯一的,沒有重複的。
建立一個set物件如下
var setList = new Set();
setList.add("a");
setList.add("c");
setList.add("b");
setList.add("a"); //重複,所以新增失敗。注意這個地方並不會儲存。
console.log(setList.size); // 長度是3
複製程式碼
set的方法有add()、has()、delete()、clear()等方法,分別用於新增、查詢、刪除、和清空的功能。
var setList = new Set(["a","b","c","d"]);
console.log(setList);//{"a", "b", "c", "d"}
setList.add("e");
console.log(setList);//{"a", "b", "c", "d", "e"}
console.log(setList.has("b"));//true
setList.delete("c");
console.log(setList);//{"a", "b", "d", "e"}
setList.clear();
console.log(setList);//{}
複製程式碼
使用set可以輕鬆的實現將一組資料去重
var arr1 = [1,1,2,2,3,3];
var arr2 = [...(new Set(arr1))]
console.log(arr2);//[1,2,3]
複製程式碼
7.2 Map資料結構
它類似於物件,裡面存放也是鍵值對,區別在於:物件中的鍵名只能是字串,如果使用map,它裡面的鍵可以是任意值。
建立一個map物件如下:
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
複製程式碼
map中的方法有:
- has(key) - 判斷給定的 key 是否在 map 中存在
- delete(key) - 移除 map 中的 key 及對應的值
- clear() - 移除 map 中所有的鍵值對
var map = new Map();
map.set("a","a");
map.set("b","b");
map.set("c","c");
console.log( map.get("a"))//a
console.log( map.has("a") ) //true
console.log( map.has("d") ) //false
map.clear();
console.log(map);//Map {}
複製程式碼
8.Class
ES6從形式上,向主流的物件導向的語言靠攏,前面我們都是建立構造器,然後去new構造器,構造器就相當於一個類,在ES6中,就可以使用class來建立物件了。
8.1 class建立物件
上文說到,通過class建立物件的格式如下:
class 類名{
constructor(引數){
this.屬性 = 引數;
}
running{//物件中簡寫方法,省略了function
}
}
複製程式碼
不使用Class,去建立一個類,如下:
function persion(name,age){
this.name = name,
this.age = age;
}
persion.prototype.say = function(){
console.log(`我是${this.name},${this.age}歲`)
}
var p1 = new persion("阿毛",20);
複製程式碼
在ES6中,可以使用class來宣告一個類了,如下:
class Persion{
constructor(name,age){
this.name = name;
this.age = age;
}
say(){
console.log(`${this.name}的年齡是${this.age}`)
}
}
var p1 = new Persion("abc",12);
p1.say();
複製程式碼
在瀏覽器中訪問之,如下:
abc的年齡是12
複製程式碼
雖然使用class非常給力,但是class是屬性JS中高階的語法,可以轉成低階的語法,如下Babel網站
8.2使用extends實現繼承
格式:
class 子類 extends 父類{
constructor(引數){
super(引數);
this.引數 = 值;
}
}
複製程式碼
注意:
- 使用 extends 關鍵字來實現繼承
- 在子類中的構造器 constructor 中,必須要顯式呼叫父類的 super 方法,如果不呼叫,則 this 不可用 在ES5的繼承如下:
function Persion(name,age){
this.name = name;
this.age = age;
}
Persion.prototype.say = function(){
console.log(`I'm ${this.name} and ${this.age}`);
}
function GoodMan(name,age,sex){
//繼承屬性
Persion.call(this,name,age);
this.sex = sex;
}
//繼承方法 利用淺拷貝繼承方法
for (var p in Persion.prototype) {
GoodMan.prototype[p] = Persion.prototype[p];
}
//GoodMan 自己的方法
GoodMan.prototype.do = function () {
console.log(`I'm ${this.name},I'm ${this.sex}`);
}
var p1 = new GoodMan("阿毛",20,"男");
p1.do();
p1.say();
複製程式碼
使用ES6中的extends來實現繼承:
class Persion{
constructor(name ,age){
this.name = name;
this.age = age;
}
say(){
console.log(`I'm ${this.name} and ${this.age}`);
}
}
class GoodMan extends Persion{
constructor(name,age,sex){
super(name,age);
this.sex = sex;
}
do(){
console.log(`I'm ${this.name},I'm ${this.sex}`);
}
}
p1 = new GoodMan("阿毛",20,"男");
p1.do();
p1.say();
複製程式碼
在瀏覽器中效果如下:
I'm 阿毛,I'm 男
I'm 阿毛 and 20
複製程式碼
8.3 類的靜態方法 static
直接通過類名來訪問的方法就是靜態方法。如:Math.abs();這裡的 abs()是靜態方法。
class Persion{
constructor(name,age){
this.name = name;
this.age = age;
}
say(){
console.log(`${this.name}的年齡是${this.age}`)
}
static run(){
console.log("running......")
}
}
Persion.run();
複製程式碼
呼叫時就是Persion.run();
==靜態方法只能用類名呼叫!!!!==
靜態方法也可以繼承
class Father{
static foo(){
console.log("我是父類的靜態方法");
}
}
class Son extends Father{
}
Son.foo(); //子類也繼承了父類的靜態方法。 這種方式呼叫和直接通過父類名呼叫時一樣的。
複製程式碼
總結:
整個ES6的新特性和用法學習下來,覺得比較有用的點如下:
- 優先使用let,因為相較var來說,let可以形成作用域,不易產生變數汙染的情況。
- 不常變動的值使用const,方便閱讀,也防止不小心的串改。
- 熟悉新加入的對字串的處理函式,簡化了對於字串處理的過程。
- 使用模板字串即 (`` ,不僅僅方便了字串的整合,同時也省去樣式的操作
- 使用匿名函式的場合,一律改為使用箭頭函式。
- 增加Set和Map的使用率,可以減少僅僅使用Array和Object物件的時的一些複雜操作。