ES 新特性

ECMAScript

ECMAScript也是一門腳本語言,一般縮寫為ES,通常被看作為JavaScript的標準化規(guī)范,實際上JavaScript是ECMAScript的擴展語言或详。

ECMAScript只提供了最基本的語法(停留在語言層面),我們使用的JavaScript實現了ECMAScript語言的標準复罐,并在這個基礎之上,做了一些擴展雄家。

瀏覽器環(huán)境中的JavaScript組成效诅,可以看做是ECMAScript + Web 提供的 APIs

  • 瀏覽器環(huán)境中的JavaScript組成

Node環(huán)境中的JavaScript組成, 就是ECMAScript + Node APIs


Node環(huán)境中的JavaScript組成

JavaScript語言本身指的就是ECMAScript趟济,從2015年開始ES就保持每年一個版本的迭代乱投,伴隨著新版本的迭代,很多新特性陸續(xù)出現顷编,其中ES2015里面包含著和之前版本有很大的不同新功能戚炫。ES2015迭代時間過長、發(fā)布內容特別多媳纬。從ES2015之后双肤,ECMAScript就不再用版本號來命名,開始按照發(fā)行年份命名层宫,由于這個決定是在ES2015發(fā)行的過程之中定的杨伙,很多人已經習慣將ES2015稱為ES6.

市面上的瀏覽器也在不斷支持ECMAScript的新特性,學習這些新特性很有必要萌腿。


ECMAScript版本

ECMAScript 2015(ES2015)

下面我們來說一些主要的ES2015的新特性抖苦,可以分為四大類:

  • 解決原有語法上的一些問題或者不足 (let毁菱、const)
  • 對原有語法進行增強 (數組對象解構、模板字符串锌历、字符串擴展方法贮庞、... 運算符、箭頭函數究西、對象字面量增強)
  • 全新的對象窗慎、全新的方法、全新的功能(Proxy、Reflect遮斥、Promise)
    *全新的數據類型和數據結構 (Reflect)

let 與塊級作用域

作用域-- 指的就是代碼中的一個成員能夠起作用的范圍峦失。
在ES2015之前,ES中只有兩種作用域

  • 全局作用域
  • 函數作用域
  • 塊級作用域 (ES2015新增)就是我們代碼中用一對花括號所包裹起來的范圍术吗,例如if語句尉辑, for語句中的花括號,都會產生塊

以前塊沒有獨立的作用域隧魄,這就導致我們在塊中定義的變量隘蝎,外部也能訪問到。對于復雜的代碼來說是非常不安全的狮含。
我們可以只用let聲明塊級變量

let聲明不會有變量提升

console.log(year)
// 報錯
let year = 2021

console.log(foo)
// undefined
var foo = 'abc'

const 常量

const在聲明時必須設置一個初始值拱撵。不能將聲明和賦值分開


const

const聲明后不允許修改內存地址

const obj = {}
// 可以修改屬性
obj.name = 'uamru'

// 會修改內存地址--報錯
obj = {}

數組的解構

快速提取成員

const arr = [100, 200, 300]
const [foo, bar, baz] = arr
console.log(foo, bar, baz)
// 100, 200, 300

// 只獲取第三個成員
const [, , three] = arr
console.log(three)
//300

// 獲取從當前位置到最后一個成員
const [one, ...rest] = arr
console.log(rest)
//[ 200, 300 ]

// 結構數量小于數組的數量(會從第一個位置開始提人┎狻)
const [one] = arr
console.log(one)
// 100

//解構數量大于數組的長度 (得到undefined)
const [one, two, three, four] = arr
console.log(four)
// undefined

// 設置默認值(使用 =)
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

對象的解構

const obj = { name: 'umaru', age: 16}
let { name } = obj
console.log(name)
// umaru

// 對屬性重命名,解決名稱沖突
const name = 'tom'
const { name: newName} = obj
console.log(newName)

// 添加默認值
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標準語句 ${ 1 + 1 },語句的返回值 最終會被輸出到插值表達式存在的位置
const name = 'umaru'
const msg = `hey, ${name}`
console.log(msg)
// hey, umaru

帶標簽的模板字符串

可以對字符串內容進行加工

// 高級用法
// 帶標簽的模板字符串,定義模板字符串之前妆距,去添加一個標簽函匕,這個標簽是一個特殊的函數,使用標簽就是調用這個函數
// const str = tag`hello world`
// 使用標簽是console.log
const str = console.log`hello world`
// [ 'hello world' ]

const name = 'umaru'
const gender = true

