ECMAScript
ECMAScript也是一門腳本語言,一般縮寫為ES,通常被看作為JavaScript的標(biāo)準(zhǔn)化規(guī)范,實(shí)際上JavaScript是ECMAScript的擴(kuò)展語言。
ECMAScript只提供了最基本的語法(停留在語言層面),我們使用的JavaScript實(shí)現(xiàn)了ECMAScript語言的標(biāo)準(zhǔn),并在這個(gè)基礎(chǔ)之上,做了一些擴(kuò)展。
瀏覽器環(huán)境中的JavaScript組成,可以看做是ECMAScript + Web 提供的 APIs
- 瀏覽器環(huán)境中的JavaScript組成
Node環(huán)境中的JavaScript組成, 就是ECMAScript + Node APIs

JavaScript語言本身指的就是ECMAScript,從2015年開始ES就保持每年一個(gè)版本的迭代,伴隨著新版本的迭代,很多新特性陸續(xù)出現(xiàn),其中ES2015里面包含著和之前版本有很大的不同新功能。ES2015迭代時(shí)間過長、發(fā)布內(nèi)容特別多。從ES2015之后,ECMAScript就不再用版本號來命名,開始按照發(fā)行年份命名,由于這個(gè)決定是在ES2015發(fā)行的過程之中定的,很多人已經(jīng)習(xí)慣將ES2015稱為ES6.
市面上的瀏覽器也在不斷支持ECMAScript的新特性,學(xué)習(xí)這些新特性很有必要。

ECMAScript 2015(ES2015)
- 相比于ES5.1的變化比較大
- 自此,標(biāo)準(zhǔn)命名規(guī)則發(fā)生變化
http://www.ecma-international.org/ecma-262/6.0/
下面我們來說一些主要的ES2015的新特性,可以分為四大類:
- 解決原有語法上的一些問題或者不足 (let、const)
- 對原有語法進(jìn)行增強(qiáng) (數(shù)組對象解構(gòu)、模板字符串、字符串?dāng)U展方法、... 運(yùn)算符、箭頭函數(shù)、對象字面量增強(qiáng))
- 全新的對象、全新的方法、全新的功能(Proxy、Reflect、Promise)
*全新的數(shù)據(jù)類型和數(shù)據(jù)結(jié)構(gòu) (Reflect)
let 與塊級作用域
作用域-- 指的就是代碼中的一個(gè)成員能夠起作用的范圍。
在ES2015之前,ES中只有兩種作用域
- 全局作用域
- 函數(shù)作用域
- 塊級作用域 (ES2015新增)就是我們代碼中用一對花括號所包裹起來的范圍,例如if語句, for語句中的花括號,都會(huì)產(chǎn)生塊
以前塊沒有獨(dú)立的作用域,這就導(dǎo)致我們在塊中定義的變量,外部也能訪問到。對于復(fù)雜的代碼來說是非常不安全的。
我們可以只用let聲明塊級變量
let聲明不會(huì)有變量提升
console.log(year)
// 報(bào)錯(cuò)
let year = 2021
console.log(foo)
// undefined
var foo = 'abc'
const 常量
const在聲明時(shí)必須設(shè)置一個(gè)初始值。不能將聲明和賦值分開

