React V16入門手冊(2)

使用React須要什麼樣的JS能力

在深刻學習React以前找到你必需要掌握的內容

若是你想學習React時,首先你須要作一些事情。您必須熟悉一些必要的技術,特別是你將在React中反覆使用的一些最新JavaScript特性相關的技術。node

有些人覺得一些功能是React提供的一些特定的功能,實際上它們只是Javascript最新的語法。react

當即掌握這些新語法,成爲這方面的專家是不可能的,也不必。但你若是想要深刻研究React,那你就須要熟練掌握他們了。es6

我會把它們列舉下來讓你快速瞭解一下。編程

變量

變量是分配給標識符的文字,所以您能夠引用它並在後面的程序裏使用它。學習如何用JavaScript聲明一個。json

JavaScript中的變量沒有附加任何類型。將特定的文字類型分配給變量後,能夠稍後再給這個變量分配類型,而不會出現類型錯誤或任何問題。c#

因此這就是爲何有時候Javascript會報'untyped'這樣的問題。windows

一個變量必須在你使用以前就聲明。有三種方法能夠作到這一點,使用var、let或const,這三種方法在之後與變量的交互方式上有所不一樣。api

用Var

知道ES2015,var一直都是定義變量的惟一方法。跨域

var a = 0
複製代碼

若是你忘了添加var,你給未聲明的變量賦值,結果可能會有所不一樣,在嚴格模式下,會獲得一個錯誤,在舊的環境,或者禁用嚴格模式下,就會使得該變量成爲一個全局變量,賦值也天然會賦值給一個全局變量。數組

若是你沒有將變量初始化,那他的值就是undefined

var a //typeof a === 'undefined'

複製代碼

你能夠屢次聲明變量

var a = 1
var a = 2
複製代碼

最重要的,你能夠一次就聲明多個變量

var a = 1, b = 2
複製代碼

做用域是代碼中變量可見的部分(有效)。在函數外部聲明一個變量,變量就是全局的,全部的函數均可以得到該變量的值,但在函數內部聲明一個變量,變量就是局部的,只有在該函數內才能得到該變量的值,就像函數的一個參數。

在與全局變量同名的函數中定義的任何變量都優先於全局變量,並對其進行跟蹤。

重要的是要理解一個塊(由一對花括號標識)沒有定義一個新的做用域。新做用域只在建立函數時建立,由於var沒有塊做用域,而是函數做用域。

函數內部,在函數的全部代碼中,變量在任何位置都是可見的,即便函數的變量聲明在函數最後仍然能夠引用。,由於JavaScript執行代碼以前將全部變量提高。但爲了不混淆,老是在函數的開頭聲明變量。

用Let

let是ES2015中引入的一個新特性,它本質上是var的塊範圍版本。它的做用域僅限於定義它的塊、語句或表達式以及全部包含的內部塊。

現代JavaScript開發人員可能選擇只使用let,而徹底放棄使用var。

若是let看起來是一個模糊的術語,只要看let color = 'red'就是讓顏色是紅色,這就更容易理解了

與var相反,在任何函數外部定義let都不會建立全局變量。

用Const

用var或let聲明的變量能夠稍後在程序中更改並從新分配。一旦const被初始化,它的值就不再會被更改,也不能被從新分配到不一樣的值。

const a = 'test'
複製代碼

咱們不能給a常量定義不一樣的文字了。可是,若是對象提供了改變其內容的方法,咱們能夠對它進行變異。 const 定義了就不能修改或從新賦值

const和let同樣都有塊級做用域

現代JavaScript開發人員可能會選擇始終對不須要在程序中稍後從新分配的變量使用const。

爲何?由於咱們應該老是使用最簡單的結構來避免未來犯錯誤。

箭頭函數

箭頭函數是在ES6 / ECMAScript 2015中引入的,自從引入以來,它們完全改變了JavaScript代碼的外觀(和工做方式)。

在我看來,這種變化很是受歡迎,以致於你如今不多看到在現代代碼庫中使用function關鍵字。

從視覺上看,這是一個簡單而受歡迎的改變,它容許你用更短的語法編寫函數,從:

const myFunction = function() {
//...
}
複製代碼