// 定義一個標簽函數
function myTagFunc(strings, name){
    // 表達式靜態(tài)分割的內容
    console.log(strings)
    //[ 'hey, ', '.' ]
    console.log(name)
    // umaru
    return '123'
}
const msg = myTagFunc`hey, ${name}.`
// 返回值 就是這個標簽函數的返回值
console.log(msg)
// 123

示例:對字符串內容進行加工

const name = 'umaru'
const gender = true
// 定義一個標簽函數
function myTagFunc(strings, name, gender){
    // 表達式靜態(tài)分割的內容
    console.log(strings)
    // [ 'hey, ', ' is a ', '.' ]

    // 我們定義的true不是很好理解中剩,可以對這個值做加工
    gender = gender ? 'man' : 'woman'
    return strings[0] + name + strings[1] + gender + strings[2]
}
const msg = myTagFunc`hey, ${name} is a ${gender}.`
// 返回值就是這個標簽函數的返回值
console.log(msg)
// hey, umaru is a man.

字符串的擴展方法

  • includes(searchString, position) 判斷字符串是否包含指定的子字符串
  • startsWith(searchString, position) 判斷字符串是否以指定的子字符串開頭
  • endsWidth(searchString, position) 判斷字符串是否以指定的子字符串結尾
    參數解析:
    (1).searchString:必需抒寂,規(guī)定要搜索的子字符串结啼。
    (2).position:可選,規(guī)定在str中搜索searchString的結束位置屈芜,默認值為str.length郊愧,也就是字符串結尾處朴译。
const message = 'Error: foo is not defined.'
console.log(
    message.startsWith('Error'),
    message.endsWith('.'),
    message.includes('foo')
)
// true true true

函數參數默認值

// 之前
function foo(enable) {
    // 通過代碼設置默認值
    enable = enable === undefined ? true : enable
    console.log('foo invoke - enable: ')
    console.log(enable)
}
foo()
// true

// ES2015
// 使用 = 設置默認值
// 帶默認值的參數要放在最后
function foo2(bar, enable = true) {
    // 通過代碼設置默認值
    console.log('foo invoke - enable: ')
    console.log(enable)
}
foo(false)
// false
foo()
// true

剩余參數

比如我們console.log 可以接受任意多個參數,并且將這些參數打印出來

對于未知數量的參數属铁,以前我們都是使用arguments這個對象去接受眠寿,arguments是個偽數組。

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)

關于... 運算符

在ES2015中有一個 ... 運算符用于操作數組红选,有兩種層面

  • 1澜公、第一個叫做 展開運算符(spread operator),作用是和字面意思一樣喇肋,就是把東西展開坟乾。可以用在array和object上都行
// joining arrays
const odd = [1, 3, 5 ];
const nums = [2 ,4 , 6, ...odd];
console.log(nums); // [ 2, 4, 6, 1, 3, 5 ]

// 可以使用spread運算符在另一個數組內的任何位置插入數組蝶防。
const odd = [1, 3, 5 ];
const nums = [2, ...odd, 4 , 6];
console.log(nums)  //[ 2, 1, 3, 5, 4, 6 ]

// 還可以將擴展運算符與ES6解構表示法結合使用:
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 }
    1. 第二個叫做 剩余操作符(rest operator)甚侣,是解構的一種,意思就是把剩余的東西放到一個array里面賦值給它间学。一般只針對array的解構
function foo(...args){
    console.log(args)
}
foo(1, 2, 3, 4)

箭頭函數 Arrow Function

const inc = n => n+1

箭頭函數不會改變this的指向

// 箭頭函數和this
const person = {
    name: 'umaru',
    sayHi: function() {
        // 傳統(tǒng)函數this指向調用者
        console.log(`hi, my name is ${this.name}`)
        // => hi, my name is umaru
    },
    sayHi2: () => {
        // 傳統(tǒng)函數this指向調用者
        console.log(`hi, my name is ${this.name}`)
        // => hi, my name is undefined
    },
    sayHiAsync: function(){
        setTimeout(function() {
            // 這個函數在setTimeOut里面會被全局作用域調用
            console.log(this.name)
            // => undefined
        }, 1000)
    },
    // 借助閉包獲取this
    sayHiAsync2: function(){
        const _this = this;
        setTimeout(function() {
            // 這個函數在setTimeOut里面會被全局作用域調用
            console.log(_this.name)
            // => umaru
        }, 1000)
    },
    sayHiAsync3: function(){
        // 使用箭頭函數就不用這么麻煩殷费,指向的還是當前作用域
        setTimeout(() => {
            console.log(this.name)
            // => umaru
        }, 1000)
    }
}
person.sayHi()
person.sayHi2()
person.sayHiAsync()
person.sayHiAsync2()
person.sayHiAsync3()