const聲明后不允許修改內(nèi)存地址
const obj = {}
// 可以修改屬性
obj.name = 'uamru'
// 會(huì)修改內(nèi)存地址--報(bào)錯(cuò)
obj = {}
數(shù)組的解構(gòu)
快速提取成員
const arr = [100, 200, 300]
const [foo, bar, baz] = arr
console.log(foo, bar, baz)
// 100, 200, 300
// 只獲取第三個(gè)成員
const [, , three] = arr
console.log(three)
//300
// 獲取從當(dāng)前位置到最后一個(gè)成員
const [one, ...rest] = arr
console.log(rest)
//[ 200, 300 ]
// 結(jié)構(gòu)數(shù)量小于數(shù)組的數(shù)量(會(huì)從第一個(gè)位置開始提取)
const [one] = arr
console.log(one)
// 100
//解構(gòu)數(shù)量大于數(shù)組的長度 (得到undefined)
const [one, two, three, four] = arr
console.log(four)
// undefined
// 設(shè)置默認(rèn)值(使用 =)
const [one, two, three, four = 'default value'] = arr
console.log(four)
// default value
// 例子:拆分字符串
const path = '/a/b/c'
const [, dir] = path.split('/')
console.log(dir)
// a
對象的解構(gòu)
const obj = { name: 'umaru', age: 16}
let { name } = obj
console.log(name)
// umaru
// 對屬性重命名,解決名稱沖突
const name = 'tom'
const { name: newName} = obj
console.log(newName)
// 添加默認(rèn)值
const { name: newName = 'default value'} = obj
console.log(newName)
// umaru
模板字符串
// 傳統(tǒng)定義字符串需要使用單引號、雙引號
const str = 'hello es2015, this is a string'
// 模板字符串
// 支持直接換行
const str2 = `hello es2015,
this is a \`string\``
console.log(str2)
// hello es2015,
// this is a `string`
// 支持模板插值
// ${} 可以嵌入任何js標(biāo)準(zhǔn)語句 ${ 1 + 1 },語句的返回值 最終會(huì)被輸出到插值表達(dá)式存在的位置
const name = 'umaru'
const msg = `hey, ${name}`
console.log(msg)
// hey, umaru
帶標(biāo)簽的模板字符串
可以對字符串內(nèi)容進(jìn)行加工
// 高級用法
// 帶標(biāo)簽的模板字符串,定義模板字符串之前,去添加一個(gè)標(biāo)簽,這個(gè)標(biāo)簽是一個(gè)特殊的函數(shù),使用標(biāo)簽就是調(diào)用這個(gè)函數(shù)
// const str = tag`hello world`
// 使用標(biāo)簽是console.log
const str = console.log`hello world`
// [ 'hello world' ]
const name = 'umaru'
const gender = true
// 定義一個(gè)標(biāo)簽函數(shù)
function myTagFunc(strings, name){
// 表達(dá)式靜態(tài)分割的內(nèi)容
console.log(strings)
//[ 'hey, ', '.' ]
console.log(name)
// umaru
return '123'
}
const msg = myTagFunc`hey, ${name}.`
// 返回值 就是這個(gè)標(biāo)簽函數(shù)的返回值
console.log(msg)
// 123
示例:對字符串內(nèi)容進(jìn)行加工
const name = 'umaru'
const gender = true
// 定義一個(gè)標(biāo)簽函數(shù)
function myTagFunc(strings, name, gender){
// 表達(dá)式靜態(tài)分割的內(nèi)容
console.log(strings)
// [ 'hey, ', ' is a ', '.' ]
// 我們定義的true不是很好理解,可以對這個(gè)值做加工
gender = gender ? 'man' : 'woman'
return strings[0] + name + strings[1] + gender + strings[2]
}
const msg = myTagFunc`hey, ${name} is a ${gender}.`
// 返回值就是這個(gè)標(biāo)簽函數(shù)的返回值
console.log(msg)
// hey, umaru is a man.
字符串的擴(kuò)展方法
- includes(searchString, position) 判斷字符串是否包含指定的子字符串
- startsWith(searchString, position) 判斷字符串是否以指定的子字符串開頭
- endsWidth(searchString, position) 判斷字符串是否以指定的子字符串結(jié)尾
參數(shù)解析:
(1).searchString:必需,規(guī)定要搜索的子字符串。
(2).position:可選,規(guī)定在str中搜索searchString的結(jié)束位置,默認(rèn)值為str.length,也就是字符串結(jié)尾處。
const message = 'Error: foo is not defined.'
console.log(
message.startsWith('Error'),
message.endsWith('.'),
message.includes('foo')
)
// true true true
函數(shù)參數(shù)默認(rèn)值
// 之前
function foo(enable) {
// 通過代碼設(shè)置默認(rèn)值
enable = enable === undefined ? true : enable
console.log('foo invoke - enable: ')
console.log(enable)
}
foo()
// true
// ES2015
// 使用 = 設(shè)置默認(rèn)值
// 帶默認(rèn)值的參數(shù)要放在最后
function foo2(bar, enable = true) {
// 通過代碼設(shè)置默認(rèn)值
console.log('foo invoke - enable: ')
console.log(enable)
}
foo(false)
// false
foo()
// true
剩余參數(shù)
比如我們console.log 可以接受任意多個(gè)參數(shù),并且將這些參數(shù)打印出來
對于未知數(shù)量的參數(shù),以前我們都是使用arguments這個(gè)對象去接受,arguments是個(gè)偽數(shù)組。
function foo() {
console.log(arguments)
// [Arguments] { '0': 1, '1': 2, '2': 3, '3': 4 }
}
// ES2015可以使用...
function foo(...args){
console.log(args)
//[ 1, 2, 3, 4 ]
}
foo(1, 2, 3, 4)
關(guān)于... 運(yùn)算符
在ES2015中有一個(gè) ... 運(yùn)算符用于操作數(shù)組,有兩種層面
- 1、第一個(gè)叫做 展開運(yùn)算符(spread operator),作用是和字面意思一樣,就是把東西展開??梢杂迷赼rray和object上都行
// joining arrays
const odd = [1, 3, 5 ];
const nums = [2 ,4 , 6, ...odd];
console.log(nums); // [ 2, 4, 6, 1, 3, 5 ]
// 可以使用spread運(yùn)算符在另一個(gè)數(shù)組內(nèi)的任何位置插入數(shù)組。
const odd = [1, 3, 5 ];
const nums = [2, ...odd, 4 , 6];
console.log(nums) //[ 2, 1, 3, 5, 4, 6 ]
// 還可以將擴(kuò)展運(yùn)算符與ES6解構(gòu)表示法結(jié)合使用:
const { a, b, ...z } = { a: 1, b: 2, c: 3, d: 4 };
console.log(a) // 1
console.log(b) // 2
console.log(z) // { c: 3, d: 4 }
- 第二個(gè)叫做 剩余操作符(rest operator),是解構(gòu)的一種,意思就是把剩余的東西放到一個(gè)array里面賦值給它。一般只針對array的解構(gòu)
function foo(...args){
console.log(args)
}
foo(1, 2, 3, 4)
箭頭函數(shù) Arrow Function
const inc = n => n+1
箭頭函數(shù)不會(huì)改變this的指向
// 箭頭函數(shù)和this
const person = {
name: 'umaru',
sayHi: function() {
// 傳統(tǒng)函數(shù)this指向調(diào)用者
console.log(`hi, my name is ${this.name}`)
// => hi, my name is umaru
},
sayHi2: () => {
// 傳統(tǒng)函數(shù)this指向調(diào)用者
console.log(`hi, my name is ${this.name}`)
// => hi, my name is undefined
},
sayHiAsync: function(){
setTimeout(function() {
// 這個(gè)函數(shù)在setTimeOut里面會(huì)被全局作用域調(diào)用
console.log(this.name)
// => undefined
}, 1000)
},
// 借助閉包獲取this
sayHiAsync2: function(){
const _this = this;
setTimeout(function() {
// 這個(gè)函數(shù)在setTimeOut里面會(huì)被全局作用域調(diào)用
console.log(_this.name)
// => umaru
}, 1000)
},
sayHiAsync3: function(){
// 使用箭頭函數(shù)就不用這么麻煩,指向的還是當(dāng)前作用域
setTimeout(() => {
console.log(this.name)
// => umaru
}, 1000)
}
}
person.sayHi()
person.sayHi2()
person.sayHiAsync()
person.sayHiAsync2()
person.sayHiAsync3()
對象字面量增強(qiáng)
const bar = '345'
const obj = {
foo: 123,
//bar: bar
//變量名相同就可以省去
bar,
// method1: function() {
// console.log('method111')
// }
method1() {
console.log('method111')
// method111
// this就是當(dāng)前對象
console.log(this)
// foo: 123, bar: '345', method1: [Function: method1] }
},
// es2015可以直接使用動(dòng)態(tài)屬性
// 計(jì)算屬性名
[Math.radom()]: 123
}
console.log(obj)
// { foo: 123, bar: '345', method1: [Function: method1] }
obj.method1()
對象擴(kuò)展方法
Object.assign(target, ...sources)
參數(shù):
target:目標(biāo)對象
sources:源對象
用于將所有可枚舉屬性的值從一個(gè)或多個(gè)源對象分配到目標(biāo)對象。它將返回目標(biāo)對象。
如果目標(biāo)對象中的屬性具有相同的鍵,則屬性將被源對象中的屬性覆蓋。后面的源對象的屬性將類似地覆蓋前面的源對象的屬性。
const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };
const returnedTarget = Object.assign(target, source);
console.log(target);
// expected output: Object { a: 1, b: 4, c: 5 }
console.log(returnedTarget);
// expected output: Object { a: 1, b: 4, c: 5 }
可以使用Object.assign復(fù)制對象,不會(huì)修改到原對象
Object.is(value1, value2)
Object.is() 方法判斷兩個(gè)值是否為同一個(gè)值。如果滿足以下條件則兩個(gè)值相等:
- 都是undefined
- 都是null
- 都是true或false
- 都是相同長度的字符串且相同字符串按相同順序排序
- 都是相同對象(意味著引用地址相同)
- 都是數(shù)組且
- 都是 +0
- 都是 -0
- 都是 NaN
- 都是 非零而且非NaN且為同一值
與 == 運(yùn)算不同。 == 運(yùn)算在判斷相等前會(huì)對兩邊的變量(如果不是同一類型)進(jìn)項(xiàng)強(qiáng)制轉(zhuǎn)換。而Object.is 不會(huì)強(qiáng)制轉(zhuǎn)換兩邊的值
與 === 運(yùn)算頁不同, === 運(yùn)算符將數(shù)字 -0和+0視為相等,而將Number.NaN與NaN視為不相等。
Proxy
為對象設(shè)置代理器,可以輕松監(jiān)視到對象的讀寫
在ES2015之前,我們可以使用Object.defineProperty捕獲到對象的讀寫過程,Vue2就是使用這個(gè)方法進(jìn)行雙向綁定。
// Proxy對象
const person = {
name: `umaru`,
age: 16
}
const personProxy = new Proxy(person, {
get(target, property){
return property in target ? target[property] : undefined
},
set(target, property, value) {
if(property === 'age') {
if(!Number.isInteger(value)){
throw new TypeError(`${value} is not an int`)
}
}
target[property] = value
}
})
personProxy.age = '111'
// 拋出異常 TypeError: 1111 is not an int
personProxy.age = 111
personProxy.gender = true
console.log(personProxy.name)
// => umaru
console.log(personProxy.XXX)
// => undefined
Proxy對比Object.defineProperty
1、defineProperty只能監(jiān)視到屬性的讀寫,Proxy能監(jiān)視到更多對象操作,例如delete和對對象當(dāng)中方法的調(diào)用
const person= {
name: 'uamru',
age: 16
}
const personProxy = new Proxy(person, {
deleteProperty(target, property){
console.log('delete', property)
// => delete age
delete target[property]
}
})
delete personProxy.age
console.log(person)
// => { name: 'uamru' }