const myFunction = () => {
//...
}
複製代碼

若是函數體只包含一條語句,則能夠省略括號,並將全部內容寫在一行中:

const myFunction = () => doSomething()
複製代碼

參數在括號中傳遞:

const myFunction = (param1, param2) => doSomething(param1, param2)
複製代碼

若是有一個(且只有一個)參數,能夠徹底省略括號:

const myFunction = param => doSomething(param)
複製代碼

因爲這種簡短的語法,箭頭函數讓咱們能使用小體積的函數。

隱式返回

箭頭函數容許你使用隱式返回:返回的值不須要使用return關鍵字。

當函數體中有一行語句時,它就能夠工做:

const myFunction = () => 'test'
myFunction() //'test'
複製代碼

另外一個例子,當返回一個對象時,記得將大括號括起來,以免它被認爲是括起來的函數的括號:

const myFunction = () => ({ value: 'test' })
myFunction() //{value: 'test'}
複製代碼

this在箭頭函數裏如何工做

this是一個很難理解的概念。由於它會根據上下文和JavaScript的模式(嚴格模式或非嚴格模式)產生不一樣的含義。

澄清這個概念很重要,由於在箭頭函數中的this與常規函數中的this很是不一樣。

當定義一個對象的方法時,在常規函數中的this指向這個對象,案例以下:

const car = {
    model: 'Fiesta',
    manufacturer: 'Ford',
    fullName: function() {
        return `${this.manufacturer} ${this.model}`
    }
}
car.fullName() //"Ford Fiesta"
複製代碼

執行car.fullName()時會返回"Ford Fiesta"

帶有箭頭函數的this做用域是從執行上下文中繼承的。箭頭函數根本不綁定this,所以它的值將在調用堆棧中查找。所以在這個代碼car.fullName()中不起做用,並將返回字符串「undefined undefined」:

const car = {
    model: 'Fiesta',
    manufacturer: 'Ford',
    fullName: () => {
        return `${this.manufacturer} ${this.model}`
    }
}
car.fullName() //"undefined undefined"
複製代碼

參考上面兩個例子,能夠看出,箭頭函數並不適用於對象的方法。

箭頭函數也不能用做構造函數,由於實例化對象時將報錯TypeError。當不須要動態上下文時,應該在這裏使用箭頭函數來代替常規函數。

在處理事件時還有一個問題,DOM事件偵聽器將this設置爲目標元素,若是在事件處理程序中依賴於此元素,則須要一個常規函數:

const link = document.querySelector('#link')
link.addEventListener('click', () => {
    // this === window
})
複製代碼
const link = document.querySelector('#link')
link.addEventListener('click', function() {
    // this === link
})
複製代碼

使用Rest和Spread處理對象和數組

學習使用JavaScript處理數組和對象的兩種現代技術

您可使用spread操做符展開數組、對象或字符串 ...

看下面的例子:

const a = [1, 2, 3]
複製代碼

你能夠像下面同樣建立一個新數組

const b = [...a, 4, 5, 6]
複製代碼

還能夠像下面同樣建立一個數組的副本

const b = [...a]
複製代碼

也能用這種方式拷貝一個對象

const newObj = { ...oldObj }
複製代碼

使用字符串時,spread操做符建立一個數組,數組內是每一個字符:

const hey = 'hey'
const arrayized = [...hey] // ['h', 'e', 'y']
複製代碼

這個操做符有一些很是有用的應用。最重要的是可以以一種很是簡單的方式將數組做爲函數參數:

const f = (foo, bar) => {}
const a = [1, 2]
f(...a)
複製代碼

(在過去,你能夠用f.apply(null, a) 來作這個可是這樣作不太好,可讀性也很差)

rest元素和spread元素在使用數組解構賦值時很是有用:

const numbers = [1, 2, 3, 4, 5]
[first, second, ...others] = numbers

const numbers = [1, 2, 3, 4, 5]
const sum = (a, b, c, d, e) => a + b + c + d + e
const sum = sum(...numbers)
複製代碼

ES2018引入了rest屬性,它們是相同的,可是是用於對象。

Rest屬性