對象字面量增強

const bar = '345'
const obj = {
  foo: 123,
  //bar: bar
  //變量名相同就可以省去
  bar,
//   method1: function() {
//       console.log('method111')
//   }
    method1() {
        console.log('method111')
        // method111
        // this就是當前對象
        console.log(this)
        //  foo: 123, bar: '345', method1: [Function: method1] }
    },
    // es2015可以直接使用動態(tài)屬性
    // 計算屬性名
    [Math.radom()]: 123
  
}

console.log(obj)
// { foo: 123, bar: '345', method1: [Function: method1] }
obj.method1()

對象擴展方法

Object.assign(target, ...sources)
參數:
target:目標對象
sources:源對象
用于將所有可枚舉屬性的值從一個或多個源對象分配到目標對象。它將返回目標對象低葫。
如果目標對象中的屬性具有相同的鍵详羡,則屬性將被源對象中的屬性覆蓋。后面的源對象的屬性將類似地覆蓋前面的源對象的屬性嘿悬。

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復制對象窒盐,不會修改到原對象

Object.is(value1, value2)
Object.is() 方法判斷兩個值是否為同一個值蟹漓。如果滿足以下條件則兩個值相等:

  • 都是undefined
  • 都是null
  • 都是true或false
  • 都是相同長度的字符串且相同字符串按相同順序排序
  • 都是相同對象(意味著引用地址相同)
  • 都是數組且
    • 都是 +0
    • 都是 -0
    • 都是 NaN
    • 都是 非零而且非NaN且為同一值

與 == 運算不同。 == 運算在判斷相等前會對兩邊的變量(如果不是同一類型)進項強制轉換塔鳍。而Object.is 不會強制轉換兩邊的值

與 === 運算頁不同, === 運算符將數字 -0和+0視為相等焚鲜,而將Number.NaN與NaN視為不相等糯彬。

Proxy

為對象設置代理器撩扒,可以輕松監(jiān)視到對象的讀寫
在ES2015之前,我們可以使用Object.defineProperty捕獲到對象的讀寫過程泉手,Vue2就是使用這個方法進行雙向綁定斩萌。

// 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和對對象當中方法的調用

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' }
Proxy

2、Proxy更好的支持數組對象的監(jiān)視
常見的一種方式驴党,重寫數組的操作方法倔既,以此去劫持對應方法的調用過程渤涌。

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 // 表示設置成功
    }
})

listProxy.push(100)

3茸俭、Proxy是以非侵入的方式監(jiān)管了對象的讀寫调鬓。一個已經定義好的對象,我們不需要對對象本身去做任何操作虹脯,就可以監(jiān)視到內部成員的讀寫归形。而object.defineProperty需要特殊的方式單獨定義對象當中需要監(jiān)視的屬性暇榴。

const person = {}
Object.defineProperty(person, 'name', {
    get(){
        console.log('name 被訪問')
        return person._name
    },
    set(value){
        console.log('name 被設置')
        person._name = value
    }
})
Object.defineProperty(person, 'age', {
    get(){
        console.log('age 被訪問')
        return person._age
    },
    set(value){
        console.log('age 被設置')
        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)的異步編程解決方案,通過鏈式調用的方式罚缕,解決了傳統(tǒng)異步編程中回調函數嵌套過深的問題怕磨。

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 {
    // 構造函數
    constructor(name){
        this.name = name
    }
    // 實例方法
    say() {
        console.log(`hi, my name is ${this.name}`)
    }
}
const p = new Person('umaru')
p.say()

實例方法vs靜態(tài)方法

ES2015中新增添加靜態(tài)成員的static關鍵詞
實例:添加一個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指向父類
        // 調用它就相當于調用父類構造函數
        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 數據結構

ES2015提供了新的數據結構Set或粮,它類似于數組渣锦,但是成員的值都是唯一的袋毙,沒有重復的值听盖。

Set 本身是一個構造函數,調用構造函數用來生成 Set 數據結構腰吟。
Set 函數可以接受一個數組(或類似數組的對象)作為參數毛雇,用來進行數據初始化灵疮。

let s = new Set([1, 2, 3, 4, 4]); 
console.log(s)
// => Set(4) { 1, 2, 3, 4 }

// 使用add鏈式調用
// 添加已有的值,這個值會被忽略掉
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) {}

// 常見使用方式伍派,為數組去重
const arr = [1, 2, 3, 4, 3, 6]
const result = new Set(arr)
console.log(result)
// => Set(5) { 1, 2, 3, 4, 6 }

// 如果我們想要再得到一個數組,可以使用Array.from
// const result2 = Array.from(new Set(arr))
// 或者使用...展開
const result2 = [...new Set(arr)]
console.log(result2)
// => [ 1, 2, 3, 4, 6 ]

Map數據結構

一個 Map的鍵可以是任意值晾腔,包括函數、對象或任意基本類型壁查。
Map 中的 key 是有序的睡腿。因此,當迭代的時候纤控,一個 Map 對象以插入的順序返回鍵值刻撒。

// Object
const obj = {}

obj[true] = 'value'
obj[123] = 'value'
obj[{a:1}] = 'value'
console.log(Object.keys(obj))
// 如果添加的鍵不是字符串疫赎,內部會將他們toString的結果當做鍵值
// => [ '123', 'true', '[object Object]' ]
// 加入我們如果想用對象作為鍵值捧搞,那這樣轉換過后都會變成一樣的值胎撇,就會有問題

// Map
const m = new Map()
const tom = {name: 'tom'}
m.set(tom, 90)
console.log(m)
// => Map(1) { { name: 'tom' } => 90 }

// m.has() 判斷某一個鍵是否存在
// console.log(m.has('2222'))
// m.delete() 刪除某一個鍵
// console.log(m.delete('222'))
// m.clear() 清空map

// 遍歷map
m.forEach((value, key) => {
    console.log(value, key)
    // => 90 { name: 'tom' }
})

Symbol

一種全新的原始數據類型,在ES2015之前雅采,對象的屬性都是字符串宝鼓,如果字符串重復愚铡,就會產生沖突碍舍。

一個具有數據類型 “symbol” 的值可以被稱為 “符號類型值”片橡。在 JavaScript 運行時環(huán)境中锻全,一個符號類型值可以通過調用函數 Symbol() 創(chuàng)建,這個函數動態(tài)地生成了一個匿名,唯一的值廊营。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)