2、Proxy更好的支持?jǐn)?shù)組對象的監(jiān)視
常見的一種方式,重寫數(shù)組的操作方法,以此去劫持對應(yīng)方法的調(diào)用過程。
const list = []
const listProxy = new Proxy(list, {
set(target, property, value){
console.log('set', property, value)
// => set 0 100
// => set length 1
target[property] = value
return true // 表示設(shè)置成功
}
})
listProxy.push(100)
3、Proxy是以非侵入的方式監(jiān)管了對象的讀寫。一個(gè)已經(jīng)定義好的對象,我們不需要對對象本身去做任何操作,就可以監(jiān)視到內(nèi)部成員的讀寫。而object.defineProperty需要特殊的方式單獨(dú)定義對象當(dāng)中需要監(jiān)視的屬性。
const person = {}
Object.defineProperty(person, 'name', {
get(){
console.log('name 被訪問')
return person._name
},
set(value){
console.log('name 被設(shè)置')
person._name = value
}
})
Object.defineProperty(person, 'age', {
get(){
console.log('age 被訪問')
return person._age
},
set(value){
console.log('age 被設(shè)置')
person._age = value
}
})
person.name = 'umaru'
console.log(person.name)
// Proxy 方式更為合理
const person2 = {
name: 'umaru',
age: 16
}
const personProxy = new Proxy(person2, {
get(target, property){
return property in target ? target[property] : undefined
},
set(target, property, value){
target[property] = value
}
})
personProxy.name = 'jack'
console.log(person2)
Reflect
統(tǒng)一對象的操作方式
const obj = {
foo: '123',
bar: '456'
}
const proxy = new Proxy(obj, {
get(target, property){
console.log('watch logic~')
return Reflect.get(target, property)
}
})
console.log(proxy.foo)
// const obj = {
// foo: '123',
// bar: '456'
// }
// const proxy = new Proxy(obj, {
// get(target, property){
// console.log('watch logic~')
// return Reflect.get(target, property)
// }
// })
// console.log(proxy.foo)
const obj = {
foo: '123',
bar: '456'
}
// 傳統(tǒng)操作對象,我們要用到各種操作符、對象的方法(in、delete、Object.keys等)
console.log('name' in obj)
console.log(delete obj['age'])
console.log(Object.keys(obj))
// =>
// false
// true
// [ 'foo', 'bar' ]
console.log(Reflect.has(obj, 'name'))
console.log(Reflect.deleteProperty(obj, 'age'))
console.log(Reflect.ownKeys(obj))
// =>
// false
// true
// [ 'foo', 'bar' ]
ES2015 Promise
一種更優(yōu)的異步編程解決方案,通過鏈?zhǔn)秸{(diào)用的方式,解決了傳統(tǒng)異步編程中回調(diào)函數(shù)嵌套過深的問題。
class類
// 使用function創(chuàng)建對象
function Person2(name) {
this.name = name
}
// 使用原型添加方法
Person2.prototype.say = function() {
console.log(`hi, my name is ${this.name}`)
}
// 使用class
class Person {
// 構(gòu)造函數(shù)
constructor(name){
this.name = name
}
// 實(shí)例方法
say() {
console.log(`hi, my name is ${this.name}`)
}
}
const p = new Person('umaru')
p.say()
實(shí)例方法vs靜態(tài)方法
ES2015中新增添加靜態(tài)成員的static關(guān)鍵詞
實(shí)例:添加一個(gè)create靜態(tài)方法
class Person {
constructor(name){
this.name = name
}
say() {
console.log(`hi, my name is ${this.name}`)
}
static create(name){
return new Person(name)
}
}
const p = Person.create('umaru')
p.say()
類的繼承 extends
class Person {
constructor(name){
this.name = name
}
say() {
console.log(`hi, my name is ${this.name}`)
}
static create(name){
return new Person(name)
}
}
class Student extends Person {
constructor (name, number){
// super指向父類
// 調(diào)用它就相當(dāng)于調(diào)用父類構(gòu)造函數(shù)
super(name)
this.number = number
}
hello(){
super.say()
console.log(`my school number is ${this.number}`)
}
}
const s = new Student('umaru', 1)
s.hello()
Set 數(shù)據(jù)結(jié)構(gòu)
ES2015提供了新的數(shù)據(jù)結(jié)構(gòu)Set,它類似于數(shù)組,但是成員的值都是唯一的,沒有重復(fù)的值。
Set 本身是一個(gè)構(gòu)造函數(shù),調(diào)用構(gòu)造函數(shù)用來生成 Set 數(shù)據(jù)結(jié)構(gòu)。
Set 函數(shù)可以接受一個(gè)數(shù)組(或類似數(shù)組的對象)作為參數(shù),用來進(jìn)行數(shù)據(jù)初始化。
let s = new Set([1, 2, 3, 4, 4]);
console.log(s)
// => Set(4) { 1, 2, 3, 4 }
// 使用add鏈?zhǔn)秸{(diào)用
// 添加已有的值,這個(gè)值會(huì)被忽略掉
s.add(7).add(5).add(1)
console.log(s)
// => Set(6) { 1, 2, 3, 4, 7, 5 }
s.forEach( i => console.log(i))
for(i of s){
console.log(i)
}
// size
console.log('size',s.size)
// => size 6
// has
console.log(s.has(100))
// => false
// delete
console.log(s.delete(3))
// => true
console.log(s)
// => Set(5) { 1, 2, 4, 7, 5 }
// 清除
s.clear()
console.log(s)
// => Set(0) {}
// 常見使用方式,為數(shù)組去重
const arr = [1, 2, 3, 4, 3, 6]
const result = new Set(arr)
console.log(result)
// => Set(5) { 1, 2, 3, 4, 6 }
// 如果我們想要再得到一個(gè)數(shù)組,可以使用Array.from
// const result2 = Array.from(new Set(arr))
// 或者使用...展開
const result2 = [...new Set(arr)]
console.log(result2)
// => [ 1, 2, 3, 4, 6 ]
Map數(shù)據(jù)結(jié)構(gòu)
一個(gè) Map的鍵可以是任意值,包括函數(shù)、對象或任意基本類型。
Map 中的 key 是有序的。因此,當(dāng)?shù)臅r(shí)候,一個(gè) Map 對象以插入的順序返回鍵值。
// Object
const obj = {}
obj[true] = 'value'
obj[123] = 'value'
obj[{a:1}] = 'value'
console.log(Object.keys(obj))
// 如果添加的鍵不是字符串,內(nèi)部會(huì)將他們toString的結(jié)果當(dāng)做鍵值
// => [ '123', 'true', '[object Object]' ]
// 加入我們?nèi)绻胗脤ο笞鳛殒I值,那這樣轉(zhuǎn)換過后都會(huì)變成一樣的值,就會(huì)有問題
// Map
const m = new Map()
const tom = {name: 'tom'}
m.set(tom, 90)
console.log(m)
// => Map(1) { { name: 'tom' } => 90 }
// m.has() 判斷某一個(gè)鍵是否存在
// console.log(m.has('2222'))
// m.delete() 刪除某一個(gè)鍵
// console.log(m.delete('222'))
// m.clear() 清空map
// 遍歷map
m.forEach((value, key) => {
console.log(value, key)
// => 90 { name: 'tom' }
})
Symbol
一種全新的原始數(shù)據(jù)類型,在ES2015之前,對象的屬性都是字符串,如果字符串重復(fù),就會(huì)產(chǎn)生沖突。
一個(gè)具有數(shù)據(jù)類型 “symbol” 的值可以被稱為 “符號類型值”。在 JavaScript 運(yùn)行時(shí)環(huán)境中,一個(gè)符號類型值可以通過調(diào)用函數(shù) Symbol() 創(chuàng)建,這個(gè)函數(shù)動(dòng)態(tài)地生成了一個(gè)匿名,唯一的值。Symbol類型唯一合理的用法是用變量存儲 symbol的值,然后使用存儲的值創(chuàng)建對象屬性。
const s = Symbol()
console.log(s)
// => Symbol()
console.log(typeof s)
// => symbol
console.log(Symbol() === Symbol())
// => false
console.log(Symbol('ONE'))
console.log(Symbol('TWO'))
console.log(Symbol('THREE'))
// => Symbol(ONE)
// Symbol(TWO)
// Symbol(THREE)
// 解決對象屬性名不重復(fù)
const obj = []
obj[Symbol()] = '123'
obj[Symbol()] = '456'
console.log(obj)
// => [ [Symbol()]: '123', [Symbol()]: '456' ]
// 使用Symbol模擬對象私有成員
const name = Symbol()
const person = {
[name]: 'umaru',
say() {
console.log(this[name])
}
}
// 在外部就只能調(diào)用普通名稱的成員
console.log(person[Symbol()])
// undefined
person.say()
Symbol每次創(chuàng)建都是一個(gè)新的值,如果想要使用同一個(gè)值,可以使用全局變量去復(fù)用一個(gè)Symbol的值,也可以使用Symbol靜態(tài)方法for方法來實(shí)現(xiàn)。
這個(gè)方法內(nèi)部維護(hù)了一個(gè)全局的注冊表,為字符串和Symbol值提供了一一對應(yīng)的方法。如果我們傳入的不是字符串,這個(gè)方法內(nèi)部會(huì)自動(dòng)把他轉(zhuǎn)換為字符串。這會(huì)導(dǎo)致我們傳入bool類型的true和字符類型的true相等。
const s1 = Symbol.for('foo')
const s2 = Symbol.for('foo')
console.log(s1===s2)
// => true
console.log(
Symbol.for(true) === Symbol.for('true')
// => true
)
toStringTag
// 傳統(tǒng)定義一個(gè)對象,如果打印對象的toString方法會(huì)得到[object object],
// 我們把這樣的字符串叫做對象的字符串標(biāo)簽
// 如果我們想要更改對象的toString標(biāo)簽,需要在對象中添加一個(gè)成員來標(biāo)識
// 如果使用字符串去添加這種標(biāo)識符,就有可能跟內(nèi)部的成員產(chǎn)生沖突,
// ES要求我們使用Symbol值去使用這樣一個(gè)接口
// toStringTag內(nèi)置常量
const obj = {
[Symbol.toStringTag]: 'XObject'
}
console.log(obj.toString())
// => [object XObject]
獲取對象中的symbol:getOwnPropertySymbols
const obj = {
[Symbol()]: 'symbol value',
foo: 'normal value'
}
// 以下方法都獲取不到Symbol值
for(let key in obj){
console.log(key)
// => foo
}
console.log(Object.keys(obj))
// => [ 'foo' ]
console.log(JSON.stringify(obj))
// => {"foo":"normal value"}
// 通過getOwnPropertySymbols獲取
console.log(Object.getOwnPropertySymbols(obj))
// => [ Symbol() ]
for...of
ES2015引入for...of作為遍歷所有數(shù)據(jù)結(jié)構(gòu)的統(tǒng)一方式。
使用:遍歷數(shù)組、Map對象
for...of 可以用break跳出循環(huán)
// for...of 使用break跳出循環(huán)
const arr = [100, 200, 300, 400]
for(item of arr){
console.log(item)
}
// 100
// 200
// 300
// 400
for(let item of arr){
if(item > 200) break;
console.log(item)
}
// 100
// 200
// 遍歷map
const m = new Map()
m.set('one','123')
m.set('two','456')
m.set('three','789')
for(const item of m){
console.log(item)
}
// 數(shù)組形式獲取鍵和值
// [ 'one', '123' ]
// [ 'two', '456' ]
// [ 'three', '789' ]
// 使用數(shù)組結(jié)構(gòu)獲取key,value
for(const [key, value] of m){
console.log(key, value)
}
// one 123
// two 456
// three 789
使用for...of遍歷Object會(huì)報(bào)錯(cuò)
const obj = {one: 123, two: 456}
for(const item of obj){
console.log(item)
}
//for(const item of obj){
^
//TypeError: obj is not iterable
我們前面說它可以遍歷說有數(shù)據(jù)結(jié)構(gòu),這是為什么呢?
ES中表示有結(jié)構(gòu)的數(shù)據(jù)類型越來越多,像Array、Object、Set、Map等,為了給各種各樣的數(shù)據(jù)結(jié)構(gòu)提供統(tǒng)一遍歷方式,ES2015提供了Iterable接口,意思是可迭代的接口。
實(shí)現(xiàn)Iterable接口就是使用for...of遍歷的前提。
迭代器(Iterator)
實(shí)現(xiàn)Iterable接口的對象,里面都有一個(gè)迭代器對象 通過調(diào)用 Symbol.iterator(他是一個(gè)函數(shù))方法獲取

