ES6 經常使用知識點總結

ES6經常使用知識總結

以前總結了es5中js的一些知識點。這段時間看了石川blue老師講解的es6課程,結合阮一峯老師的es6教程,隨手作了一些筆記和總結分享給你們。內容仍是es6主要的知識點,基本沒有什麼創新點,主要是石川blue對裏面一些難懂的知識點分析的挺好的,對我我的理解es6頗有幫助,也但願對你們能有所幫助。

石川blue老師es6講解視頻百度雲地址:https://pan.baidu.com/s/1qZpUeni 密碼:2m9tjavascript

es6 是什麼

首先弄明白ECMA和js的關係。ECMA是標準,Javascript是ECMA的實現。由於js也是一種語言,但凡語言都有一套標準,而ECMA就是javascript的標準。
在2015年正式發佈了ECMAscript6.0,簡稱ES6,又稱爲ECMAscript2015。前端

1. let const

在es6以前,定義變量都是使用var,可是var存在一些問題,好比能夠重複聲明,僅支持函數做用域問題。因此es6設計了let和const來彌補不足的地方,下面看一下let和const具有哪些特性java

letes6

  • 不能重複聲明
  • 塊級做用域
  • 可修改let變量的值

constajax

  • 不可重複聲明
  • 塊級做用域
  • 不可修改const變量的值

2. 箭頭函數

箭頭函數在寫法上對es5作了一些修整,代碼看起來更顯得簡潔數組

  • 若是隻有一個參數,圓括號"()"能夠省略
  • 函數體若是隻有一句return語句,花括號也能夠省略
// 定義一個箭頭函數
let a = (arg)=>{ //  這裏=>符號就至關於function關鍵字
    return arg+=1
}
// 也能夠簡寫爲
let a = arg => arg+=1

箭頭函數也對this的指向作了修整
es6以前的函數的this指向調用函數時所在的對象,而箭頭函數的this指向函數定義時所在的對象異步

// 普通函數
 var obj = {
   say: function () {
     setTimeout(function() {
       console.log(this)
     });
   }
 }
// 箭頭函數
var obj = {
    say: function () {
        setTimeout(() => {
            console.log(this)
        });
    }
}
obj.say(); // obj

3. 解構賦值

容許按照必定模式,從數組和對象中提取值,對變量進行賦值,這被稱爲解構。好比:async

var [a,b] = [1,2]
// a=1  b=2

解構賦值必須符合下面三條規則:函數

  • 左右結構必須同樣
  • 右邊必須是一個合法的數據
  • 聲明和賦值必須一句話完成,不能把聲明與賦值分開
let [a, b] = [1, 2]                // 左右都是數組,能夠解構賦值
let {a, b} = {a:1, b:2}            // 左右都是對象,能夠解構賦值
let [obj, arr] = [{a:1}, [1, 2]]   // 左右都是對象,能夠解構賦值

let [a, b] = {a:1, b:2}            // err 左右結構不同,不能夠解構賦值
let {a,b} = {1, 2}                 // err 右邊不是一個合法的數據,不能解構賦值

let [a, b];
[a, b] = [1, 2]                    // err 聲明與賦值分開,不能解構賦值

4. 字符串

1.字符轉模板this

字符轉模板使用反引號(``)來定義字符串,字符串模板支持斷行,也能夠在字符串嵌入變量,很是方便,可謂是前端的福利。

let name = 'Jone'
`hello ${name},
how are you
`

2. startsWith(), endsWith(), includes()

  • startsWith() 表示參數字符串是否在原字符串的頭部,返回布爾值
  • endsWith() 表示參數字符串是否在原字符串的尾部,返回布爾值
  • includes() 表示是否在原字符串找到了參數字符串,返回布爾值

這三個方法都支持第二個參數,表示開始搜索的位置。

let s = 'Hello world!';

s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false

5. 數組

石川blue老師講的是es6對數組擴展了4個方法:map、reduce、filter、forEach。我認爲這四個方法好像在es5就有了(我的感受,有可能不對),無論這四個方法是es5仍是es6添加的方法了,你們對這幾個函數應該都挺熟悉,就很少作介紹了,下面說一下es6對數組擴展的其餘的新特性。
擴展運算符
擴展運算符(spread)是三個點(...),它能夠將一個數組拆分爲參數序列,也能夠收集剩下的全部的參數或者數組元素。

// 將一個數組拆分爲參數序列
let arr = [1,2]
function add (a,b) {
    return a+b
}
add(...arr)

// 收集剩下的全部的參數
function f(a, ...arr) {
    console.log(...arr) // 2 3
}
f(1,2,3)

