作爲前端開發工程師,盲目追逐框架似乎有點捨本逐末,要知道基本功纔是硬核。JavaScript 的語法這幾年一直在更新,不管我們是框架的核心開發者還是業務重塑者,學習下最新的 JavaScript 語法和能力是非常有好處的。下面我們通過幾個小示例來看下新語法的強大之處:
“初始化一個數組,要求數組的長度是 5,每個元素的默認值是 0”
這道題看似非常簡單,我們可能會這樣來寫代碼:
const arr = []
for(let i = 0; i < 5; i++){
arr.push(0)
}
可是如果你學習過 ES6 的語法,就會知道 Array 新增的原型對象方法上有個 fill 的 API,它可以輕鬆實現這個題目,代碼如下:
const arr = Array(5).fill(0)
這就是新的語法賦予 JavaScript 新的能力,如果我們不持續學習新的語法,寫出來的代碼很難是最簡、最優雅、性能最好。當然,閱讀其他同學或者開源代碼的時候也不一定能看懂。那麼 ES6 到底新增或者增強了哪些能力呢?我們來看下圖譜:
從這個圖譜不能看出 ES6 增加了很多新的語法,比如 Class、Generator、Proxy、Iterator 等。它們可以解決類、異步、代理、自定義遍歷等功能。不如我們再來看個小示例:
實現類與繼承。
在 ES6 之前實現類與繼承都是藉助函數來實現的,在繼承方面也是利用原型鏈。代碼如下:
function Component () {
this.id = Math.random().toString(36).slice(-5)
Object.defineProperty(this, 'id', {
writable: false
})
}
const com = new Component()
com.id = 3
console.log(com.id) // jklls
這段代碼的含義是定義一個組件類,類定義了一個屬性 id,這個 id 是隨機、只讀的。ES6 有了專門的語法來定義類。
class Component {
constructor () {
this.id = Math.random().toString(36).slice(-5)
Object.defineProperty(this, 'id', {
writable: false
})
}
}
const com = new Component()
com.id = 3
console.log(com.id)
在語義上看 ES6 的寫法更容易讀懂。不信,我們在看下繼承的寫法:
function Component () {
this.id = Math.random().toString(36).slice(-5)
Object.defineProperty(this, 'id', {
writable: false
})
}
function SubComponent () {
Component.call(this)
}
SubComponent.prototype = Component.prototype
const com = new SubComponent()
com.id = 3
console.log(com.id)
class Component {
constructor () {
this.id = Math.random().toString(36).slice(-5)
Object.defineProperty(this, 'id', {
writable: false
})
}
}
class SubComponent extends Component {
}
const com = new SubComponent()
com.id = 3
console.log(com.id)
上下代碼對比可以看出來 ES6 的方式要舒服很多,也更容易閱讀。藉助這個題我們再來思考 ES6 這個寫法還能繼續優化嗎?比如 Object.defineProperty 方法在構造函數裏顯得那麼格格不入。有沒有更優雅的寫法呢?不妨試試 ES6 新的語法 Proxy?
class Component {
constructor () {
this.proxy = new Proxy({
id: Math.random().toString(36).slice(-5)
}, {})
}
get id () {
return this.proxy.id
}
}
const com = new Component()
com.id = 3
console.log(com.id)
利用 Proxy 和 Class getter 方式就能保證 id 是隻讀的,在 proxy 實例化的時候也能保證 id “隨機”、“唯一”。有同學會說這個代碼有漏洞,proxy 還可以修改會導致 id 也可以被修改。說的沒錯,但是低估了 proxy 的能力,你再看:
class Component {
constructor () {
this.proxy = new Proxy({
id: Math.random().toString(36).slice(-5)
}, {
set (target, key, value) {
return false
}
})
}
get id () {
return this.proxy.id
}
}
const com = new Component()
com.proxy.id = 4
com.id = 3
console.log(com.id)
要知道 proxy 下面可以放很多跟 id 一樣的內容,這樣我們就不會一個一個用 Object.defineProperty 去顯示的定義“只讀”。用 class getter + proxy 的方式寫起來“不露痕跡”,大家是否享受這種寫法呢?當然,proxy 還有很多用武之地,比如把保護數據、數據校驗等等。
如果大家沒過癮,我們再看一個更強大的功能:自定義遍歷。
“我們數據庫裏存放着很多圖書的作者,這些作者按照圖書的類別進行分類,現在想遍歷所有作者該怎麼辦?”
let authors = {
allAuthors: {
fiction: [
'Agatha Christie',
'J. K. Rowling',
'Dr. Seuss'
],
scienceFiction: [
'Neal Stephenson',
'Arthur Clarke',
'Isaac Asimov',
'Robert Heinlein'
],
fantasy: [
'J. R. R. Tolkien',
'J. K. Rowling',
'Terry Pratchett'
]
}
}
我們希望可以對 authors 進行遍歷並得到所有作者的名單。
for (let author of authors) {
console.log(author)
}
本希望可以這做可是瀏覽器報錯了,告訴我們 authors 是不可遍歷的。那我們只能通過遍歷所有 key 的方式來實現:
for (let key in authors) {
let r = []
for (let k in authors[key]) {
r = r.concat(authors[key][k])
}
console.log(r)
// ["Agatha Christie", "J. K. Rowling", "Dr. Seuss", "Neal Stephenson", "Arthur Clarke", "Isaac Asimov", "Robert Heinlein", "J. R. R. Tolkien", "J. K. Rowling", "Terry Pratchett"]
}
雖然用 ES5 的方式實現了,可是我們仍希望用 for...of 的方式來實現,簡單便捷。ES6 增加了 Iterator 讓任意數據結構可以實現自定義遍歷器。直接上代碼:
authors[Symbol.iterator] = function () {
let allAuthors = this.allAuthors
let keys = Reflect.ownKeys(allAuthors)
let values = []
return {
next () {
if (!values.length) {
if (keys.length) {
values = allAuthors[keys[0]]
keys.shift()
}
}
return {
done: !values.length,
value: values.shift()
}
}
}
}
我們只需要對 authors 這個數據結構增加 Iterator 遍歷器接口即可用 for...of 的方式來遍歷了,瀏覽器不再報錯了。有沒有很驚豔?其實 ES6 之後 ES7、ES8、ES9、ES10相繼誕生,它們讓原生 JavaScript 的能力再次提升。
不學習原生 JavaScript 新技能註定會讓我們錯過更多魅力之城,一起結對學 ES6~ES10。