Symbol.iterator 為每一個(gè)對象定義了默認(rèn)的迭代器。該迭代器可以被for...of循環(huán)使用。
當(dāng)需要對一個(gè)對象進(jìn)行迭代時(shí)(比如開始用于一個(gè)for..of循環(huán)中),它的@@iterator方法都會(huì)在不傳參情況下被調(diào)用,返回的迭代器用于獲取要迭代的值。
const set = new Set(['foo', 'bar', 'baz'])
const iterator = set[Symbol.iterator]()
// 使用next()方法遍歷
console.log(iterator.next())
// => { value: 'foo', done: false }
自定義迭代器
const obj = {
store: ['foo', 'bar', 'baz'],
// 使用計(jì)算屬性名 添加Symbol.iterator
[Symbol.iterator]: function(){
let index = 0
const self = this
return {
next: function(){
const result = {
value: self.store[index],
done: index >= self.store.length
}
index++
return result
}
}
}
}
for(const item of obj){
console.log('循環(huán)體', item)
}
迭代器模式(設(shè)計(jì)模式)
對外提供統(tǒng)一遍歷接口,讓外部不用關(guān)心內(nèi)部結(jié)構(gòu)
ES2015生成器 Generator
生成器對象是由一個(gè) generator function 返回的,并且它符合可迭代協(xié)議和迭代器協(xié)議。
定義生成器 就是在普通的函數(shù)function后加一個(gè)*,這個(gè)函數(shù)就變成了一個(gè)生成器函數(shù)
// 普通函數(shù)
function foo(){
console.log('umaru')
return 100
}
const result = foo()
console.log(result)
// umaru
// 100
// 生成器函數(shù)
function * foo2(){
console.log('umaru')
return 100
}
const result2 = foo2()
console.log(result2)
// Object [Generator] {}
// 調(diào)用next方法,函數(shù)體才開始執(zhí)行
console.log(result2.next())
// umaru
// { value: 100, done: true }
生成器函數(shù)與yield
調(diào)用生成器的next方法,返回一個(gè)由 yield表達(dá)式生成的值。
function * one() {
console.log(1111)
yield 100
console.log(2222)
yield 200
console.log(3333)
yield 300
}
const generator = one()
console.log(generator.next())
// 1111
// { value: 100, done: false }
console.log(generator.next())
// 2222
// { value: 200, done: false }
console.log(generator.next())
// 3333
// { value: 300, done: false }
console.log(generator.next())
// { value: undefined, done: true }
生成器應(yīng)用
// Generator 應(yīng)用
// 案例1 發(fā)號器
function * createIdMaker() {
let id = 1
// 不用擔(dān)心死循環(huán),調(diào)用next才會(huì)執(zhí)行
while(true) {
yield id++
}
}
const idMaker = createIdMaker()
console.log(idMaker.next().value)
console.log(idMaker.next().value)
console.log(idMaker.next().value)
console.log(idMaker.next().value)
// 案例2 用生成器去實(shí)現(xiàn)iterator方法
// const todos = {
// life: ['吃飯','睡覺','打豆豆'],
// learn: ['語文','數(shù)學(xué)','外語'],
// work: ['演講'],
// [Symbol.iterator]: function () {
// const all = [...this.life, ...this.learn, ...this.work]
// let index = 0
// return {
// next: function() {
// return {
// value: all[index],
// done: index++ >= all.length
// }
// }
// }
// }
// }
const todos = {
life: ['吃飯','睡覺','打豆豆'],
learn: ['語文','數(shù)學(xué)','外語'],
work: ['演講'],
[Symbol.iterator]: function * () {
const all = [...this.life, ...this.learn, ...this.work]
for(const item of all){
yield item
}
}
}
// 測試iterator
const it = todos[Symbol.iterator]()
console.log(it.next())
// { value: '吃飯', done: false }
console.log(it.next())
// { value: '睡覺', done: false }
for(const item of todos){
console.log(item)
}
// 吃飯
// 睡覺
// 打豆豆
// 語文
// 數(shù)學(xué)
// 外語
// 演講
ES Modules
ESMAScripit 2016
Array.property.includes
const array = ['foo', 1, NaN, false]
console.log(array.indexOf('foo'))
// 0
console.log(array.indexOf('bar'))
// -1
console.log(array.indexOf(NaN))
// -1
// includes可以直接判斷數(shù)組中是否又該元素
// includes可以匹配 NaN
console.log(array.includes('foo'))
// true
console.log(array.includes(NaN))
// true
指數(shù)運(yùn)算符
console.log(Math.pow(2, 10))
// 1024
console.log(2 ** 10)
// 1024
ECMAScript2017
對象的擴(kuò)展方法
// ECMAScript2017
const obj = {
one: 'value1',
two: 'value2'
}
// Object.values -------------
// 返回對象中所有值組成的數(shù)組
console.log(Object.values(obj))
// => [ 'value1', 'value2' ]
// Object.entries ------------
// 拿到key、value組成的數(shù)組
console.log(Object.entries(obj))
// => [ [ 'one', 'value1' ], [ 'two', 'value2' ] ]
for(const [key, value] of Object.entries(obj)){
console.log(key, value)
}
// one value1
// two value2
// 將對象轉(zhuǎn)換成map
console.log(new Map(Object.entries(obj)))
// => Map(2) { 'one' => 'value1', 'two' => 'value2' }
// Object.getOwnPropertyDescriptors --------------
// 獲取對象當(dāng)中屬性描述的完整信息
const p1 = {
firstName: 'li',
lastName: 'lei',
get fullName(){
return this.firstName + ' ' + this.lastName
}
}
console.log(p1.fullName)
// => li lei
const p2 = Object.assign({}, p1)
p2.firstName = 'zzz'
console.log(p2)
// { firstName: 'zzz', lastName: 'lei', fullName: 'li lei' }
// Object.assign 把fullName當(dāng)成普通的對象去復(fù)制了
// 此時(shí)可以使用getOwnPropertyDescriptors
const descriptors= Object.getOwnPropertyDescriptors(p1)
console.log(descriptors)
// {
// firstName: { value: 'li', writable: true, enumerable: true, configurable: true },
// lastName: {
// value: 'lei',
// writable: true,
// enumerable: true,
// configurable: true
// },
// fullName: {
// get: [Function: get fullName],
// set: undefined,
// enumerable: true,
// configurable: true
// }
// }
const p3 = Object.defineProperties({}, descriptors)
p3.firstName = 'hhhh'
console.log(p3)
// => { firstName: 'hhhh', lastName: 'lei', fullName: [Getter] }
console.log(p3.fullName)
// => hhhh lei
字符串填充
// 字符串填充
// String.prototype.padStart / String.prototype.padEnd --------------------
const books = {
html: 5,
css: 10,
javascript: 128
}
for(const [name, count] of Object.entries(books)){
console.log(`${name.padEnd(16, '-')} | ${count.toString().padStart(3, '0')}`)
}
// html------------ | 005
// css------------- | 010
// javascript------ | 128
// 在函數(shù)參數(shù)中添加尾逗號 ------------------------------
// 沒有實(shí)際功能
function foo(bar, baz,) {
}
const arr = [
100,
200,
300,
]
Async/Await
在async/await之前,我們有三種方式寫異步代碼
- 嵌套回調(diào)
- 以Promise為主的鏈?zhǔn)交卣{(diào)
- 使用Generators
async/await相比較Promise 對象then 函數(shù)的嵌套,與 Generator 執(zhí)行的繁瑣(需要借助co才能自動(dòng)執(zhí)行,否則得手動(dòng)調(diào)用next() ), Async/Await 可以讓你輕松寫出同步風(fēng)格的代碼同時(shí)又擁有異步機(jī)制,更加簡潔,邏輯更加清晰。
被async修飾的函數(shù)是異步函數(shù),異步函數(shù)就是代碼執(zhí)行起來不會(huì)阻塞后面后面代碼的進(jìn)程.