// 用於數組複製
let arr1 = [1,2]
let arr2 = [...arr1] // 或者let [...arr2] = arr1

6. 面向對象 class

ES6 提供了更接近傳統語言的寫法,引入了 Class(類)這個概念,做爲對象的模板。經過class關鍵字,能夠定義類。
先看如何定義一個class類:

class User {
    constructor(name) {          // 構造器,至關於es5中的構造函數
        this.name = name         // 實例屬性
    }
    showName(){                  // 定義類的方法,不能使用function關鍵字,不能使用逗號分隔
        console.log(this.name)   
    }
}
var foo = new User('foo')

(1)constructor

  • es6中class類專用的構造器,至關於以前定義的構造函數,每一個類都必須有constructor,若是沒有則自動添加一個空的constructor構造器。
  • 建立實例的時候自動執行constructor函數
  • constructor中的this指向實例,而且默認返回this(實例)

(2)class類的prototype

其實class的基本類型就是函數(typeof User = "function"),既然是函數,那麼就會有prototype屬性。

類的全部方法都是定義在prototype上

class User {
  constructor() {
    // ...
  }

  toString() {
    // ...
  }

  toValue() {
    // ...
  }
}
User.toValue()             // err User.toValue is not a function
User.prototype.toValue()   // 能夠調用toValue方法

// 等同於

User.prototype = {
  constructor() {},
  toString() {},
  toValue() {},
};

(3)類的實例

  • 類的實例只能經過new來建立
  • 除了靜態方法,定義在類上的全部的方法都會被實例繼承
  • 除非定義在類的this對象上纔是實例屬性,不然都是定義在類的原型(prototype)上
//定義類
class Point {

  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }

}

var point = new Point(2, 3);

point.toString() // (2, 3)

point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty('toString') // true

(4)靜態方法

若是在類中定義一個方法的前面加上static關鍵字,就表示定義一個靜態方法,靜態方法不會被實例繼承,但會被子類繼承,因此不能經過實例使用靜態方法,而是經過類直接調用。

class User {
    constructor(name){
        this.name = name
    }
    static show(){
        console.log('123')
    }
}
class VipUser extends User{}
VipUser.show()                    // 123
User.show()                       // 123
var foo = new User('foo')
foo.show()                        // foo.show is not a function

(5)靜態屬性

  • class的靜態屬性指的是 Class 自己的屬性,目前只能經過Class.propName定義靜態屬性
  • 靜態屬性能夠被子類繼承,不會被實例繼承
class User{}
User.name = 'foo' // 爲class定義一個靜態屬性

class VipUser extends User{}
console.log(VipUser.name)         // foo

var foo = new User()
console.log(foo.name)             // undefined

(6)私有屬性和私有方法

es6是不支持私有屬性和私有方法,可是平常需求可能會用到私有屬性和私有方法,因此目前有一些提案,不過只是提案,還沒有支持。

7. 類的繼承

class經過extends關鍵字實現繼承:

class User {
    constructor(name){
        this.name = name
    }
    show(){...}
}
class VipUser extends User{
    constructor(vipName){      // 子類的構造器
        super(vipName)         // 調用父類的constructor。至關於User.prototype.constructor.call(this,vipName)
    }
    showVip(){...}
}

var v = new VipUser('foo')     // 建立實例
v instanceof VipUser           // v是子類VipUser的實例
v instanceof User              // v仍是父類User的實例

(1)super

super能夠當作函數使用,也能夠當作對象使用。

  • 當作函數使用

super做爲函數調用時,表明父類的構造函數,就是在子類的構造器中執行父類的constructor函數以獲取父類的this對象,由於子類沒有本身的this對象,因此ES6規定子類必須在constructor中執行一次super函數。super()函數只能在子類的constructor中執行,不能在其餘地方執行。

雖然super表明父類的構造器,可是super()在執行時內部的this指向子類,因此super()就至關於User.prototype.constructor.call(this)。

  • 當作對象使用

super能夠做爲對象調用父類的屬性和方法,在子類的普通方法中,指向父類的原型對象(即User.prototype);在子類的靜態方法中,指向父類。

class User {
  constructor(){
    this.x = 'hello'
  }
  show() {
    return 2;
  }
}

class VipUser extends User {
  constructor() {
    super();
    console.log(super.show()); // 2  此時super指向User.prototype,至關於User.prototype.show()
    console.log(super.x)       // undefined  沒法訪問實例屬性
  }
}

let vip = new VipUser();