const { first, second, ...others } = {
    first: 1,
    second: 2,
    third: 3,
    fourth: 4,
    fifth: 5
}
first // 1
second // 2
others // { third: 3, fourth: 4, fifth: 5 }
複製代碼

擴展屬性容許經過合併在擴展操做符以後傳遞的對象屬性來建立一個新對象:

const items = { first, second, ...others }
items //{ first: 1, second: 2, third: 3, fourth: 4, fifth: 5 }
複製代碼

數組和對象的解構賦值

學習如何使用解構賦值語法來處理JavaScript中的數組和對象

給定一個對象,使用解構賦值語法,您能夠提取一些值,並將它們放入命名變量:

const person = {
firstName: 'Tom',
lastName: 'Cruise',
actor: true,
age: 54 //made up
}
const { firstName: name, age } = person //name: Tom, age: 54
複製代碼

name和age包含了所須要的值。

這個語法也能夠在數組上使用

const a = [1, 2, 3, 4, 5]
const [first, second] = a
複製代碼

該語句經過從數組a中獲取索引0、一、4的項來建立3個新變量(first,second,fifth)

const [first, second, , , fifth] = a
複製代碼

模板字符串

在ES2015(又名ES6)中引入的模板字符串提供了一種聲明字符串的新方法,也提供了一些已經很是流行的有趣的新構造方法。

模板字符串介紹

模板文字是ES2015 / ES6的新特性,與ES5及如下版本相比,它容許你以一種新穎的方式處理字符串。 語法乍一看很是簡單,只需使用反引號而不是單引號或雙引號:

const a_string = `something`
複製代碼

它們是很獨特的的,由於它們提供了許多用引號構建的普通字符串所沒有的特性,特別是:

  • 它們提供了一個很好的語法來定義多行字符串
  • 它們提供了一種簡單的方法在字符串中用變量和表達式插值
  • 它們容許您使用模板標記建立DSL (DSL意味着特定於領域的語言,例如在React by style組件中使用DSL爲組件定義CSS)

讓咱們詳細研究上面三個東西

多行字符串

在es6以前,要建立一個跨越兩行的字符串,您必須在一行末尾使用\字符

const string =
  'first part \ second part'
複製代碼

這容許在兩行建立一個字符串,但它只呈如今一行:

first part second part
複製代碼

要在多行渲染字符串,你須要顯式地在每行末尾添加\n,以下所示:

const string =
  'first line\n \ second line'

//或者
const string = 'first line\n' + 'second line'
複製代碼

用模板字符串就簡單多了

一旦模板文字使用回車,你只需按回車鍵來建立一個沒有特殊字符的新行,它就會按原樣呈現:

const string = `Hey
this

string
is awesome!`

//結果以下
Hey
this

string
is awesome!
複製代碼

記住,模板字符串的空格是有意義的,因此這樣作:

const string = `First
                Second`
                
                
//結果以下
First
                Second
複製代碼

解決這個問題的一種簡單方法是,在第一行中設置一個空行,並在結束後加上trim()方法,這將消除第一個字符以前的任何空格:

const string = `
First
Second`.trim()
複製代碼

模板字符串插值

模板字符串提供了一種將變量和表達式插入字符串的簡單方法。 你能夠用這樣的語法${...}

const var = 'test'
const string = `something ${var}` //something test
複製代碼

${...}裏你能夠插入任何東西,甚至是表達式

const string = `something ${1 + 2 + 3}`
const string2 = `something ${foo() ? 'x' : 'y'}`
複製代碼

Class(類)

2015年,ECMAScript 6 (ES6)標準引入了類。

JavaScript有一種很是少見的實現繼承的方法:原型繼承。雖然原型繼承在我看來很好,但它不一樣於大多數其餘流行編程語言的繼承實現,後者是基於類的。

來自Java、Python或其餘語言的人很難理解原型繼承的複雜性,因此ECMAScript委員會決定在原型繼承的基礎上添加語法糖,這樣就像其餘流行實現中的基於類的繼承同樣。

這一點很重要:底層的JavaScript仍然是相同的,您仍是能夠用常規的方式訪問對象原型。

一個class類的定義

一個類長下面這樣

