前言 本文大量參考了阮一峰老師的開源教程 "ECMAScript6入門" ,適合新手入門或者對ES6常用知識點進行全面回顧,目標是以較少的篇幅涵蓋ES6及部分ES7在實踐中的絕大多數使用場景。更全面、更深入的請進入上面的教程。如果您覺得有遺漏的常見知識點或者錯誤的地方,請評論指出! 新的變數聲明方式 ...
前言
本文大量參考了阮一峰老師的開源教程ECMAScript6入門,適合新手入門或者對ES6常用知識點進行全面回顧,目標是以較少的篇幅涵蓋ES6及部分ES7在實踐中的絕大多數使用場景。更全面、更深入的請進入上面的教程。如果您覺得有遺漏的常見知識點或者錯誤的地方,請評論指出!
新的變數聲明方式let和const
是什麼:
新的變數聲明方式,提供變數的塊級作用域,同時通過一些限制來更防止我們犯錯誤。也就是說是更好的聲明變數的方式
怎麼用
1)let/const與var的區別是提供了塊級作用域與不再具備變數提升(hoisting)
2)在同一個作用域內let/const禁止重覆聲明相同的變數
var a = 1
let a = 2 // SyntaxError
3)let聲明的變數可重新賦值,const聲明的變數不能重新賦值,即常量。
4)暫時性死區:在當前作用域,使用的變數已經存在,但是在代碼執行到變數聲明前禁止訪問。
var tmp = 123
if (true) {
tmp = 'abc' // ReferenceError
let tmp
}
常見使用場景
1)因為能創建塊級作用域,所以常見於if和for中
for (let i = 0; i < arr.length; i++) {
console.log(arr[i])
}
2)const在實踐中常用來聲明一個對象,之後可以再對這個對象的屬性進行修改
const foo = {
name: 'bar'
}
foo.name = 'baz'
console.log(foo)
解構
是什麼:
按照阮一峰大神的說法:ES6允許按照一定模式,從數組和對象中提取值,對變數進行賦值,這被稱為解構(Destructuring)。也就是說通過模式匹配來進行變數賦值。
怎麼用:
1)數組基本用法
let [a, b, c] = [1, 2, 3]
a //1
b //2
c //3
2)對象基本用法
let { foo, bar } = { foo: "aaa", bar: "bbb" }
foo // "aaa"
bar // "bbb"
3)函數參數的解構賦值
如在vuex中action不使用解構如下:
actions: {
increment (context) {
context.commit('increment')
}
}
使用解構
actions: {
increment ({ commit }) {
commit('increment')
}
}
4)支持不完全解構
let [foo, bar] = [1, 2, 3]
5)如果解構不成功,變數的值就等於undefined,同時解構賦值允許指定預設值,預設值生效的條件是對象的屬性值嚴格等於undefined。
常見使用場景
1)交換變數的值
[x, y] = [y, x]
2)提取JSON數據
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
}
let { id, status, data: number } = jsonData
console.log(id, status, number) // 42, "OK", [867, 5309]
3)函數參數的預設值
jQuery.ajax = function (url, {
async = true,
beforeSend = function () {},
cache = true,
complete = function () {},
crossDomain = false,
global = true,
// ... more config
}) {
// ... do stuff
}
4)指定載入模塊的什麼功能
import { mapActions } from 'vuex'
箭頭函數
幹嘛的:
箭頭函數可以用來替換函數表達式,不用寫function,更加簡化。也就是說是函數表達式的簡化方式
怎麼用:
1)註意箭頭函數中的this指向外層的this
2)無法用call/apply/bind來改變this指向。
3)在ES6中,會預設採用嚴格模式,因此預設情況下this不是指向window對象,而是undefined。
<script type="text/javascript">
setTimeout(() => console.log(this), 1000) // undefined,不是window
</script>
4)不可以使用arguments對象,該對象在函數體內不存在。如果要用,可以用rest參數代替。
使用場景:
關鍵是你需要this指向什麼
預設參數和rest參數
是什麼:
預設參數就是設置參數預設值,rest參數(翻譯為不具名參數,也叫做剩餘參數)是將傳入的未具名的參數作為一個數組集合
怎麼用:
如下,預設參數給參數賦一個預設值,rest參數使用三個點(...)加數組集合名
function foo(arg1 = 1, ...restArg){
console.log(arg1, restArg)
}
foo(undefined, 2, 3, 4) // 1, [2, 3, 4]
foo(2, 3, 4) // 2, [3, 4]
擴展運算符
是什麼:
同rest參數一樣,也是三個點。它好比rest參數的逆運算,將一個數組轉為用逗號分隔的參數序列。因此常用於函數調用。
常見使用場景:
1)合併數組
let newArr = [...arr1, ...arr2, ...arr3]
2)與解構賦值結合
// ES5
a = list[0], rest = list.slice(1)
// ES6
[a, ...rest] = list
3)將字元串轉為數組
[...'test'] // [ "t", "e", "s", "t"]
對象擴展
語法變化
1)屬性簡寫,當對象的一個屬性名稱與本地變數名相同的時候,可以省略冒號和值
var foo = 'bar'
var baz = {foo}
baz // {foo: "bar"}
2)屬性名錶達式,可以在以對象字面量方式定義對象是使用表達式作為屬性名
// ES5只能這樣
obj['a' + 'bc'] = 123
// ES6還能這樣
let obj = {
['a' + 'bc']: 123
}
3)方法簡寫,省去:和function
const foo = {
bar () {
console.log('1')
}
}
Object.is()
更好的判斷方法,與===的不同有兩點:一是+0不等於-0,二是NaN等於自身。
NaN === NaN // false
Object.is(NaN, NaN) // true
Object.assign()
1)Object.assign方法用於對象的合併,用法與jQuery和underscore的extend方法類似,而且同樣會改變target。
Object.assign(target, source1, source2)
2)只拷貝源對象的自身屬性(不拷貝繼承屬性),也不拷貝不可枚舉的屬性(enumerable: false)。
3)Object.assign方法實行的是淺拷貝,而不是深拷貝。
Object.setPrototypeOf
用來設置一個對象的prototype對象,返回參數對象本身
Object.setPrototypeOf(object, prototype)
Object.getPrototypeOf()
Object.getPrototypeOf方法可以用來從子類上獲取父類。因此,可以使用這個方法判斷,一個類是否繼承了另一個類。參見下麵類與繼承章節
遍歷
對象的每個屬性都有一個描述對象(Descriptor),Object.getOwnPropertyDescriptor方法可以獲取該屬性的描述對象。這個描述對象有value、writable、enumerable、configurable四大屬性。
ES5下麵三個操作會忽略enumerable為false的屬性。
- for...in迴圈:只遍歷對象自身的和繼承的可枚舉的屬性
- Object.keys():返回對象自身的所有可枚舉的屬性的鍵名
- JSON.stringify():只串列化對象自身的可枚舉的屬性
ES6新增的操作Object.assign(),也會忽略enumerable為false的屬性,只拷貝對象自身的可枚舉的屬性。
ES6一共有5種方法可以遍歷對象的屬性。
(1)for...in
for...in迴圈遍歷對象自身的和繼承的可枚舉屬性(不含Symbol屬性)。
(2)Object.keys(obj)
Object.keys返回一個數組,包括對象自身的(不含繼承的)所有可枚舉屬性(不含Symbol屬性)。
(3)Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames返回一個數組,包含對象自身的所有屬性(不含Symbol屬性,但是包括不可枚舉屬性)。
(4)Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols返回一個數組,包含對象自身的所有Symbol屬性。
(5)Reflect.ownKeys(obj)
Reflect.ownKeys返回一個數組,包含對象自身的所有屬性,不管屬性名是Symbol或字元串,也不管是否可枚舉。
以上的5種方法遍歷對象的屬性,都遵守同樣的屬性遍歷的次序規則。
- 首先遍歷所有屬性名為數值的屬性,按照數字排序。
- 其次遍歷所有屬性名為字元串的屬性,按照生成時間排序。
- 最後遍歷所有屬性名為Symbol值的屬性,按照生成時間排序。
大多數時候,我們只關心對象自身的可枚舉屬性。所以,儘量不要用for...in迴圈,而用Object.keys()代替。
字元串擴展
以前判斷一個字元串是否包含某個字元串只能通過indexOf的值是否大於-1來判斷,現在新增了三種方法:
includes():表示是否包含該字元串。
startsWith():表示該字元串是否在頭部。
endsWith():表示該字元串是否在尾部。
'hello world'.includes('hello') // true
模板字元串
幹嘛的:
和handlebars那些模板引擎功能類似,有模板字元串可以不用拼接字元串了
怎麼用:
用反引號``將整個字元串包裹起來,${}表示一個變數或者一個表達式,可以嵌套
const tmpl = addrs => `
<table>
${addrs.map(addr => `
<tr><td>${addr.first}</td></tr>
<tr><td>${addr.last}</td></tr>
`).join('')}
</table>
`
const data = [
{ first: 'Jane', last: 'Bond' },
{ first: 'Lars', last: 'Croft' },
]
console.log(tmpl(data))
標簽模板
函數名後面緊接一個模板字元串。該函數將被調用來處理這個模板字元串。這被稱為“標簽模板”功能(tagged template)。當字元串模板有變數時,函數的第一個參數為被變數分開的字元串組成的數組,後面的參數依次為變數,這些變數的參數序列可以使用rest參數。
var a = 5
var b = 10
tag`Hello ${ a + b } world ${ a * b }`
// 等同於
tag(['Hello ', ' world ', ''], 15, 50)
數組擴展
Array.of()
Array.of方法用於將一組值,轉換為數組。可以替代Array,且其行為非常統一,不像Array只有一個正整數參數n時,會生成n個空位構成的數組
Array.of(1) // [1]
Array.of(1, 2, 3) // [1, 2, 3]
Array(1) // [undefined * 1],其實不是undefined,是空位,如下可證明兩者並不一樣
0 in [undefined, undefined, undefined] // true
0 in [, , ,] // false
Array(1, 2, 3) // [1, 2, 3]
Array.from()
Array.from方法用於將兩類對象轉為真正的數組:類數組對象(array-like object)和可遍歷(iterable)對象(包括ES6新增的數據結構Set和Map)。Array.from還可以接受第二個參數,作用類似於數組的map方法,用來對每個元素進行處理,將處理後的值放入返回的數組。
Array.from({'0': 'a', length: 1}) // ['a']
Array.from('hello') // ['h', 'e', 'l', 'l', 'o'],因為字元串有Iterator介面,可遍歷
Array.from([1, 2, 3], (x) => x * x) // [1, 4, 9]
常見使用場景:
1)轉換NodeList集合。常見的類似數組的對象是DOM操作返回的NodeList集合,以及函數內部的arguments對象。但是後者使用rest參數更簡便
// NodeList對象
let elementDivList = document.querySelectorAll('div')
Array.from(elementDivList).forEach(function (div) {
console.log(div)
})
2)數組去重。與下麵要講到的Set配合即可很簡單地實現數值去重。
var arr = [1, 3, 5, 5, 8, 3, 2]
var uniqueArr = Array.from(new Set(arr))
console.log(uniqueArr) // [1, 3, 5, 8, 2]
數組實例的copyWithin方法
Array.prototype.copyWithin(target, start = 0, end = this.length)
它接受三個參數。
- target(必需):從該位置開始替換數據。
- start(可選):從該位置開始讀取數據,預設為0。如果為負值,表示倒數。
- end(可選):到該位置前停止讀取數據,預設等於數組長度。如果為負值,表示倒數。
[1, 2, 3, 4, 5].copyWithin(0,2) // [3, 4, 5, 4, 5]
/* 從索引2開始讀取數據,到數組尾部停止,即讀到(3, 4, 5),然後從索引0開始替換數據 */
數組實例的find和findIndex方法
找到第一個符合條件的item(項)或index(索引),前者相當於underscore中的first方法,後者則和underscore中的同名方法一致。另外,這兩個方法都可以藉助Object.is發現NaN,彌補了數組的IndexOf方法的不足。
[1, 2, 3, 4].find(x => x > 2) // 3
[1, 2, 3, 4].findIndex(x => x > 2) // 2
[NaN].findIndex(x => Object.is(NaN, x)) // 0
數組實例的fill方法
Array.prototype.fill(fillItem, start = 0, end = this.length)
[1, 3, 6, 11, 4].fill(10,2) // [1, 3, 10, 10, 10]
數組實例的includes方法
與字元串的includes方法類似。該方法屬於ES7,但Babel轉碼器已經支持。
[1, 2, 3].includes(3, 3) // false
[1, 2, 3].includes(3, -1) // true
[NaN].includes(NaN) // true
Set和Map
ES6中增加了兩個新的數據結構:Set和Map。Set是不包含重覆值的列表,而Map則是鍵與相對應的值的集合。
Set
是什麼:
Set是不包含重覆值的有序列表。
怎麼用:
1)Set構造函數可以接受一個數組(或類似數組的對象)作為參數,用來初始化。
const set = new Set([1, 2, 3, 4, 4])
console.log(set)
2)四個操作方法(add()、delete()、has()、clear())和一個屬性(size),使用方法根據名字和下麵例子就知道了
const s = new Set()
s.add(1).add(2).add(2) // 註意2被add了兩次
s.size // 2
s.has(1) // true
s.has(2) // true
s.has(3) // false
s.delete(2)
s.has(2) // false
s.add(3)
s.size // 2
s.clear()
s.size // 0
3)三個遍歷器生成函數(keys()、values()、entries())和一個遍歷方法(forEach())
keys方法、values方法、entries方法返回的都是遍歷器對象(詳見Iterator)。都這可以使用遍歷器對象的方法for...of進行遍歷。由於 Set 結構沒有鍵名,只有鍵值(或者說鍵名和鍵值是同一個值),所以keys方法和values方法的行為完全一致。forEach方法與ES5數組的forEach類似。
let set = new Set(['red', 'green', 'blue'])
for (let item of set.keys()) {
console.log(item)
}
// red
// green
// blue
for (let item of set.values()) {
console.log(item)
}
// red
// green
// blue
for (let item of set.entries()) {
console.log(item)
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
set.forEach((value, key) => console.log(value, key))
4)Set轉化為數組,有兩種方法:...擴展運算符和Array.from()
這兩者可互換,因此前面提到的使用Array.from()來數組去重也可以這樣做:[...new Set(arr)]
// 方法一
let set = new Set([1, 2, 3])
set = new Set([...set].map(val => val * 2))
// set的值是2, 4, 6
// 方法二
let set = new Set([1, 2, 3])
set = new Set(Array.from(set, val => val * 2))
// set的值是2, 4, 6
Map
是什麼:
一種由鍵值對集合構成的數據結構,類似於對象,但是“鍵”的範圍不限於字元串,各種類型的值(包括對象)都可以當作鍵。
const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"
怎麼用:
1)Set構造函數可以接收任何一個具有Iterator介面的數據結構作為參數
const set = new Set([
['foo', 1],
['bar', 2]
])
const m1 = new Map(set)
m1.get('foo') // 1
2)5個操作方法(set(key, value)、get(key)、has(key)、delete(key)、clear())和一個屬性(size)
3)遍歷生成函數和遍歷方法和Set類似,Map結構的預設遍歷器介面(Symbol.iterator屬性),就是entries方法。
map[Symbol.iterator] === map.entries // true
4)Map轉為數組,使用...擴展運算符
WeakSet和WeakMap
WeakSet、WeakMap分別和Set、Map類似,不過存儲的是對象的弱引用方式,這樣在記憶體管理上更加容易優化。