// 解決對象屬性名不重復
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])
    }
}

// 在外部就只能調用普通名稱的成員
console.log(person[Symbol()])
// undefined
person.say()

Symbol每次創(chuàng)建都是一個新的值,如果想要使用同一個值掌眠,可以使用全局變量去復用一個Symbol的值蓝丙,也可以使用Symbol靜態(tài)方法for方法來實現。
這個方法內部維護了一個全局的注冊表沧烈,為字符串和Symbol值提供了一一對應的方法锌雀。如果我們傳入的不是字符串腋逆,這個方法內部會自動把他轉換為字符串惩歉。這會導致我們傳入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)定義一個對象粉楚,如果打印對象的toString方法會得到[object object]模软,
// 我們把這樣的字符串叫做對象的字符串標簽

// 如果我們想要更改對象的toString標簽,需要在對象中添加一個成員來標識
// 如果使用字符串去添加這種標識符回俐,就有可能跟內部的成員產生沖突鲫剿,
// ES要求我們使用Symbol值去使用這樣一個接口
// toStringTag內置常量
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作為遍歷所有數據結構的統(tǒng)一方式。

使用:遍歷數組政冻、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)
}
// 數組形式獲取鍵和值
// [ 'one', '123' ]
// [ 'two', '456' ]
// [ 'three', '789' ]


// 使用數組結構獲取key李丰,value
for(const [key, value] of m){
    console.log(key, value)
}
// one 123
// two 456
// three 789

使用for...of遍歷Object會報錯

const obj = {one: 123, two: 456}
for(const item of obj){
    console.log(item)
}
//for(const item of obj){
                  ^
//TypeError: obj is not iterable

我們前面說它可以遍歷說有數據結構,這是為什么呢?

ES中表示有結構的數據類型越來越多氏仗,像Array皆尔、Object慷蠕、Set砌们、Map等昔头,為了給各種各樣的數據結構提供統(tǒng)一遍歷方式揭斧,ES2015提供了Iterable接口盅视,意思是可迭代的接口闹击。
實現Iterable接口就是使用for...of遍歷的前提。

迭代器(Iterator)

實現Iterable接口的對象成艘,里面都有一個迭代器對象 通過調用 Symbol.iterator(他是一個函數)方法獲取

image.png

Symbol.iterator 為每一個對象定義了默認的迭代器赏半。該迭代器可以被for...of循環(huán)使用。

當需要對一個對象進行迭代時(比如開始用于一個for..of循環(huán)中)淆两,它的@@iterator方法都會在不傳參情況下被調用断箫,返回的迭代器用于獲取要迭代的值。

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'],
    // 使用計算屬性名 添加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)
}

迭代器模式(設計模式)

對外提供統(tǒng)一遍歷接口秋冰,讓外部不用關心內部結構

ES2015生成器 Generator