class Person {
  constructor(name) {
    this.name = name
  }
  hello() {
    return 'Hello, I am ' + this.name + '.'
  }
}
複製代碼

類有一個標識符,咱們可使用它來使用new ClassIdentifier()建立新對象

初始化對象時,調用constructor方法,並傳遞任意參數。

一個類也有它所須要的全部方法。在這種狀況下,hello是一個方法,這個類派生的全部對象均可以調用這個方法:

const flavio = new Person('Flavio')
flavio.hello()
複製代碼

類的實例

類能夠擴展另外一個類,使用該類初始化的對象繼承父類的全部方法。

若是繼承的類的方法與層次結構中較高層的類的名稱相同,則最近的方法優先:

class Programmer extends Person {
  hello() {
    return super.hello() + ' I am a programmer.'
  }
}
const flavio = new Programmer('Flavio')
flavio.hello()
//輸出 Hello, I am Flavio. I am a programmer.
複製代碼

類沒有顯式的類變量聲明,可是必須初始化構造函數中的任何變量

在類中,能夠用super()來引用父類。

靜態方法

通用方法是在實例上定義的,而不是在類上定義的。 靜態方法在類上執行:

class Person {
  static genericHello() {
    return 'Hello'
  }
}
Person.genericHello() //Hello
複製代碼

私有方法

JavaScript沒有內置的方法來定義私有或受保護的方法。(能夠參考閉包等概念)

有一些變通方法,但我不會在這裏描述它們。

Getters 和 setters

你能夠添加以get或set爲前綴的方法來建立getter和setter,這是根據您正在作的事情執行兩段不一樣的代碼:訪問變量或修改其值。

class Person {
  constructor(name) {
    this._name = name
  }
  set name(value) {
    this._name = value
  }
  get name() {
    return this._name
  }
}
複製代碼

若是您只有一個getter,則沒法設置該屬性,而且任何這樣作的嘗試都將被忽略:

class Person {
  constructor(name) {
    this.name = name
  }
  get name() {
    return this.name
  }
}
複製代碼

若是你只有一個setter,你能夠改變值,但不能從外部訪問它:

class Person {
  constructor(name) {
    this.name = name
  }
  set name(value) {
    this.name = value
  }
}
複製代碼

回調

計算機在設計上是異步的。

異步意味着事情能夠獨立於主程序流發生。

在當前的客戶端計算機中,每一個程序都運行一個特定的時間段,而後中止執行,讓另外一個程序繼續執行。這個東西以一種沒法察覺的速度循環運行,咱們認爲計算機同時運行許多程序,但這是一種錯覺(多處理器機器除外)。

程序在內部使用中斷——這是一種向處理器發出的信號,以引發系統的注意。

我不會深刻討論它的內部原理,可是要記住,程序是異步的是很正常的,在它們須要注意的時候中止它們的執行,而計算機能夠同時執行其餘事情。當程序正在等待來自網絡的響應時,它不能在請求完成以前中止處理器。

一般,編程語言是同步的,有些語言提供了一種方法來管理語言或庫中的異步性。C, Java, c#, PHP, Go, Ruby, Swift, Python,它們默認都是同步的。其中一些經過使用線程處理異步,生成一個新進程。

JavaScript默認是同步的,而且是單線程的。這意味着代碼不能建立新線程並並行運行。

一行接一行的執行代碼,例如:

const a = 1
const b = 2
const c = a * b
console.log(c)
doSomething()
複製代碼

可是JavaScript是在瀏覽器中誕生的,它最初的主要工做是響應用戶操做,好比onClick、onMouseOver、onChange、onSubmit等等。它如何使用同步編程模型實現這一點呢?

答案就在它所處的環境中。瀏覽器提供了一種方法,它提供了一組api來處理這種功能。

最近,NodeJS引入了一個非阻塞I/O環境,將這個概念擴展到文件訪問、網絡調用等。

你不知道用戶何時會點擊按鈕,因此你要作的是,爲點擊事件定義一個事件處理器。此事件處理程序接受一個函數,該函數將在事件觸發時被調用:

document.getElementById('button').addEventListener('click', () => {
  //item clicked
})
複製代碼

這就是回調(callback)