因爲super對象在普通函數中使用super指向User.prototype,因此super只能訪問父類的原型上的方法,無法訪問父類的實例屬性和實例方法。

ES6規定若是在子類中使用super對象調用父類的方法時,方法內部的this指向子類

class User {
    constructor() {
        this.x = 1
    }
    show() {
        return this.x;
    }
}

class VipUser extends User {
    constructor() {
        super();
        this.x = 2
        console.log(super.show())   // 2   此時show()方法內部的this指向子類,因此輸出2,而不是1
    }
}

let vip = new VipUser();

上述代碼中雖然super.show()調用的是User.prototype.show(),可是因爲經過super對象調用父類方法時,方法內部的this指向子類,因此super.show()至關於 super.show().call(this),也就是User.prototype.show().call(this)

在子類的靜態方法中super對象指向父類,而不是父類的原型(User.prototype)。

class User {
    constructor() {
        this.x = 1
    }
    static fn() {
        console.log('父類靜態方法')
    }
}

class VipUser extends User {
    constructor() {
        super();
        this.x = 2
    }
    static childFn() {
        super.fn()       // 至關於User.fn()
    }
}

VipUser.childFn()

(2)類的prototype和__proto__屬性

在es5中每個對象都有__proto__屬性,指向對應的構造函數的prototype屬性。Class 做爲構造函數的語法糖,同時有prototype屬性和__proto__屬性,所以同時存在兩條繼承鏈。

  • 子類的__proto__屬性,表示構造函數的繼承,老是指向父類。
  • 子類prototype屬性的__proto__屬性,表示方法的繼承,老是指向父類的prototype屬性。
class User {
}

class VipUser extends User {
}

VipUser.__proto__ === User // true
VipUser.prototype.__proto__ === User.prototype // true

(3)實例的__proto__屬性

子類實例的__proto__屬性指向子類的原型(子類的prototype),子類實例的__proto__屬性的__proto__屬性指向父類的原型(父類的prototype)

class User {
}

class VipUser extends User {
}

var vip = new VipUser()
 
console.log(vip.__proto__ === VipUser.prototype)           // true
console.log(vip.__proto__.__proto__ === User.prototype)    // true

8. 對象

(1) 對象的擴展運算符 ...

對象的擴展運算符(...)能夠把對象可枚舉的屬性拆分爲鍵值對序列

  • 用於對象拷貝
let obj1 = {a:1,b:2}
let obj2 = {...obj1}
console.log(obj2)   // {a:1,b:2}
  • 用於合併對象
let obj1 = {a:1,b:2}
let obj2 = {c:3,d:4}
let obj3 = {a:100, ...obj1, ...obj2, e:5, f:6}  // {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6}
let obj4 = { ...obj1, ...obj2, e:5, f:6, a:100} // {a: 100, b: 2, c: 3, d: 4, e: 5, f: 6}

若是後面的屬性和前面的屬性key相同,則會覆蓋前面的值

(2) Object.assign

Object.assign方法用於對象的合併,將源對象(source)的全部可枚舉屬性,拷貝(淺拷貝)到目標對象。

若是目標對象與源對象有同名屬性,或多個源對象有同名屬性,則後面的屬性會覆蓋前面的屬性。

const target = { a: 1 };

const source1 = {a: 100, b: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:100, b:2, c:3}

Object.assign拷貝的屬性是有限制的,只拷貝源對象的自身屬性(不拷貝繼承屬性),也不拷貝不可枚舉的屬性。

(3) Object.keys(),Object.values(),Object.entries()

ES2017 引入了跟Object.keys、Object.values和Object.entries,做爲遍歷一個對象的補充手段,供for...of循環使用。

  • Object.keys() 返回一個數組,成員是參數對象全部可枚舉的屬性的鍵名
var obj = { foo: 'bar', baz: 42 };
Object.keys(obj) // ["foo", "baz"]
  • Object.values() 返回一個數組,成員是參數對象全部可枚舉屬性的鍵值。
const obj = { 100: 'a', 2: 'b', 7: 'c' };
Object.values(obj)  // ["b", "c", "a"]
  • Object.entries() 返回一個數組,成員是參數對象全部可枚舉屬性的鍵值對數組。
const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)  // [ ["foo", "bar"], ["baz", 42] ]

9. Generator函數

Generator能夠理解爲生成器,和普通函數沒多大區別,普通函數是隻要開始執行,就一直執行到底,而Generator函數是中間能夠停,搭配使用next函數繼續執行,用石川blue老師的話說就是踹一腳走一步。

(1)定義一個Generator函數

function * fn(){
    alert('a')
    yield
    alert('b')
}