生成器對象是由一個 generator function 返回的,并且它符合可迭代協議迭代器協議

定義生成器 就是在普通的函數function后加一個*,這個函數就變成了一個生成器函數

// 普通函數
function foo(){
    console.log('umaru')
    return 100
}
const result = foo()
console.log(result)
// umaru
// 100



// 生成器函數
function * foo2(){
    console.log('umaru')
    return 100
}
const result2 = foo2()
console.log(result2)
// Object [Generator] {}

// 調用next方法,函數體才開始執(zhí)行
console.log(result2.next())
// umaru
// { value: 100, done: true }

生成器函數與yield
調用生成器的next方法芝发,返回一個由 yield表達式生成的值例书。

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 }

生成器應用

// Generator 應用
// 案例1 發(fā)號器
function * createIdMaker() {
    let id = 1
    // 不用擔心死循環(huán),調用next才會執(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 用生成器去實現iterator方法
// const todos = {
//     life: ['吃飯','睡覺','打豆豆'],
//     learn: ['語文','數學','外語'],
//     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: ['語文','數學','外語'],
    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)
}
// 吃飯
// 睡覺
// 打豆豆
// 語文
// 數學
// 外語
// 演講

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可以直接判斷數組中是否又該元素
// includes可以匹配 NaN
console.log(array.includes('foo'))
// true
console.log(array.includes(NaN))
// true

指數運算符

console.log(Math.pow(2, 10))
// 1024

console.log(2 ** 10)
// 1024

ECMAScript2017

對象的擴展方法

// ECMAScript2017
const obj = {
    one: 'value1',
    two: 'value2'
}
// Object.values -------------
// 返回對象中所有值組成的數組
console.log(Object.values(obj))
// => [ 'value1', 'value2' ]

// Object.entries ------------
// 拿到key荡陷、value組成的數組
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

// 將對象轉換成map
console.log(new Map(Object.entries(obj)))
// => Map(2) { 'one' => 'value1', 'two' => 'value2' }


// Object.getOwnPropertyDescriptors --------------
// 獲取對象當中屬性描述的完整信息

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當成普通的對象去復制了

// 此時可以使用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

// 在函數參數中添加尾逗號 ------------------------------
// 沒有實際功能
function foo(bar, baz,) {
    
}
const arr = [
    100,
    200,
    300,
]

Async/Await
在async/await之前,我們有三種方式寫異步代碼

  • 嵌套回調
  • 以Promise為主的鏈式回調
  • 使用Generators
    async/await相比較Promise 對象then 函數的嵌套骇径,與 Generator 執(zhí)行的繁瑣(需要借助co才能自動執(zhí)行晰筛,否則得手動調用next() )卦方, Async/Await 可以讓你輕松寫出同步風格的代碼同時又擁有異步機制黔宛,更加簡潔,邏輯更加清晰踢京。

被async修飾的函數是異步函數呐芥,異步函數就是代碼執(zhí)行起來不會阻塞后面后面代碼的進程.

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市奇钞,隨后出現的幾起案子,更是在濱河造成了極大的恐慌册着,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伞租,死亡現場離奇詭異,居然都是意外死亡槽奕,警方通過查閱死者的電腦和手機掩幢,發(fā)現死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門萧锉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人折晦,你說我怎么就攤上這事耙考。” “怎么了鄙早?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵霜瘪,是天一觀的道長膘流。 經常有香客問我,道長允扇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任恃锉,我火速辦了婚禮序臂,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己奥秆,他們只是感情好逊彭,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著构订,像睡著了一般侮叮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上悼瘾,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天囊榜,我揣著相機與錄音,去河邊找鬼亥宿。 笑死卸勺,一個胖子當著我的面吹牛,可吹牛的內容都是我干的箩绍。 我是一名探鬼主播孔庭,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼材蛛!你這毒婦竟也來了圆到?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤卑吭,失蹤者是張志新(化名)和其女友劉穎芽淡,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體豆赏,經...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡挣菲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了掷邦。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片白胀。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖抚岗,靈堂內的尸體忽然破棺而出或杠,到底是詐尸還是另有隱情,我是刑警寧澤宣蔚,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布向抢,位于F島的核電站,受9級特大地震影響胚委,放射性物質發(fā)生泄漏挟鸠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一亩冬、第九天 我趴在偏房一處隱蔽的房頂上張望艘希。 院中可真熱鬧,春花似錦、人聲如沸覆享。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽淹真。三九已至,卻和暖如春连茧,著一層夾襖步出監(jiān)牢的瞬間核蘸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工啸驯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留客扎,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓罚斗,卻偏偏與公主長得像徙鱼,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子针姿,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內容