回調是一個簡單的函數,它做爲一個值傳遞給另外一個函數,只在事件發生時執行。咱們能夠這樣作,由於JavaScript具備一流的函數,能夠將其分配給變量並傳遞給其餘函數(稱爲高階函數)

將全部代碼包裝在windows對象上的load事件監聽器中是很常見的,它只在頁面準備好時才運行回調函數:

window.addEventListener('load', () => {
  //window loaded
  //do what you want
})
複製代碼

回調能夠用在任何地方,不僅是dom事件上

一個經常使用的定時器例子:

setTimeout(() => {
  // runs after 2 seconds
}, 2000)
複製代碼

XHR請求也接受回調,在本例中,它將一個函數分配給一個屬性,該屬性將在特定事件發生時被調用(在本例中,請求狀態發生變化):

const xhr = new XMLHttpRequest()
xhr.onreadystatechange = () => {
  if (xhr.readyState === 4) {
    xhr.status === 200 ? console.log(xhr.responseText) : console.error('error')
  }
}
xhr.open('GET', 'https://yoursite.com')
xhr.send()
複製代碼

處理回調中的錯誤

如何在回調處理錯誤?一個很是常見的策略是使用Node所採用的方法:任何回調函數中的第一個參數都是error對象:error-first回調

若是沒有錯誤,則對象爲null。若是有錯誤,它包含錯誤的一些描述和其餘信息。

fs.readFile('/file.json', (err, data) => {
  if (err !== null) {
    //handle error
    console.log(err)
    return
  }
  //no errors, process data
  console.log(data)
})
複製代碼

回調存在的問題

回調對於簡單的狀況很是有用

然而,每一個回調都會增長一個嵌套級別,當你有不少回調時,代碼開始變得很是複雜:

window.addEventListener('load', () => {
  document.getElementById('button').addEventListener('click', () => {
    setTimeout(() => {
      items.forEach(item => {
        //your code here
      })
    }, 2000)
  })
})
複製代碼

這只是一個簡單的4層代碼,但我見過更多級別的嵌套,這並不有趣。 怎麼解呢?

回調的替代品

從ES6開始,JavaScript引入了幾個特性,幫助咱們處理不涉及回調的異步代碼:

  • Promises (ES6)
  • Async/Await (ES8)

Promise

Promise是處理異步代碼的一種方法,無需在代碼中編寫太多回調。

儘管它們已經存在多年,可是在ES2015中已經被標準化並引入,如今在ES2017中已經被異步函數所取代。

Async函數使用promise API做爲它們的構建塊,所以理解它們是很是重要的,即便在較新的代碼中您可能會使用Async函數而不是promise。

簡而言之,Promise是如何工做的

一旦Promise被調用,它將以pending狀態啓動。這意味着調用方函數將繼續執行,同時等待Promise本身進行處理,並給調用方函數一些反饋。

此時,調用方函數等待它以resolved狀態或rejected狀態返回承諾,但如您所知,JavaScript是異步的,所以函數在Promise工做時繼續執行。

哪一個JS API使用Promise?

除了您本身的代碼和庫代碼以外,Promises還被標準的現代Web api(如Fetch或Service Workers)使用。

在現代JavaScript中,您不太可能不使用承諾,因此讓咱們開始深刻研究它們。

建立一個promise

Promise API公開了一個Promise構造函數,您可使用它進行初始化new Promise()

let done = true
const isItDoneYet = new Promise((resolve, reject) => {
  if (done) {
    const workDone = 'Here is the thing I built'
    resolve(workDone)
  } else {
    const why = 'Still working on something else'
    reject(why)
  }
})
複製代碼

正如您所看到的,promise檢查done全局常量,若是它爲真,則返回一個resolve的promise,不然返回一個 reject的promise。

使用resolve和reject,咱們能夠返回一個值,在上面的例子中,咱們只返回一個字符串,但它也能夠是一個對象。

使用promise

在上一節中,咱們介紹瞭如何建立承諾。 如今讓咱們看看如何使用承諾。

const isItDoneYet = new Promise()
//...
const checkIfItsDone = () => {
  isItDoneYet
    .then(ok => {
      console.log(ok)
    })
    .catch(err => {
      console.error(err)
    })
}
複製代碼