var f = fn()
f.next()  // a
f.next()  // b

直接調用Generator函數,是什麼都不執行的,調用第一個next()纔開始執行,一直執行到第一個yield中止,第二次調用next(),從第一個yield執行到第二個yield中止,依次類推

(2)yield和nwxt

yield表明暫時暫停執行,next表明繼續執行。

yield和next能夠傳參數,也能夠有返回值

1  function * fn(arg){
2      console.log(arg)
3      let x = yield 100
4      console.log(x)
5      return 200
6  }
7 
8  var f = fn('hello')
9  let step1 = f.next(1)
10 console.log(step1)
11 let step2 = f.next(2)
12 

// 輸出結果爲
// hello
// {value: 100, done: false}
// 2
// {value: 200, done: true}

先看第八行 var f = fn('hello'),能夠給Generator第一次執行傳參數'hello',等第一次調用next的時候就開始Generator第一次執行,這時arg的值就是'hello'

而後第九行let step1 = f.next(1),這裏須要注意,第一次調用next(1)時傳的參數是做廢的,因此1不會被傳入到Generator函數中去,由於Generator函數第一次執行傳的參數是經過上面傳arg的方式傳的參數。因此調用next()開始Generator第一次執行(console.log(arg)),此時arg的值是‘hello’,因此首先打印‘hello’

而後第十行打印step1的返回值。其實next是有返回值的,返回值是一個對象,其中value屬性的值就是第一個yield返回的值100,done表明Generator時候執行完畢。

而後第十一行let step2 = f.next(2),這裏的next(2)的參數能夠傳到Generator函數中去,可是爲何把2傳給了x,估計不少人都想不明白,下面借鑑石川blue老師畫的一張圖來解釋一下:

clipboard.png

看到黃色方框圈的let step1 = f.next(1),對應執行黃色曲線圈的那部分代碼,紅色方框圈的let step12= f.next(2)對應執行紅色曲線圈的部分代碼,不要管爲何,你只須要這麼理解就ok了。因此第二次next(2)傳的參數2就傳給了x

而後再看第十二行console.log(step2),因此第二個next的返回值就是對象{value: 200, done: true},value表示最後Generator函數的返回值return 200,done表示Generator函數執行完畢。

(3) 適用場景

這種Generator函數適用多個異步請求之間有邏輯分析的狀況,好比有一個需求,先請求用戶數據,根據用戶數據的類型判斷用戶是普通用戶仍是VIP用戶,而後再根據判斷結果請求普通商品數據或者VIP商品數據。

// 藉助runner腳本,runner腳本規定Generator函數執行完一個next以後自動執行下一個next
runner(function() * (){
    let userData = yield $.ajax(...) // 請求用戶數據
    if(userData.type === 'vip') {
        let goods = yield $.ajax(...) // 請求vip商品數據
    } else {
        let goods = yield $.ajax(...) // 請求普通商品數據
    }
})

使用Generator函數使得代碼看起來更像同步代碼,其實使用Promise一樣能夠實現這種效果,只不過得須要在then()函數中嵌套請求。

9. async await

async其實就是對Generator的封裝,只不過async能夠自動執行next()。

async function read () {
    let data1= await new Promise(resolve => {
        resolve('100')
    })
    let data2 = await 200
    
    return 300
}

(1)async 返回值

async默認返回一個Promise,若是return不是一個Promise對象,就會被轉爲當即resolve的Promise,能夠在then函數中獲取返回值。

async必須等到裏面全部的await執行完,async纔開始return,返回的Promise狀態才改變。除非遇到return和錯誤。

async function fn () {
    await 100
    await 200
    return 300
}
fn().then(res => {
    console.log9(res) // 300
})

(3)await

await也是默認返回Promise對象,若是await後面不是一個Promise對象,就會轉爲當即resolve的Promise

若是一個await後面的Promise若是爲reject,那麼整個async都會中斷執行,後面的awiat都不會執行,而且拋出錯誤,能夠在async的catch中捕獲錯誤

async function f() {
  await Promise.reject('error');
  await Promise.resolve('hello world'); // 不會執行
}
f().then(res =>{

}).catch(err=>{
    console.log(err)  // error
})

若是但願一個await失敗,後面的繼續執行,可使用try...catch或者在await後面的Promise跟一個catch方法:

// try...catch
async function f() {
  try {
    await Promise.reject('出錯了');
  } catch(e) {
  }
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))   // hello world

// catch
async function f() {
  await Promise.reject('出錯了')
    .catch(e => console.log(e));   // 出錯了
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))  // hello world
相關文章
相關標籤/搜索