前言
ES6(ESMAScript 6, ESMAScript2015)
是指ESMA
組織在2015
年釋出的ESMAScript 2015
標準,由於相比ES5
(2009年釋出),ES6
的改動非常大,故廣義上,我們也將ES6
統稱所有ES5
之後的新特性,包括ES7
(ESMAScript2016
),ES8
(ESMAScript2017
)...ES10
(ESMAScript2019
)等,這裡專指ES6
。
1、var
,let
與const
我們知道在ES5
中,用var
關鍵字宣告的變數存在變數提升,函式及變數的宣告都將被提升到函式的最頂部,這就導致可以在使用之後在宣告變數,這種不規範的行為很多時候都依靠程式設計習慣來約束,尤其對於新手來說存在不少隱患,於是let
與const
應運而生,二者的作用域都是當前程式碼塊域,let
宣告的變數可以修改,const
宣告的是常量,宣告後不可修改。
let foo = 10;
const bar = {key: 20};
foo = 20;
bar.key = 30;
bar = 30; // TypeError
複製程式碼
這裡解釋一下bar.key = 30
這句話改變的不是bar
的值,只是修改了其中的屬性,就好比古代的女人嫁雞隨雞、從一而終,後來這隻雞變了,那也只是變成了一隻“大雞”,但雞還是這隻雞……
1.1、 作用域的差別
在JavaScript
中,我們總能聽到這樣的話:在JavaScript
中只有函式作用域,沒有塊級作用域。那麼他們到底是什麼呢?
以一個function
宣告的程式碼塊是一個函式作用域,如下程式碼塊中的scope 1
和scope 2
就是同一個函式作用域,但他們是兩個不同的程式碼塊
function fn(){
console.log(foo1); // undefined
console.log(bar1); // undefined
// scope 1
var foo1 = 10;
let foo2 = 20;
if(true) {
// scope 2
var bar1 = 30;
let bar2 = 40;
}
console.log(foo1); // 10
console.log(foo2); // 20
console.log(bar1); // 30
console.log(bar2); // ReferenceError
}
複製程式碼
我們可以看出用let
宣告的變數bar2
在if
語句外是不能訪問的(ReferenceError
引用錯誤,代表引用了一個未宣告的變數)
1.2、 變數提升
在上面的例子中第一句console.log(bar1);
輸出了undefined
,並未報錯,說明用var
宣告的變數在函式開始時就已經定義(即變數提升),但需要注意的是此時並未初始化。但採用let
和const
宣告的變數則不存在變數提升,必須先宣告後使用,否則就會報錯:ReferenceError
,同時我們把這種在宣告之前不可使用這種特性成為 暫時性死區(Temproal Dead Zone, TDZ)。
console.log(foo); // ReferenceError
let foo = 10;
複製程式碼
1.3、 重複宣告
var foo = 10;
var foo = 20;
let bar = 20;
let bar = 20; // SyntaxError
複製程式碼
當我們用var
宣告變數的時候,是可以重複宣告的,後宣告的會覆蓋前面的宣告,但當我們用let
或const
宣告的時候則會報語法錯誤SyntaxError
,注意,function
宣告的函式也和var
有相似的表現:存在 變數提升 和 重複宣告。
2、解構賦值
解構顧名思義按照一定的結構“解析”一個物件,通過這種方式我們可以從陣列或物件中取值,本質上屬於 模式匹配,這是ES6
給我們提供的新語法,只要等號兩邊的模式相同即可解析相應的值。
// 解構一個物件,可以只解構我們需要的部分
const {foo, bar} = {foo: 1, bar: 2, other: 3};
console.log(foo, bar); // 1 2
// 解構一個陣列
const [first, second] = [10, 20, 30];
console.log(first, second); // 10 20
// 忽略陣列的某個值
const [first, , third] = [10, 20, 30];
console.log(first, third); // 10 30
// 解構一個不存在的鍵時,其值為 undefined
const {foo, bar} = {foo: 1}; // undefined
// 注意陣列的 length
const [length] = [12];
console.log(length); // 12
// 用方括號可花括號解析的差別
const {length} = [12];
console.log(length); // 1
// 當屬性中包含關鍵字或保留字時需要重新命名
const {class: clazz} = {class: 'class-name'};
// 動態匹配,同樣需要重新命名
const key = 'thisKey';
const {[key]: value} = {thisKey: 23};
console.log(value); // 23
// 同樣動態匹配也可用在物件宣告中
const key = 'currentKey';
const obj = {[key]: 20};
console.log(obj); // {currentKey: 20}
// 用解構賦值交換兩個變數,不需要宣告第三個變數
let foo = 10;
let bar = 20;
console.log(foo, bar); // 10 20
[foo, bar] = [bar, foo];
console.log(foo, bar); // 20 10
複製程式碼
3、 擴充套件運算子
在使用解構賦值的時候,可以使用擴充套件運算子...
來將剩餘值統一放入一個物件中,
// 用於物件
const {foo, ...rest} = {foo: 1, bar: 2, other: 3};
console.log(foo, rest); // 1 {bar: 2, other: 3}
// 用於陣列
const [first, ...others] = [1, 2, 3];
console.log(first, others); // 1 [2, 3]
// 函式呼叫中傳遞引數
function callback(first, second){
console.log(first, second);
}
let params = [1, 2];
callback(...params); // 輸出 1 2
// 用於獲取 arguments,這種特性主要用於箭頭函式
const arrow = (...args) => {
console.log(args);
};
arrow(1, 2, 3); // 輸出陣列 [1, 2, 3]
複製程式碼
擴充套件運算子實際上是呼叫了Iterator
介面,通常的Array
,String
,NodeList
,Arguments
,Set
,Map
都實現了這個介面,所以可通過擴充套件運算子獲得arguments
,也可用於函式呼叫時傳遞引數,但Object
並未實現Iterator
,所以當我們呼叫fn(...obj)
時,會報錯TypeError
。
4、預設值和預設引數
在我們使用解構賦值的時候,可以為不存在的值或值為undefined
賦預設值
// 不存在指定對映
const {foo, bar = 20} = {foo: 10};
console.log(foo, bar); // 10 20
// 不存在指定值為 undefined
const {foo, bar = 20} = {foo: 10, bar: undefined};
console.log(foo, bar); // 10 20
複製程式碼
只有為undefined
時才會執行預設值賦值步驟
const {foo, bar = (function(){
console.log('預設值');
return 20;
})()} = {foo: 10};
// 預設值
console.log(foo, bar); // 10 20
function defaultVal(){
console.log('預設值');
return 20;
}
const {key1, key2 = defaultVal()} = {key1: 10, key2: null};
console.log(key1, key2); // 10 null
// 可見 key2 為 null,並未輸出 “預設值”
複製程式碼
在宣告函式時,我們也可以為函式引數賦預設值,同樣只有在傳入引數為undefined
時,才執行預設值賦值操作
function fn(foo, bar = 20){
console.log(foo, bar);
}
fn(10); // 10 20
複製程式碼
5、箭頭函式
ES6
給我們帶來了新的函式語法箭頭函式
,在平常的書寫中,極大的提高了我們的開發效率,但是箭頭函式和普通函式卻有著很大的差別
// 基本使用
const arrow = () => {};
// 有一個引數
const arrow = (arg) => {};
const arrow = arg => {}; // 括號可省略
// 有多個引數
const arrow = (arg1, arg2, arg3) => {}
// 函式體只有一句
const arrow = () => 1; // 花括號可省略
// 等價於
const arrow = () => {return 1;};
// 函式體有多句
const arrow = arg => {
if(arg){
return 1;
} else {
return 2;
}
};
// 返回一個空物件
// 這實際上只有一句,但括號不能省略,否則會被解析成程式碼塊
const arrow = () => ({});
複製程式碼
5.1、沒有自身this
箭頭函式的this
是和宣告時的父作用域繫結的,不可通過其他方式(call
,apply
,bind
)更改this
指向,如:
'use strict';
function fn1(){
console.log(this.foo);
return function() {
console.log(this.foo)
}
}
const obj = {foo: 10};
const bar = fn1.call(obj); // 10
bar(); // TypeError(因為此時 this 指向 undefined)
const changedObj = {foo: 60};
bar.call(changedObj); // 60 (this 可以重新繫結)
複製程式碼
如果是箭頭函式:
'use strict';
function fn1(){
console.log(this.foo);
return () => {
console.log(this.foo)
}
}
const obj = {foo: 10};
const bar = fn1.call(obj); // 10
bar(); // 10 (此時 this 仍然指向 obj)
obj.foo = 20;
bar(); // 20
const changedObj = {foo: 60};
bar.call(changedObj); // 20
複製程式碼
5.2、不可使用new
const foo = () => {};
const bar = function(){
}
new bar(); // bar {}
new foo(); // TypeError
複製程式碼
5.3、沒有arguments
箭頭函式沒有arguments
,但可以使用上面的解構語法獲得 引數列表,但得到的不是Arguments
物件,而是一個包含所有引數的陣列
const arrow = (...args) => {
console.log(args);
}
arrow(1, 2, 3); // [1, 2, 3]
複製程式碼
6、class
語法
在ES5
中,只能通過Function.prototype
來實現類的宣告和繼承,ES6
提供了新的語法來宣告一個類,使其更加貼合物件導向程式設計,
// ES5
function Student(){}
Student.staticMethod = function(){
// ES5 的靜態方法
}
Student.prototype.memberMethod = function(){
// ES5 的例項方法
}
// ES6
class Person {
constructor(name) {
this.name = name;
this.memberMethod = this.memberMethod.bind(this);
}
static staticMethod(){
console.log('這是一個靜態方法');
}
static memberMethod(){
// 注意例項方法 ES6 並未繫結 this
// React 中也建議了,在構造器中應手動繫結 this
// 如上
console.log('這是例項方法');
}
}
複製程式碼
7、Promise
(相比以上,這是大概是最複雜的,下一篇將專門將這一特性,後續還會介紹ES7
的async
,await
,他們都是依賴Promise
,所以……很重要!)