運行checkIfItsDone()將執行isItDoneYet() promise,並使用then回調等待它解決,若是出現錯誤,它將在catch回調中處理它。

promise鏈

一個promise能夠返回給另外一個promise,建立一個promise鏈。

Fetch API是連接承諾的一個很好的例子,它是XMLHttpRequest API之上的一層,咱們可使用它來獲取資源,並在獲取資源時對要執行的promise鏈進行排隊。

Fetch API是一種基於promise的機制,調用Fetch()至關於使用new promise()定義咱們本身的promise。

例子:

const status = response => {
  if (response.status >= 200 && response.status < 300) {
    return Promise.resolve(response)
  }
  return Promise.reject(new Error(response.statusText))
}
const json = response => response.json()
fetch('/todos.json')
  .then(status)
  .then(json)
  .then(data => {
    console.log('Request succeeded with JSON response', data)
  })
  .catch(error => {
    console.log('Request failed', error)
  })
複製代碼

在本例中,咱們調用fetch()從TODO中獲取TODO項的列表。在域根目錄中找到json文件,而後建立promise鏈。

運行 fetch()後返回一個響應,它有不少屬性,在這些屬性中咱們引用:

  • status表示HTTP狀態代碼的數值
  • statusText狀態消息,若是是OK就是請求成功

response也有一個json()方法,它返回一個promise,該promise將解析處理並轉換爲JSON的主體內容。

在這些前提下,會發生這樣的狀況:鏈中的第一個promise是咱們定義的一個函數status(),它檢查響應狀態,若是它不是一個成功響應(在200到299之間),則拒絕該promise。

此操做將致使promise鏈跳過列出的全部連接的promise,並直接跳到底部的catch()語句,記錄請求失敗的文本和錯誤消息。

若是成功,則調用咱們定義的json()函數。因爲上一個promise成功時返回響應對象,因此咱們將它做爲第二個promise的輸入。

在這種狀況下,咱們返回JSON處理過的數據,因此第三個promise直接接收JSON:

.then((data) => {
  console.log('Request succeeded with JSON response', data)
})
複製代碼

咱們只需將其打印到控制檯

處理錯誤

在上面的例子中,在上一節中,咱們有一個catch,它被附加到promise鏈中。

當promise鏈中的任何內容失敗並引起錯誤或拒絕promise時,該控件將轉到鏈中最近的catch()語句。

new Promise((resolve, reject) => {
  throw new Error('Error')
}).catch(err => {
  console.error(err)
})
// or
new Promise((resolve, reject) => {
  reject('Error')
}).catch(err => {
  console.error(err)
})
複製代碼

串聯錯誤

若是在catch()中引起錯誤,能夠附加第二個catch()來處理它,依此類推。

new Promise((resolve, reject) => {
  throw new Error('Error')
})
  .catch(err => {
    throw new Error('Error')
  })
  .catch(err => {
    console.error(err)
  })
複製代碼

用 Promise.all()來編排promise

若是您須要同步執行不一樣的promise,Promise.all()能夠幫助您定義一個promise列表,並在全部promise都獲得解析時執行某些操做。

例子:

const f1 = fetch('/something.json')
const f2 = fetch('/something2.json')
Promise.all([f1, f2])
  .then(res => {
    console.log('Array of results', res)
  })
  .catch(err => {
    console.error(err)
  })
複製代碼

ES2015析構賦值語法也容許您這樣作

Promise.all([f1, f2]).then(([res1, res2]) => {
  console.log('Results', res1, res2)
})
複製代碼

固然你不侷限於使用fetch,任何promise都是好的。

用Promise.race()編排promise

Promise.race()在您傳遞給它的某個promise解析時當即運行,而且在解析第一個promise的結果時,它只運行附加的回調一次(最早執行成功的promise後就返回該promise,其餘的promise就無論了)。

例子:

const promiseOne = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, 'one')
})
const promiseTwo = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'two')
})
Promise.race([promiseOne, promiseTwo]).then(result => {
  console.log(result) // 'two'
})
複製代碼

Async/Await

JavaScript在很短的時間內從回調發展到了promise (ES2015),並且因爲ES2017異步JavaScript使用async/ wait語法更加簡單。

