這篇文章的出發點是爲了幫助前端開發者串聯 ES6先後的 JavaScript 知識,而且能夠快速瞭解 JavaScript 語言的最新進展。javascript
JavaScript 在當下處於特權地位,由於它是惟一能夠在瀏覽器中運行的語言,而且是被高度集成和優化過的。html
JavaScript 在將來有着極好的發展空間,跟上它的變化不會比如今更加的困難。個人目標是讓你可以快速且全面的瞭解這門語言可使用的新內容。前端
ECMAScript 簡介node
ES2015react
ES2016git
ES2017github
ES2018web
ESNext正則表達式
每當閱讀 JavaScript 相關的文章時,我都會常常遇到以下術語: ES3, ES5, ES6, ES7, ES8, ES2015, ES2016, ES2017, ECMAScript 2017, ECMAScript 2016, ECMAScript 2015 等等,那麼它們是指代的是什麼?
它們都是指代一個名爲 ECMAScript 的標準。
JavaScript 就是基於這個標準實現的,ECMAScript 常常縮寫爲 ES。
除了 JavaScript 之外,其它基於 ECMAScript 實現語言包括:
可是如今流傳最廣、影響最大的基於 ES 標準的語言實現無疑就是 JavaScript了
爲啥要用這個奇怪的名字呢?Ecma International 是瑞士標準協會,負責制定國際標準。
JavaScript 被建立之後,經由 Netscape 和 Sun Microsystems 公司提交給歐洲計算機制造商協會進行標準化,被採納的 ECMA-262 別名叫 ECMAScript。
This press release by Netscape and Sun Microsystems (the maker of Java) might help figure out the name choice, which might include legal and branding issues by Microsoft which was in the committee, according to Wikipedia.
IE9 以後微軟的瀏覽器中就看不到對 JScript 這個命名的引用了,取而代之都統稱爲 JavaScript。
所以,截至201x,JavaScript 成爲最流行的基於 ECMAScript 規範實現的語言。
目前的最新的 ECMAScript 版本是 ES2018。
於 2018 年 6 月發佈。
TC39(Technical Committee 39)是一個推進 JavaScript 發展的委員會。
TC39的成員包括各個主流瀏覽器廠商以及業務與瀏覽器緊密相連的公司,其中包括 Mozilla,Google ,Facebook,Apple,Microsoft,Intel,PayPal,SalesForce等。
每一個標準版本提案都必須通過四個不一樣的階段,這裏有詳細的解釋。
令我費解的是 ES 版本的命名依據有時根據迭代的版本號,有時卻根據年份來進行命名。而這個命名的不肯定性又使得人們更加容易混淆 JS/ES 這個兩個概念😄。
在 ES2015 以前,ECMAScript 各個版本的命名規範一般與跟着標準的版本更新保持一致。所以,2009年 ECMAScript 規範更新之後的的正式版本是 ES5。
Why does this happen? During the process that led to ES2015, the name was changed from ES6 to ES2015, but since this was done late, people still referenced it as ES6, and the community has not left the edition naming behind — the world is still calling ES releases by edition number. 爲何會發生這一切?在ES2015誕生的過程當中,名稱由ES6更改成ES2015,但因爲最終完成太晚,人們仍然稱其爲ES6,社區也沒有將版本號徹底拋之於後 — 世界仍然使用 ES 來定義版本號。
下圖比較清晰的展現了版本號與年份的關聯:
接下來,咱們來深刻了解 JavaScript 自 ES5 以來增長的特性。
ES2015 以前, var
是惟一能夠用來聲明變量的語句。
var a = 0
複製代碼
上面語句若是你遺漏了 var
,那麼你會把這個值(0)賦給一個未聲明的變量,其中聲明和未聲明變量之間存在一些差別。
在現代瀏覽器開啓嚴格模式時,給未聲明的變量賦值會拋出 ReferenceError 異常,在較老的瀏覽器(或者禁用嚴格模式)的狀況下,未聲明的變量在執行賦值操做時會隱式的變爲全局對象的屬性。
當你聲明一個變量卻沒有進行初始化,那麼它的值直到你對它進行賦值操做以前都是 undefined
。
var a //typeof a === 'undefined'
複製代碼
你能夠對一個變量進行屢次從新聲明,並覆蓋它:
var a = 1
var a = 2
複製代碼
你也能夠在一條聲明語句中一次聲明多個變量:
var a = 1, b = 2
複製代碼
做用域是變量可訪問的代碼部分。
在函數以外用 var
聲明的會分配給全局對象,這種變量能夠在全局做用域中被訪問到。而在函數內部聲明的變量只能在函數局部做用域被訪問到,這相似於函數參數。
在函數中定義的局部變量名如何跟全局變量重名,那麼局部變量的優先級更高,在函數內沒法訪問到同名的全局變量。
須要注意的是,var
是沒有塊級做用域(標識符是一對花括號)的,可是 var
是有函數做用域的,因此在新建立的塊級做用域或者是函數做用域裏面聲明變量會覆蓋全局同名變量,由於 var
在這兩種狀況下沒有建立新的做用域。
在函數內部,其中定義的任何變量在全部函數代碼中都是可見的,由於JavaScript在執行代碼以前實際上將全部變量都移到了頂層(被稱爲懸掛的東西)。 在函數的內部定義的變量在整個函數做用域中都是可見(可訪問),即便變量是在函數體末尾被聲明,可是仍然能夠再函數體開頭部分被引用,由於 JavaScript存在變量提高機制。爲避免混淆,請在函數開頭聲明變量,養成良好的編碼規範。
let
let
是ES2015中引入的新功能,它本質上是具備塊級做用域的 var
。它能夠被當前做用域(函數以及塊級做用域)以及子級做用域訪問到。
現代 JavaScript 開發者在 let
和 var
的選擇中可能會更傾向於前者。
若是
let
看起來是一個很抽象的術語,當你閱讀到let color = 'red'
這一段,由於使用let
定義了color 爲紅色,那麼這一切就變的有意義了。
在任何函數以外用 let
聲明變量,和 var
相反的是 它並不會建立全局變量。
const
使用變量 var
或 let
聲明的變量能夠被從新賦值。 使用 const
聲明的變量一經初始化,它的值就永遠不能再改變,即不可從新被賦值。
const a = 'test'
複製代碼
咱們不能再爲 a
進行賦值操做。然而,a
若是它是一個具備屬性或者方法的對象,那麼咱們能夠改變它的屬性或者方法。
const
並不意味着具備不可變性,只是保證用 const
聲明的變量的引用地址不被變動。
相似於 let
,const
也具備塊級做用域。
現代 JavaScript 開發者在遇到不會進行二次賦值的變量聲明時,應該儘可能使用 const
。
箭頭函數的引入極大的改變了代碼的書寫風格和一些工做機制。
在我看來,箭頭函數很受開發者歡迎,如今不多在比較新的代碼庫中看到 function
關鍵字了,雖然它並未被廢棄。
箭頭函數看起來會更加的簡潔,由於它容許你使用更短的語法來書寫函數:
const myFunction = function() {
//...
}
複製代碼
到
const myFunction = () => {
//...
}
複製代碼
若是函數體中只包含一條語句,你甚至能夠省略大括號並直接書寫這條語句:
const myFunction = () => doSomething()
複製代碼
參數在括號中傳遞:
const myFunction = (param1, param2) => doSomething(param1, param2)
複製代碼
若是該函數只有一個參數,那麼能夠省略掉括號:
const myFunction = param => doSomething(param)
複製代碼
因爲這種簡短的語法,使得咱們能夠更便捷的使用比較簡短的函數
箭頭函數支持隱式返回:能夠正常的 return
一個返回值可是能夠不使用 return
關鍵字。
隱式返回只在函數體內只包含一條語句的狀況下生效:
const myFunction = () => 'test'
myFunction() //'test'
複製代碼
須要注意的一種狀況,當返回一個對象時,記得將大括號括在括號中以免產生歧義,誤將其(大括號)解析爲函數體的大括號。
const myFunction = () => ({ value: 'test' })
myFunction() //{value: 'test'}
複製代碼
this
this
多是一個很難掌握的概念,由於它會根據上下文而進行變化,而且會在不一樣的 JavaScript的模式(是否爲嚴格模式)下表現出差別。
理解 this
這個概念對於箭頭函數的使用很重要,由於與常規函數相比,箭頭函數的表現很是不一樣。
對象的方法爲常規函數時,方法中的this
指向這個對象,所以能夠這樣作:
const car = {
model: 'Fiesta',
manufacturer: 'Ford',
fullName: function() {
return `${this.manufacturer} ${this.model}`
}
}
複製代碼
執行 car.fullName()
會返回 "Ford Fiesta"
。
若是上述方法使用是是箭頭函數,因爲箭頭中的 this
的做用域繼承自執行上下文,箭頭函數自身不綁定 this
,所以 this
的值將在調用堆棧中查找,所以在此代碼 car.fullName()
中不會返回常規函數那樣的結果,實際會返回字符串 "undefined undefined":
const car = {
model: 'Fiesta',
manufacturer: 'Ford',
fullName: () => {
return `${this.manufacturer} ${this.model}`
}
}
複製代碼
所以,箭頭函數不適合做爲對象方法。
一樣,箭頭函數也不適合使用在做爲建立構造函數,由於在實例化對象時會拋出 TypeError
。
因此在不須要動態上下文時請使用常規函數。
固然,在事件監聽器上使用箭頭函數也會存在問題。由於 DOM 事件偵聽器會自動將 this
與目標元素綁定,若是該事件處理程序的邏輯依賴 this
,那麼須要常規函數:
const link = document.querySelector('#link')
link.addEventListener('click', () => {
// this === window
})
const link = document.querySelector('#link')
link.addEventListener('click', function() {
// this === link
})
複製代碼
JavaScript 實現繼承的方式比較罕見:原型繼承。原型繼承雖然在我看來很棒,但與其它大多數流行的編程語言的繼承實現機制不一樣,後者是基於類的。
所以 Java、Python 或其它語言的開發者很難理解原型繼承的方式,所以 ECMAScript 委員會決定在原型繼承之上實現 class 的語法糖,這樣便於讓其它基於類實現繼承的語言的開發者更好的理解 JavaScript 代碼。
注意:class 並無對 JavaScript 底層作修改,你仍然能夠直接訪問對象原型。
以下是一個 class 的例子:
class Person {
constructor(name) {
this.name = name
}
hello() {
return 'Hello, I am ' + this.name + '.'
}
}
複製代碼
class 具備一個標識符,咱們可使用 new ClassIdentifier()
來建立一個對象實例。
初始化對象時,調用 constructor
方法,並將參數傳遞給此方法。
類聲明語句中也能夠增長類須要的一些原型方法。在這種狀況下 hello
是 Person
類的一個原型方法,能夠在這個類的對象實例上調用:
const flavio = new Person('Flavio')
flavio.hello()
複製代碼
一個子類能夠 extend 另外一個類,經過子類實例化出來的對象能夠繼承這兩個類的全部方法。
若是子類中的方法與父類中的方法名重複,那麼子類中的同名方法優先級更高:
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.」)
類沒有顯示的類變量聲明,但你必須在初始化構造函數 constructor
中去初始化類成員變量。
在子類中,你能夠經過調用super()
引用父類。
在類中,一般會把方法直接掛載到實例對象上,直接在實例對象上調用。
而靜態方法則是直接使用類名來調用,而不是經過對象實例調用:
class Person {
static genericHello() {
return 'Hello'
}
}
Person.genericHello() //Hello
複製代碼
JavaScript 沒有內置真正意義上的受保護的私有方法。
社區有解決方法,但我不會在這裏作講解。
你能夠經過增長方法 前綴 get
或者 set
建立一個 getter 和 setter,getter 和 setter會在你去獲取特定值或者修改特定值的時候執行 get
或者 set
內的相關方法。
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
}
}
複製代碼
函數 doSomething
接收一個 param1
參數。
const doSomething = (param1) => {
}
複製代碼
咱們能夠給 param1 設定默認值,若是在調用函數時未傳入參數,那麼該參數自動設定未默認值。
const doSomething = (param1 = 'test') => {
}
複製代碼
固然,這種機制一樣適用於多個參數:
const doSomething = (param1 = 'test', param2 = 'test2') => {
}
複製代碼
假如你的函數是一個具備特定屬性的對象該怎麼處理?
曾幾什麼時候,若是咱們必需要取一個對象的特定屬性值,爲了作兼容處理(對象格式不正確),你必須在函數中添加一些代碼:
const colorize = (options) => {
if (!options) {
options = {}
}
const color = ('color' in options) ? options.color : 'yellow'
...
}
複製代碼
經過解構,你能夠給特定屬性提供默認值,如此能夠大大簡化代碼:
const colorize = ({ color = 'yellow' }) => {
...
}
複製代碼
若是在調用 colorize
函數時沒有傳遞任何對象,咱們一樣能夠獲得一個默認對象做爲參數以供使用:
const spin = ({ color = 'yellow' } = {}) => {
...
}
複製代碼
模板字符串不一樣於 ES5 之前的版本,你能夠用新穎的方式使用字符串。
這個語法看起來很是簡便,只須要使用一個反引號替換掉單引號或雙引號:
const a_string = `something`
複製代碼
這個用法是獨一無二的,由於它提供了許多普通字符串所沒有的功能,以下:
下面讓咱們深刻每一個功能的細節。
在 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!`
複製代碼
須要特別留意空格在這裏是有特殊意義的,若是這樣作的話:
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'}`
複製代碼
標記模板多是一個聽起來不太有用的功能,但它實際上被許多流行的庫使用,如 Styled Components 、Apollo 、GraphQL客戶端/服務器庫,所以瞭解它的工做原理相當重要。
在 Styled Components 模板標籤中用於定義CSS字符串
const Button = styled.button` font-size: 1.5em; background-color: black; color: white; `
複製代碼
在 Apollo 中,模板標籤用於定義 GraphQL 查詢模式:
const query = gql` query { ... } `
複製代碼
上面兩個例子中的styled.button
和gql
模板標籤其實都是函數:
function gql(literals, ...expressions) {}
複製代碼
這個函數返回一個字符串,能夠是任意類型的計算結果。
字面量
(literals)是一個包含了表達式插值的模板字面量的序列。 表達式
(expressions)包含了全部的插值。
舉個例子:
const string = `something ${1 + 2 + 3}`
複製代碼
這個例子裏面的字面量
是由2個部分組成的序列。第1部分就是something
,也就是第一個插值位置(${})以前的字符串,第2部分就是一個空字符串,從第1個插值結束的位置直到字符串的結束。
這個例子裏面的表達式
就是隻包含1個部分的序列,也就是6
。
舉一個更復雜的例子:
const string = `something another ${'x'} new line ${1 + 2 + 3} test`
複製代碼
這個例子裏面的字面量
的序列裏面,第1個部分是:
;`something another `
複製代碼
第2部分是:
;` new line `
複製代碼
第3部分是:
;` test`
複製代碼
這個例子裏面的表達式
包含了2個部分:x
和6
。
拿到了這些值的函數就能夠對其作任意處理,這就是這個特性的威力所在。
好比最簡單的處理就是字符串插值,把字面量
和表達式
拼接起來:
const interpolated = interpolate`I paid ${10}€`
複製代碼
插值
的過程就是:
function interpolate(literals, ...expressions) {
let string = ``
for (const [i, val] of expressions) {
string += literals[i] + val
}
string += literals[literals.length - 1]
return string
}
複製代碼
給定一個object,你能夠抽取其中的一些值而且賦值給命名的變量:
const person = {
firstName: 'Tom',
lastName: 'Cruise',
actor: true,
age: 54, //made up
}
const {firstName: name, age} = person
複製代碼
name
和age
就包含了對應的值。
這個語法一樣能夠用到數組當中:
const a = [1,2,3,4,5]
const [first, second] = a
複製代碼
下面這個語句建立了3個新的變量,分別取的是數組a
的第0、一、4下標對應的值:
const [first, second, , , fifth] = a
複製代碼
ES2015賦予了對象字面量更大的威力。
原來的寫法:
const something = 'y'
const x = {
something: something
}
複製代碼
新的寫法:
const something = 'y'
const x = {
something
}
複製代碼
原型能夠這樣指定:
const anObject = { y: 'y' }
const x = {
__proto__: anObject
}
複製代碼
const anObject = { y: 'y', test: () => 'zoo' }
const x = {
__proto__: anObject,
test() {
return super.test() + 'x'
}
}
x.test() //zoox
複製代碼
const x = {
['a' + '_' + 'b']: 'z'
}
x.a_b //z
複製代碼
2009年的ES5引入了forEach()
循環,雖然很好用,可是它跟for
循環不同,無法break。
ES2015引入了**for-of**
循環,就是在forEach
的基礎上加上了break的功能:
//iterate over the value
for (const v of ['a', 'b', 'c']) {
console.log(v);
}
//get the index as well, using `entries()`
for (const [i, v] of ['a', 'b', 'c'].entries()) {
console.log(index) //index
console.log(value) //value
}
複製代碼
留意一下const
的使用。這個循環在每次迭代中都會建立一個新的做用域,因此咱們可使用const
來代替let
。
它跟for...in
的區別在於:
for...of
遍歷屬性值for...in
遍歷屬性名promise的通常定義: 它是一個代理,經過它能夠最終獲得一個值.
Promise是處理異步代碼的一種方式,能夠少寫不少回調。
異步函數是創建在promise API上面的,因此理解Promise是一個基本的要求。
一個promise被調用的時候,首先它是處於pending狀態。在promise處理的過程當中,調用的函數(caller)能夠繼續執行,直到promise給出反饋。
此時,調用的函數等待的promise結果要麼是resolved狀態,要麼是rejected狀態。可是因爲JavaScript是異步的,因此promise處理的過程當中,函數會繼續執行。
除了你的代碼和第三方庫的代碼以外,promise在用在現代的Web API中,好比:
在現代的JavaScript中,不使用promise是不太可能的,因此咱們來深刻研究下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
這個全局變量,若是爲true,就返回一個resolved promise,不然就返回一個rejected promise。
經過resolve
和reject
,咱們能夠獲得一個返回值,返回值能夠是字符串也能夠是對象。
上面講了怎麼建立一個promise,下面就講怎麼使用(consume)這個promise。
const isItDoneYet = new Promise()
//...
const checkIfItsDone = () => {
isItDoneYet
.then(ok => {
console.log(ok)
})
.catch(err => {
console.error(err)
})
}
複製代碼
運行checkIfItsDone()
方法時,會執行isItDoneYet()
這個promise,而且等待它resolve的時候使用then
回調,若是有錯誤,就用catch
回調來處理。
一個promise能夠返回另外一個promise,從而建立promise鏈條(chain)。
一個很好的例子就是Fetch API,它是基於XMLHttpRequest API的一個上層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()
,從根目錄的todos.json
文件中獲取一系列的TODO項目,而且建立一個鏈式promise。
運行fetch()
方法會返回一個response,它包含不少屬性,咱們從中引用以下屬性:
status
, 一個數值,表示HTTP狀態碼statusText
, 一個狀態消息,當請求成功的時候返回OK
response
還有一個json()
方法,它返回一個promise,返回內容轉換成JSON後的結果。
因此這些promise的調用過程就是:第一個promise執行一個咱們定義的status()
方法,檢查response status,判斷是否一個成功的響應(status在200和299之間),若是不是成功的響應,就reject這個promise。
這個reject操做會致使整個鏈式promise跳事後面的全部promise直接到catch()
語句,打印Request failed
和錯誤消息。
若是這個promise成功了,它會調用咱們定義的json()函數。由於前面的promise成功以後返回的response
對象,咱們能夠拿到並做爲第2個promise的參數傳入。
在這個例子裏面,咱們返回了JSON序列化的數據,因此第3個promise直接接收這個JSON:
.then((data) => {
console.log('Request succeeded with JSON response', data)
})
複製代碼
而後咱們把它打印到console。
在上一節的的例子裏面,咱們有一個catch
接在鏈式promise後面。
當promise鏈中的任意一個出錯或者reject的時候,就會直接跳到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.all()
來聲明一系列的promise,而後當它們所有resolve的時候再執行一些操做。
例子:
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.race()
運行全部傳遞進去的promise,可是隻要有其中一個resolve了,就會運行回調函數,而且只執行一次回調,回調的參數就是第一個resolve的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'
})
複製代碼
ES Module是用於處理模塊的ECMAScript標準。
雖然 Node.js 多年來一直使用 CommonJS標準,但瀏覽器卻從未有過模塊系統,由於模塊系統的決策首先須要 ECMAScript 標準化後才由瀏覽器廠商去實施實現。
這個標準化已經完成在 ES2015中,瀏覽器也開始實施實現這個標準,你們試圖保持一致,以相同的方式工做。如今 ES Module 能夠在 Chrome Safari Edge 和 Firefox(從60版本開始) 中使用。
模塊很是酷,他們可讓你封裝各類各樣的功能,同時將這些功能做爲庫暴露給其它 JavaScript 文件使用。
引入模塊的語法:
import package from 'module-name'
複製代碼
CommonJS 則是這樣使用:
const package = require('module-name')
複製代碼
一個模塊是一個 JavaScript 文件,這個文件使用 export
關鍵字 導出 一個或多個值(對象、函數或者變量)。例如,下面這個模塊提供了一個將字符串變成大寫形式的函數:
uppercase.js
export default str => str.toUpperCase()
複製代碼
在這個例子中,這個模塊定義了惟一一個 default export,所以能夠是一個匿名函數。不然,須要一個名稱來和其它 導出 作區分。
如今,任何其它的 JavaScript 模塊 能夠經過 import 導入 uppercase.js 的這個功能。
一個 HTML 頁面能夠經過使用了特殊的 type=module
屬性的 <script>
標籤添加一個模塊。
<script type="module" src="index.js"></script>
複製代碼
注意: 這個模塊導入的行爲就像
*defer*
腳本加載同樣。具體能夠看 efficiently load JavaScript with defer and async
須要特別注意的是,任何經過 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語法:
import { toUpperCase } from '/uppercase.js'
import { toUpperCase } from '../uppercase.js'
複製代碼
下面是錯誤的使用:
import { toUpperCase } from 'uppercase.js'
import { toUpperCase } from 'utils/uppercase.js'
複製代碼
由於這裏既不是使用絕對地址,也不是使用的相對地址。
咱們瞭解了上面的例子:
export default str => str.toUpperCase()
複製代碼
這裏生成了一個 default export。然而,你能夠經過下面的語法在一個文件裏面 導出 多個功能:
const a = 1
const b = 2
const c = 3
export { a, b, c }
複製代碼
另一個模塊可使用下面的方式 import 導入全部:
import * from 'module'
複製代碼
你也能夠經過解構賦值的方式僅僅 import 導出一部分:
import { a } from 'module'
import { a, b } from 'module'
複製代碼
爲了方便,你還可使用 as
重命名任何 import 的東西:
import { a, b as two } from 'module'
複製代碼
你能夠導入模塊中的默認出口以及經過名稱導入任何非默認的出口:
import React, { Component } from 'react'
複製代碼
這是一篇關於 ES 模塊的文章,能夠看一下: glitch.com/edit/#!/fla…
進行遠程獲取模塊的時候是遵循 CORS 機制的。這意味着當你引用遠程模塊的時候,必須使用合法的 CORS 請求頭來容許跨域訪問(例如:Access-Control-Allow-Origin: *
)。
結合 type="module"
、nomodule
一塊兒使用:
<script type="module" src="module.js"></script>
<script nomodule src="fallback.js"></script>
複製代碼
ES 模塊是現代瀏覽器中的一大特性。這些特性是 ES6 規範中的一部分,要在瀏覽器中所有實現這些特性的路還很漫長。
咱們如今就能使用它們!可是咱們一樣須要知道,有一些模塊會對咱們的頁面性能產生性能影響。由於瀏覽器必需要在運行時執行它們。
Webpack 可能仍然會被大量使用,即便 ES 模塊能夠在瀏覽器中執行。可是語言內置這個特性對於客戶端和 nodejs 在使用模塊的時候是一種巨大的統一。
任何字符串有了一些實例方法:
repeat()
codePointAt()
根據指定的次數重複字符串:
'Ho'.repeat(3) //'HoHoHo'
複製代碼
沒有提供參數以及使用 0
做爲參數的時候返回空字符串。若是給一個負數參數則會獲得一個 RangeError
的錯誤。
這個方法能用在處理那些須要 2 個 UTF-16 單元表示的字符上。
使用 charCodeAt
的話,你須要先分別獲得兩個 UTF-16 的編碼而後結合它們。可是使用 codePointAt()
你能夠直接獲得整個字符。
下面是一個例子,中文的 「𠮷」 是由兩個 UTF-16 編碼組合而成的:
"𠮷".charCodeAt(0).toString(16) //d842
"𠮷".charCodeAt(1).toString(16) //dfb7
複製代碼
若是你將兩個 unicode 字符組合起來:
"\ud842\udfb7" //"𠮷"
複製代碼
你也能夠用 codePointAt()
獲得一樣的結果:
"𠮷".codePointAt(0) //20bb7
複製代碼
若是你將獲得的 unicode 編碼組合起來:
"\u{20bb7}" //"𠮷"
複製代碼
更多關於 Unicode 的使用方法,參考個人Unicode guide。
ES2015 在 Object 類下引入了一些靜態方法:
Object.is()
肯定兩個值是否是同一個Object.assign()
用來淺拷貝一個對象Object.setPrototypeOf
設置一個對象的原型這個方法用來幫助比較對象的值:
使用方式:
Object.is(a, b)
複製代碼
返回值在下列狀況以外一直是 false
:
a
和 b
是同一個對象a
和 b
是相等的字符串(用一樣的字符組合在一塊兒的字符串是相等的)a
和 b
是相等的數字a
和 b
都是 undefined
, null
, NaN
, true
或者都是 false
0
和 -0
在 JavaScript 裏面是不一樣的值, 因此對這種狀況要多加當心(例如在比較以前,使用 +
一元操做符將全部值轉換成 +0
)。
在 ES2015
版本中引入,這個方法拷貝全部給出的對象中的可枚舉的自身屬性到另外一個對象中。
這個 API 的基本用法是建立一個對象的淺拷貝。
const copied = Object.assign({}, original)
複製代碼
做爲淺拷貝,值會被複制,對象則是拷貝其引用(不是對象自己),所以當你修改了源對象的一個屬性值,這個修改也會在拷貝出的對象中生效,由於內部引用的對象是相同的。:
const original = {
name: 'Fiesta',
car: {
color: 'blue'
}
}
const copied = Object.assign({}, original)
original.name = 'Focus'
original.car.color = 'yellow'
copied.name //Fiesta
copied.car.color //yellow
複製代碼
我以前提到過,源對象能夠是一個或者多個
:
const wisePerson = {
isWise: true
}
const foolishPerson = {
isFoolish: true
}
const wiseAndFoolishPerson = Object.assign({}, wisePerson, foolishPerson)
console.log(wiseAndFoolishPerson) //{ isWise: true, isFoolish: true }
複製代碼
設置一個對象的原型。能夠接受兩個參數:對象以及原型。
使用方法:
Object.setPrototypeOf(object, prototype)
複製代碼
例子:
const animal = {
isAnimal: true
}
const mammal = {
isMammal: true
}
mammal.__proto__ = animal
mammal.isAnimal //true
const dog = Object.create(animal)
dog.isAnimal //true
console.log(dog.isMammal) //undefined
Object.setPrototypeOf(dog, mammal)
dog.isAnimal //true
dog.isMammal //true
複製代碼
你能夠展開一個數組、一個對象甚至是一個字符串,經過使用展開操做符 ...
。
讓咱們以數組來舉例,給出:
const a = [1, 2, 3]
複製代碼
你可使用下面的方式建立出一個新的數組:
const b = [...a, 4, 5, 6]
複製代碼
你也能夠像下面這樣建立一個數組的拷貝:
const c = [...a]
複製代碼
這中方式對於對象仍然有效。使用下面的方式克隆一個對象:
const newObj = { ...oldObj }
複製代碼
用在字符串上的時候,展開操做符會以字符串中的每個字符建立一個數組:
const hey = 'hey'
const arrayized = [...hey] // ['h', 'e', 'y']
複製代碼
這個操做符有一些很是有用的應用。其中最重要的一點就是以一種很是簡單的方式使用數組做爲函數參數的能力:
const f = (foo, bar) => {}
const a = [1, 2]
f(...a)
複製代碼
(在以前的語法規範中,你只能經過 f.apply(null, a)
的方式來實現,可是這種方式不是很友好和易讀。)
剩餘參數(rest element)在和數組解構(array destructuring)搭配使用的時候很是有用。
const numbers = [1, 2, 3, 4, 5]
[first, second, ...others] = numbers
複製代碼
下面是展開元素 (spread elements):
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 properties):
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 }
複製代碼
屬性展開(Spread properties)容許咱們結合跟在 ...
操做符以後對象的屬性:
const items = { first, second, ...others }
items //{ first: 1, second: 2, third: 3, fourth: 4, fifth: 5 }
複製代碼
一個 Set 數據結構容許咱們在一個容器裏面增長數據。
一個 Set 是一個對象或者基礎數據類型(strings、numbers或者booleans)的集合,你能夠將它看做是一個 Map,其中值做爲映射鍵,map 值始終爲 true。
Set 能夠經過下面的方式初始化:
const s = new Set()
複製代碼
你可使用 add
方法向 Set 中添加項:
s.add('one')
s.add('two')
複製代碼
Set 僅會存貯惟一的元素,所以屢次調用 s.add('one')
不會重複添加新的元素。
你不能夠同時向 set 中加入多個元素。你須要屢次調用 add()
方法。
咱們能夠經過下面的方式檢查元素是否在 set 中:
s.has('one') //true
s.has('three') //false
複製代碼
使用 delete()
方法:
s.delete('one')
複製代碼
使用 size
屬性:
s.size
複製代碼
使用 clear()
方法:
s.clear()
複製代碼
使用 keys()
或者 values()
方法 - 它們等價於下面的代碼:
for (const k of s.keys()) {
console.log(k)
}
for (const k of s.values()) {
console.log(k)
}
複製代碼
entries()
方法返回一個迭代器,你能夠這樣使用它:
const i = s.entries()
console.log(i.next())
複製代碼
調用 i.next()
將會以 { value, done = false }
對象的形式返回每個元素,直到迭代結束,這時 done
是 true
。
你也能夠調用 set 的 forEach()
方法:
s.forEach(v => console.log(v))
複製代碼
或者你就直接使用 for..of
循環吧:
for (const k of s) {
console.log(k)
}
複製代碼
你可使用一些值初始化一個 set:
const s = new Set([1, 2, 3, 4])
複製代碼
const a = [...s.keys()]
// or
const a = [...s.values()]
複製代碼
一個 WeakSet 是一個特殊的 Set.
在 set 中,元素不會被 gc(垃圾回收)。一個 weakSet 讓它的全部元素都是能夠被 gc 的。weakSet 中的每一個鍵都是一個對象。當這個對象的引用消失的時候,對應的值就能夠被 gc 了。
下面是主要的不一樣點:
一個 weakSet 一般是在框架級別的代碼中使用,僅僅暴露了下面的方法:
一份map結構的數據容許咱們創建數據和key的關係
在引入Map以前,開發者一般把對象(Object)當Map使用,把某個object或value值與指定的key進行關聯:
const car = {}
car['color'] = 'red'
car.owner = 'Flavio'
console.log(car['color']) //red
console.log(car.color) //red
console.log(car.owner) //Flavio
console.log(car['owner']) //Flavio
複製代碼
ES6引入了Map數據結構,它爲咱們處理這種數據結構提供了一種合適的工具
Map的初始化:
const m = new Map()
複製代碼
你能夠經過set()
方法把條目設定到map中:
m.set('color', 'red')
m.set('age', 2)
複製代碼
你能夠經過get()
方法從map中取出條目:
const color = m.get('color')
const age = m.get('age')
複製代碼
使用delete()
方法:
m.delete('color')
複製代碼
使用clear()
方法:
m.clear()
複製代碼
使用has()
方法
const hasColor = m.has('color')
複製代碼
使用 size
屬性:
const size = m.size
複製代碼
你能夠用一組value來初始化一個map:
const m = new Map([['color', 'red'], ['owner', 'Flavio'], ['age', 2]])
複製代碼
任何值(對象,數組,字符串,數字)均可以做爲一個map的value值(使用key-value鍵值的形式),任何值也能夠用做key,即便是object對象。
若是你想經過get()
方法從map中獲取不存在的key,它將會返回undefined
const m = new Map()
m.set(NaN, 'test')
m.get(NaN) //test
const m = new Map()
m.set(+0, 'test')
m.get(-0) //test
複製代碼
Map提供了keys()
方法,經過該方法咱們能夠迭代出全部的key值:
for (const k of m.keys()) {
console.log(k)
}
複製代碼
Map提供了values()
方法,經過該方法咱們能夠迭代出全部的value值:
for (const v of m.values()) {
console.log(v)
}
複製代碼
Map提供了entries()
方法,經過該方法咱們能夠迭代出全部的鍵值對:
for (const [k, v] of m.entries()) {
console.log(k, v)
}
複製代碼
使用方法還能夠簡化爲:
for (const [k, v] of m) {
console.log(k, v)
}
複製代碼
const a = [...m.keys()]
複製代碼
const a = [...m.values()]
複製代碼
WeakMap是一種特殊的Map
在一個map對象中,定義在其上數據永遠不會被垃圾回收,WeakMap替而代之的是它容許在它上面定義的數據能夠自由的被垃圾回收走,WeakMap的每個key都是一個對象,當指向該對象的指針丟失,與之對應的value就會被垃圾回收走。
這是WeakMap的主要不一樣處:
WeakMap提供了以下幾種方法,這些方法的使用和在Map中同樣:
get(k)
set(k, v)
has(k)
delete(k)
關於WeakMap的用例不如Map的用例那麼明顯,你可能永遠也不會在哪裏會用到它,但從實際出發,WeakMap能夠構建不會干擾到垃圾回收機制的內存敏感性緩存,還能夠知足封裝的嚴謹性及信息的隱藏性需求。
Generators是一種特殊的函數,它可以暫停自身的執行並在一段時間後再繼續運行,從而容許其它的代碼在此期間運行(有關該主題的詳細說明,請參閱完整的「javascript生成器指南」)。
Generators的代碼決定它必須等待,所以它容許隊列中的其它代碼運行,並保留「當它等待的事情」完成時恢復其操做的權力。
全部這一切都是經過一個簡單的關鍵字「yield`」完成的。當生成器包含該關鍵字時,將中止執行。
generator生成器能夠包含許多yield
關鍵字,從而使本身能屢次中止運行,它是由*function
關鍵字標識(不要將其與C、C++或Go等低級語言中使用的取消指針引用操做符混淆)。
Generators支持JavaScript中全新的編程範式,包括:
這裏有一個解釋generator如何工做的例子:
function *calculator(input) {
var doubleThat = 2 * (yield (input / 2))
var another = yield (doubleThat)
return (input * doubleThat * another)
}
複製代碼
咱們先初始化它:
const calc = calculator(10)
複製代碼
而後咱們在generator中開始進行iterator迭代:
calc.next()
複製代碼
第一個迭代器開始了迭代,代碼返回以下object對象:
{
done: false
value: 5
}
複製代碼
具體過程以下:代碼運行了函數,並把input=10
傳入到生成器構造函數中,該函數一直運行直到抵達yield
,並返回yield
輸出的內容: input / 2 = 5
,所以,咱們獲得的值爲5,並告知迭代器尚未done(函數只是暫停了)。
在第二個迭代處,咱們輸入7:
calc.next(7)
複製代碼
而後咱們獲得告終果:
{
done: false
value: 14
}
複製代碼
7被做爲doubleThat
的值,注意:你可能會把input/2
做爲輸入參數,但這只是第一次迭代的返回值。如今咱們忽略它,使用新的輸入值7
,並將其乘以2.
而後,咱們獲得第二個yield的值,它返回doubleThat
,所以返回值爲14
。
在下一個,也是最後一個迭代器,咱們輸入100
calc.next(100)
複製代碼
這樣咱們獲得:
{
done: true
value: 14000
}
複製代碼
當迭代器完成時(沒有更多的yield關鍵字),咱們返回input * doubleThat * another
,這至關於10 * 14 * 100
。
這些都是在2015年的ES2015引入的特性,如今咱們深刻了解下ES2016,它的做用域範圍更小。
該特性引入了一種更簡潔的語法,同來檢查數組中是否包含指定元素。
對於ES6及更低版本,想要檢查數組中是否包含指定元素,你不得不使用indexOf
方法,它檢查數組中的索引,若是元素不存在,它返回-1
,因爲-1
被計算爲true,你需對其進行取反操做,例子以下:
if (![1,2].indexOf(3)) {
console.log('Not found')
}
複製代碼
經過ES7引入的新特性,咱們能夠如此作:
if (![1,2].includes(3)) {
console.log('Not found')
}
複製代碼
求冪運算符**
至關於Math.pow()
方法,可是它不是一個函數庫,而是一種語言機制:
Math.pow(4, 2) == 4 ** 2
複製代碼
對於須要進行密集數學運算的程序來講,這個特性是個很好的加強,在不少語言中,**
運算符都是標準(包括Python、Ruby、MATLAB、Perl等其它多種語言)。
這些都是2016年引入的特性,如今讓咱們進入2017年。
字符串填充的目的是給字符串添加字符,以使其達到指定長度。
ES2017引入了兩個String
方法:padStart()
和padEnd()
。
padStart(targetLength [, padString])
padEnd(targetLength [, padString])
複製代碼
使用例子:
該方法返回一個數組,數組包含了對象本身的全部屬性,使用以下:
const person = { name: 'Fred', age: 87 }
Object.values(person) // ['Fred', 87]
複製代碼
Object.values()
也能夠做用於數組:
const people = ['Fred', 'Tony']
Object.values(people) // ['Fred', 'Tony']
複製代碼
該方法返回一個數組,數組包含了對象本身的全部屬性鍵值對,是一個[key, value]
形式的數組,使用以下:
const person = { name: 'Fred', age: 87 }
Object.entries(person) // [['name', 'Fred'], ['age', 87]]
複製代碼
Object.entries()
也能夠做用於數組:
const people = ['Fred', 'Tony']
Object.entries(people) // [['0', 'Fred'], ['1', 'Tony']]
複製代碼
該方法返回本身(非繼承)的全部屬性描述符,JavaScript中的任何對象都有一組屬性,每一個屬性都有一個描述符,描述符是屬性的一組屬性(attributes),由如下部分組成:
Object.getOwnPropertyDescriptors(obj)
接受一個對象,並返回一個帶有描述符集合的對象。
ES6給咱們提供了Object.assign()
方法,它從一個一個或多個對象中複製全部可枚舉的屬性值,並返回一個新對象。
可是,這也存在着一個問題,由於它不能正確的複製一個具備非默認屬性值的屬性。
若是對象只有一個setter,那麼它就不會正確的複製到一個新對象上,使用Object.assign()
進行以下操做:
const person1 = {
set name(newName) {
console.log(newName)
}
}
複製代碼
這將不會起做用:
const person2 = {}
Object.assign(person2, person1)
複製代碼
但這將會起做用:
const person3 = {}
Object.defineProperties(person3,
Object.getOwnPropertyDescriptors(person1))
複製代碼
經過一個簡單的console控制檯,你能夠查看如下代碼:
person1.name = 'x'
"x"
person2.name = 'x'
person3.name = 'x'
"x"
複製代碼
person2
沒有setter,它沒能複製進去,對象的淺複製限定也出如今**Object.create()**方法中。
該特性容許在函數定義時有尾逗號,在函數使用時能夠有尾逗號:
const doSomething = (var1, var2,) => {
//...
}
doSomething('test2', 'test2',)
複製代碼
該改變將鼓勵開發者中止「在一行開始時寫逗號」的醜陋習慣
JavaScript在很短的時間內從回調函數進化到Promise函數(ES2015),並自從ES2017以來,異步JavaScript的async/wait語法變得更加簡單。 異步函數是Promise和generator的結合,基本上,它是比Promise更高級的抽象,我再重複通常:async/await是基於Promise創建的
它減小了圍繞promise的引用,並打破了Promise — 「不要打斷鏈式調用」的限制。
當Promise在ES2015中引入時,它的本意是來解決異步代碼的問題,它也確實作到了,但在ES2015和ES2017間隔的這兩年中,你們意識到:Promise不是解決問題的終極方案。
Promise是爲了解決著名的回調地獄而被引入的,但它自己也帶來了使用複雜性和語法複雜性。
Promise是很好的原生特性,圍繞着它開發人員能夠探索出更好的語法,所以當時機成熟後,咱們獲得了async函數
async函數使代碼看起來像是同步函數同樣,但其背後倒是異步和非堵塞的。
一個async函數會返回一個promise,以下例:
const doSomethingAsync = () => {
return new Promise(resolve => {
setTimeout(() => resolve('I did something'), 3000)
})
}
複製代碼
當你想要調用該函數時,你在前面加上了一個wait
,這樣調用就會被中止,直到該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')
複製代碼
上面的代碼將會在瀏覽器的console中打印出以下結果:
Before
After
I did something //after 3s
複製代碼
將 async
關鍵字標記在任何函數上,意味着這個函數都將返回一個 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 資源並解析它:
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()
複製代碼
這是使用 async/await 實現相同功能的例子:
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()
複製代碼
async 函數很是容易,而且它的語法比 Promise 更易讀。
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
複製代碼
調試 Promise 就很困難,由於調試器沒法跨越異步代碼,但調試 async/await 就很是的簡單,調試器會像調試同步代碼同樣來處理它。
WebWorkers 能夠在瀏覽器中建立多線程程序。
它們經過事件的方式來傳遞消息,從 ES2017 開始,你可使用 SharedArrayBuffer
在每個 Worker 中和它們的建立者之間共享內存數組.
因爲不知道寫入內存部分須要多長的週期來廣播,所以在讀取值時,任何類型的寫入操做都會完成,Atomics
能夠避免競爭條件的發生。
關於它的更多細節能夠在proposal中找到。
這是 ES2017,接下來我將介紹 ES2018 的功能。
ES2015 引入瞭解構數組的方法,當你使用時:
const numbers = [1, 2, 3, 4, 5]
[first, second, ...others] = numbers
複製代碼
and 展開參數:
const numbers = [1, 2, 3, 4, 5]
const sum = (a, b, c, d, e) => a + b + c + d + e
const sum = sum(...numbers)
複製代碼
ES2018 爲對象引入了一樣的功能。
解構:
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 }
複製代碼
for-await-of
容許你使用異步可迭代對象作爲循環迭代:
for await (const line of readLines(filePath)) {
console.log(line)
}
複製代碼
由於它使用的了 await
,所以你只能在 async
函數中使用它。
當一個 Promise 是 fulfilled 時,它會一個接一個的調用 then。
若是在這個過程當中發生了錯誤,則會跳過 then
而執行 catch
。
而 finally()
容許你運行一些代碼,不管是成功仍是失敗:
fetch('file.json')
.then(data => data.json())
.catch(error => console.error(error))
.finally(() => console.log('finished'))
複製代碼
ES2018 對正則表達式引入了許多改進,這些均可以在 flaviocopes.com/javascript-… 上找到。
如下是關於 ES2018 正則表達式改進的具體補充:
這是一個 lookahead: 你可使用 ?=
來匹配字符串,後面跟隨一個特定的字符串:
/Roger(?=Waters)/
/Roger(?= Waters)/.test('Roger is my dog') //false
/Roger(?= Waters)/.test('Roger is my dog and Roger Waters is a famous musician') //true
複製代碼
?!
能夠執行逆操做,若是匹配的字符串是no而不是在此後跟隨特定的子字符串的話:
/Roger(?!Waters)/
/Roger(?! Waters)/.test('Roger is my dog') //true
/Roger(?! Waters)/.test('Roger Waters is a famous musician') //false
複製代碼
Lookaheads 使用 ?=
Symbol,它們已經能夠用了。
Lookbehinds, 是一個新功能使用?<=
.
/(?<=Roger) Waters/ /(?<=Roger) Waters/.test('Pink Waters is my dog') //false /(?<=Roger) Waters/.test('Roger is my dog and Roger Waters is a famous musician') //true 複製代碼
若是一個 lookbehind 是否認,那麼使用 ?>!
:
/(?<!Roger) Waters/ /(?<!Roger) Waters/.test('Pink Waters is my dog') //true /(?<!Roger) Waters/.test('Roger is my dog and Roger Waters is a famous musician') //false 複製代碼
\p{…}
and \P{…}
在正則表達式模式中,你可使用 \d
來匹配任意的數字,\s
來匹配任意不是空格的字符串,\w
來匹配任意字母數字字符串,以此類推。
This new feature extends this concept to all Unicode characters introducing \p{}
and is negation \P{}
.
這個新功能擴展了unicode字符,引入了 \p{}
來處理
任何 unicode 字符都有一組屬性,例如 script
確認語言,ASCII
是一個布爾值用於檢查 ASCII 字符。你能夠將此屬性方在() 中,正則表達式未來檢查是否爲真。
/^\p{ASCII}+$/u.test('abc') //✅
/^\p{ASCII}+$/u.test('ABC@') //✅
/^\p{ASCII}+$/u.test('ABC🙃') //❌
複製代碼
ASCII_Hex_Digit
是另外一個布爾值,用於檢查字符串是否包含有效的十六進制數字:
/^\p{ASCII_Hex_Digit}+$/u.test('0123456789ABCDEF') //✅
/^\p{ASCII_Hex_Digit}+$/u.test('h') //❌
複製代碼
此外,還有不少其它的屬性。你能夠在()中添加它們的名字來檢查它們,包括 Uppercase
, Lowercase
, White_Space
, Alphabetic
, Emoji
等等:
/^\p{Lowercase}$/u.test('h') //✅
/^\p{Uppercase}$/u.test('H') //✅
/^\p{Emoji}+$/u.test('H') //❌
/^\p{Emoji}+$/u.test('🙃🙃') //✅
複製代碼
除了二進制屬性外,你還能夠檢查任何 unicode 字符屬性以匹配特定的值,在這個例子中,我檢查字符串是用希臘語仍是拉丁字母寫的:
/^\p{Script=Greek}+$/u.test('ελληνικά') //✅
/^\p{Script=Latin}+$/u.test('hey') //✅
複製代碼
閱讀github.com/tc39/propos… 獲取使用全部屬性的詳細信息。
In ES2018 a capturing group can be assigned to a name, rather than just being assigned a slot in the result array:
const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
const result = re.exec('2015-01-02')
// result.groups.year === '2015';
// result.groups.month === '01';
// result.groups.day === '02';
複製代碼
s
flag for regular expressionsThe s
flag, short for single line, causes the .
to match new line characters as well. Without it, the dot matches regular characters but not the new line:
/hi.welcome/.test('hi\nwelcome') // false
/hi.welcome/s.test('hi\nwelcome') // true
複製代碼
什麼是 ESNext ?
ESNext 是一個始終指向下一個版本 JavaScript 的名稱。
當前的 ECMAScript 版本是 ES2018,它於2018年6月被髮布。
歷史上 JavaScript 標準化的版本都是在夏季被髮布,所以咱們能夠預期 ECMAScript 2019 將於 2019 年的夏季被髮布。
因此在編寫本文時 ES2018 已經被髮布,所以 ESNext 指的是 ES2019。
ECMAScript 標準的提案是分階段組織的,第一到第三階段屬於功能性的孵化,第四階段的功能才最終肯定爲新標準的一部分。
在編寫本文時主要瀏覽器都實現了第四階段大部分的功能,所以我將在本文中介紹它們。
其中一些變化主要在內部使用,但知道發生了什麼這也很好。
第三階段還有一些其它功能,可能會在接下來的幾個月內升級到第四階段,你能夠在這個 Github 倉庫中查看它們:github.com/tc39/propos…。
flat()
是一個新的數組實例方法,它能夠將多維數組轉化成一維數組。
例子:
['Dog', ['Sheep', 'Wolf']].flat()
//[ 'Dog', 'Sheep', 'Wolf' ]
複製代碼
默認狀況下它只能將二維的數組轉化成一維的數組,但你能夠添加一個參數來肯定要展開的級別,若是你將這個參數設置爲 Infinity
那麼它將展開無限的級別到一維數組:
['Dog', ['Sheep', ['Wolf']]].flat()
//[ 'Dog', 'Sheep', [ 'Wolf' ] ]
['Dog', ['Sheep', ['Wolf']]].flat(2)
//[ 'Dog', 'Sheep', 'Wolf' ]
['Dog', ['Sheep', ['Wolf']]].flat(Infinity)
//[ 'Dog', 'Sheep', 'Wolf' ]
複製代碼
若是你熟悉數組的 map
方法,那麼你就知道使用它能夠對數組的每一個元素執行一個函數。
flatMap()
是一個新的數組實例方法,它將 flat()
和 map
結合了起來,當你指望在map
函數中作一些處理時這很是有用,同時又但願結果如同 flat
:
['My dog', 'is awesome'].map(words => words.split(' '))
//[ [ 'My', 'dog' ], [ 'is', 'awesome' ] ]
['My dog', 'is awesome'].flatMap(words => words.split(' '))
//[ 'My', 'dog', 'is', 'awesome' ]
複製代碼
有時候咱們並不須要將參數綁定到 try/catch 中。
在之前咱們不得不這樣作:
try {
//...
} catch (e) {
//handle error
}
複製代碼
即便咱們歷來沒有經過 e
來分析錯誤,但如今咱們能夠簡單的省略它:
try {
//...
} catch {
//handle error
}
複製代碼
Objects have an entries()
method, since ES2017.
從 ES2017 開始 Object將有一個 entries()
方法。
它將返回一個包含全部對象自身屬性的數組的數組,如[key, value]
:
const person = { name: 'Fred', age: 87 }
Object.entries(person) // [['name', 'Fred'], ['age', 87]]
複製代碼
ES2019 引入了一個新的 Object.fromEntries()
方法,它能夠從上述的屬性數組中建立一個新的對象:
const person = { name: 'Fred', age: 87 }
const entries = Object.entries(person)
const newPerson = Object.fromEntries(entries)
person !== newPerson //true
複製代碼
這些功能已經被 v8/Chrome 實現了近一年的時間,它將在 ES2019 中實現標準化。
trimStart()
刪除字符串首部的空格並返回一個新的字符串:
'Testing'.trimStart() //'Testing'
' Testing'.trimStart() //'Testing'
' Testing '.trimStart() //'Testing '
'Testing'.trimStart() //'Testing'
複製代碼
trimEnd()
刪除字符串尾部的空格並返回一個新的字符串:
'Testing'.trimEnd() //'Testing'
' Testing'.trimEnd() //' Testing'
' Testing '.trimEnd() //' Testing'
'Testing '.trimEnd() //'Testing'
複製代碼
如今你可使用 description
來獲取 Symbol 的值,而沒必要使用 toString()
方法:
const testSymbol = Symbol('Test')
testSymbol.description // 'Test'
複製代碼
在此以前 JSON 字符串中不容許使用分隔符(\u2028)和分隔符(\u2029)。
使用 JSON.parse 時,這些字符會致使一個 SyntaxError
錯誤,但如今它們能夠正確的解析並如 JSON 標準定義的那樣。
修復 JSON.stringify()
在處理 UTF-8 code points (U+D800 to U+DFFF)。
在修復以前,調用 JSON.stringify()
將返回格式錯誤的 Unicode 字符,如(a "�")。
如今你能夠安全放心的使用 JSON.stringify()
轉成字符串,也可使用 JSON.parse()
將它轉換回原始表示的形態。
函數總會有一個 toString
方法,它將返回一個包含函數代碼的字符串。
ES2019 對返回值作了修改,以免剝離註釋和其它字符串(如:空格),將更準確的表示函數的定義。
If previously we had
之前也許咱們這樣過:
function /* this is bar */ bar () {}
複製代碼
當時的行爲:
bar.toString() //'function bar() {}
複製代碼
如今的行爲:
bar.toString(); // 'function /* this is bar */ bar () {}'
複製代碼
總結一下,我但願這篇文章能夠幫助你瞭解一些最新的 JavaScript 以及咱們在 2019 年即將看見的內容。
Click here to get a PDF / ePub / Mobi version of this post to read offline
版權聲明: 閃電礦工翻譯組 譯文僅用於學習、研究和交流。版權歸 閃電礦工翻譯組、文章做者和譯者全部,歡迎非商業轉載。轉載前請聯繫譯者或 管理員 獲取受權,並在文章開頭明顯位置註明本文出處、譯者、校對者和閃電礦工翻譯組的完整連接,違者必究。