異步函數是 promise和generate的組合,基本上,它們是比promise更高層次的抽象。讓我重複一遍:async/ wait基於promise。

爲何要Async/Await

這種方式減小了promise的使用,和‘不打破promise鏈’的限制

當promise在ES2015中引入時,它們是爲了解決異步代碼的問題,它們確實解決了這個問題,可是在ES2015和ES2017分開的兩年時間裏,很明顯promise並非最終的解決方案。

引入promise是爲了解決著名的回調地獄問題,但它們自己也帶來了複雜性,以及語法複雜性。

它們語義化更好,能夠向開發人員提供更好的語法,所以當時機成熟時,咱們就可使用async函數。

它們使代碼看起來是同步的,但在幕後它是異步的,非阻塞的。

async如何工做的

一個async函數返回一個promise,就像這個例子:

const doSomethingAsync = () => {
  return new Promise(resolve => {
    setTimeout(() => resolve('I did something'), 3000)
  })
}
複製代碼

當您想要調用這個函數時,您須要預先等待,調用代碼將中止,直到promise被resolve或reject。一個警告:函數必須定義爲async的。這裏有一個例子:

const doSomething = async () => {
  console.log(await doSomethingAsync())
}
複製代碼

一個快速的案例

這是一個簡單的async/await 的例子,用於異步運行一個函數:

const doSomethingAsync = () => {
  return new Promise(resolve => {
    setTimeout(() => resolve('I did something'), 3000)
  })
}
const doSomething = async () => {
  console.log(await doSomethingAsync())
}
console.log('Before')
doSomething()
console.log('After')
複製代碼

以上代碼將打印如下內容到瀏覽器控制檯:

Before
After
I did something //after 3s
複製代碼

全部的事都是promise

在任何函數前面加上async關鍵字意味着函數將返回一個promise。

即便它沒有顯式地返回promise,它也會在內部讓它返回一個promise。

這就是爲何這個代碼是有效的:

const aFunction = async () => {
  return 'test'
}
aFunction().then(alert) // This will alert 'test'
複製代碼

上面也和下面同樣

const aFunction = async () => {
  return Promise.resolve('test')
}
aFunction().then(alert) // This will alert 'test'
複製代碼

代碼更易讀

正如您在上面的示例中看到的,咱們的代碼看起來很是簡單。將其與使用純promise(帶有連接和回調函數)的代碼進行比較。

這是一個很是簡單的例子,當代碼更加複雜時,主要的好處就會顯現出來。

例如,下面是如何得到JSON資源,並使用promise對其進行解析:

const getFirstUserData = () => {
  return fetch('/users.json') // get users list
    .then(response => response.json()) // parse JSON
    .then(users => users[0]) // pick first user
    .then(user => fetch(`/users/${user.name}`)) // get user data
    .then(userResponse => response.json()) // parse JSON
}
getFirstUserData()
複製代碼

用await/async來實現上面的功能時

const getFirstUserData = async () => {
  const response = await fetch('/users.json') // get users list
  const users = await response.json() // parse JSON
  const user = users[0] // pick first user
  const userResponse = await fetch(`/users/${user.name}`) // get user data
  const userData = await user.json() // parse JSON
  return userData
}
getFirstUserData()
複製代碼

串聯多個異步函數

異步函數能夠很容易地連接起來,並且語法比普通的承諾更易讀

const promiseToDoSomething = () => {
  return new Promise(resolve => {
    setTimeout(() => resolve('I did something'), 10000)
  })
}
const watchOverSomeoneDoingSomething = async () => {
  const something = await promiseToDoSomething()
  return something + ' and I watched'
}
const watchOverSomeoneWatchingSomeoneDoingSomething = async () => {
  const something = await watchOverSomeoneDoingSomething()
  return something + ' and I watched as well'
}
watchOverSomeoneWatchingSomeoneDoingSomething().then(res => {
  console.log(res)
})

//輸出
I did something and I watched and I watched as well
複製代碼

更好debug

調試promise很困難,由於調試器不會跳過異步代碼。

Async/ wait使這一切變得很是簡單,由於對於編譯器來講,它就像同步代碼同樣。

ES 模塊

ES模塊是用於處理模塊的ECMAScript標準。

nodeJS多年來一直使用CommonJS標準,瀏覽器歷來沒有模塊系統,由於每個重大決策,好比模塊系統,都必須首先由ECMAScript標準化,而後由瀏覽器實現。

這個標準化過程用ES6完成,瀏覽器開始實現這個標準,試圖保持一切正常運行,以相同的方式工做,如今在Chrome、Safari、Edge和Firefox(從版本60開始)中都支持ES模塊。

模塊很是酷,由於它們容許您封裝各類功能,並將這些功能做爲庫公開給其餘JavaScript文件。

ES模塊語法

導入模塊的語法是:

import package from 'module-name'
複製代碼

用CommonJS 時:

const package = require('module-name')
複製代碼

模塊是一個JavaScript文件,它使用export關鍵字導出一個或多個值(對象、函數或變量)。例如,這個模塊導出一個函數,返回一個大寫字符串:

uppercase.js

export default str => str.toUpperCase()
複製代碼

在本例中,模塊定義了一個default export,所以它能夠是一個匿名函數。不然,它須要一個名稱來將其與其餘導出區分開。

如今,經過導入這個文件,任何其餘JavaScript模塊均可以用導入的uppercase.js提供的功能。 HTML頁面可使用 <script> 標記添加模塊,該標記具備特殊的type="module"屬性:

<script type="module" src="index.js"></script>
複製代碼

注意:此模塊導入的行爲相似於defer腳本加載。

須要注意的是,使用type="module"加載的任何腳本都是在嚴格模式下加載的。

在這個例子中,uppercase.js 模塊定義了一個 default export,因此當咱們導入它的時候,咱們能夠給它分配一個咱們喜歡的名字:

import toUpperCase from './uppercase.js'
複製代碼

咱們能夠這樣使用

toUpperCase('test') //'TEST'
複製代碼

您還可使用模塊導入的絕對路徑,來引用在另外一個域中定義的模塊:

import toUpperCase from 'https://flavio-es-modules-example.glitch.me/uppercase.js'
複製代碼

這也是有效的導入語法:

import { foo } from '/uppercase.js'
import { foo } from '../uppercase.js'
複製代碼

下面是不對的:

import { foo } from 'uppercase.js'
import { foo } from 'utils/uppercase.js'
複製代碼

它要麼是絕對的,要麼在名字前有一個./或者/。

其餘 import/export方法

咱們看到上面的例子:

export default str => str.toUpperCase()
複製代碼

這將建立一個默認導出。在一個文件中,你能夠導出多個東西,經過使用如下語法:

const a = 1
const b = 2
const c = 3
export { a, b, c }
複製代碼

另外一個模塊可使用import *來導入全部這些export的內容

import * from 'module'
複製代碼

你能夠只導入其中的幾個導出,使用析構賦值:

import { a } from 'module'
import { a, b } from 'module'
複製代碼

爲了方便,可使用as重命名任何導入

import { a, b as two } from 'module'
複製代碼

您能夠按名稱導入默認導出和任何非默認導出,如如下常見的React導入:

import React, { Component } from 'react'
複製代碼

CORS(跨域)

使用CORS獲取模塊。這意味着若是您引用來自其餘域的腳本,它們必須具備容許跨站點加載的有效CORS頭(好比Access-Control-Allow-Origin: *)

那麼不支持模塊的瀏覽器呢?

結合使用type="module"nomodule

<script type="module" src="module.js"></script>
<script nomodule src="fallback.js"></script>
複製代碼

ES模塊是現代瀏覽器中引入的最大特性之一。它們是ES6的一部分,但實現它們的道路是漫長的。

咱們如今可使用它們了!可是咱們還必須記住,若是有多個模塊,那麼頁面的性能將受到影響,由於這是瀏覽器在運行時必須執行更多一個步驟。

即便ES模塊在瀏覽器裏能用了,Webpack可能仍然是一個巨大的玩家,可是直接在語言中構建這樣的特性對於統一模塊在客戶端和nodeJS的工做方式是很是重要的。

下節預告:React的概念

相關文章
相關標籤/搜索