只要學不死,就往死裏學。
ECMAScript是一種能夠在宿主環境中執行計算並能操做可計算對象的基於對象的程序設計語言。
ECMAScript是一種語言設計標準,雖然一般也被稱爲JavaScript,可是嚴格意義上,ECMAScript並不等於JavaScript,前者是後者的設計標準,後者是前者的具體實現和擴展(在瀏覽器環境中,JavaScript不只僅實現了ECMAScript,同時還基於瀏覽器實現了針對DOM和BOM的操做)。編程
ECMAScript有不少版本,其中ECMAScript2015最爲突出。首先,其改變了傳統的以版本命名的方式,改成以年份命名。其次,其明確了標準發佈時間,由之前的時間不固定改成一年一個版本。最後,其增長了諸多特性,使js更像現代化編程語言。segmentfault
ES6能夠特指ECMAScript2015, 也能夠泛指ECMAScript2015及之後的全部版本。
ECMAScript2015新增的特性能夠總結爲四大類:api
在ECMAScript2015之前,js中存在兩種做用域:數組
ECMAScript2015新增了塊級做用域,代碼只在相應的代碼塊中起做用。瀏覽器
// before if (true) { var num = 1 } console.log(num) // 輸出1, 能夠在塊以外訪問變量 // after if (true) { let num = 1 } console.log(num) // 抱錯,不存在num, num只在{}構成的代碼塊中起做用
let,const和var同樣,都是聲明變量的方式,只是let,const會給變量添加做用範圍,規避了var帶來的諸多問題。數據結構
// before console.log(num) // 輸出1, 由於瀏覽器編譯js代碼的時候,會將變量提高,所以能夠在變量聲明前訪問變量 var num = 1 // after console.log(num) let num = 1 // 抱錯,由於let變量只能先聲明後使用。
// before for (var i = 0; i < 3; i++) { setTimeout(function () { console.log(i) // 輸出三次 3, setTimeout在執行時,操做的都是全局做用域中的i變量。 }, 100) } // after for (let i = 0; i < 3; i++) { setTimeout(function () { console.log(i) // 輸出0, 1, 2, 因爲每次setTimeout在執行的時候,都使用的是其做用域內的i,不會相互影響 }, 100) }
const的用法和let相同,只不過,const聲明的變量是隻讀的,不能修改。app
這裏的只讀,指的是不能修改變量指向的內存地址,可是能夠修改變量的屬性。
const obj = { name: 'zhangsan' } obj.name = 'lisi' // 能夠修改 obj = { name: 'lisi' } // 抱錯,修改了變量的內存指向地址,指向了新的地址。
解構容許你使用相似數組或對象字面量的語法將數組和對象的屬性賦給各類變量。dom
// before, 用下面的方式獲取數組中的每一項,並賦值給對應的變量 let arr = [1, 2, 3] let a = arr[0] let b = arr[0] let c = arr[0] // after let [a, b, c] = arr // 解構會將數組中的每一項自動賦值到對應位置的變量 let [, , c] = arr // 若是隻想獲取第三個,能夠傳入兩個, let [a, b] = arr // 若是隻想獲取前兩個,能夠省略後面。 // 對象也能夠解構 let obj = { name: 'zhangsan', age: 18, gender: true } let { name, age, gender } = obj // 因爲對象屬性沒有順序,所以須要變量和屬性名相對應的方式解構。 let { address = '北京' } = obj // 能夠爲不存在的屬性默認值 let { name: otherName = 'lisi'} = obj // 能夠爲某個屬性對應的變量重命名
js原有"", ''兩種方式表示字符串,ECMAScript2015新增了``表示字符串。異步
// 模版字符串支持多行字符串,方便包含換行符的字符串聲明 let div = `<div> this is div </div>` let name = 'zhangsan' let intro = `my name is ${name}` // 能夠嵌入變量等任何包含返回值的js合法語句,將被替換成返回值。
除了上述基礎應用外,還包含一種特殊的帶標籤的模版字符串。編程語言
let obj = { name: 'zhangsan', age: 18, gender: 0 } // 定義標籤方法 // strs表示用${}分割後的字符串數組,後續的參數表明${}對應的計算值 function changeGender(strs, name, gender) { console.log(strs, name, gender) // [ '我叫', ',我是', '' ] zhangsan 0 let g = gender === 0 ? '男人' : '女人' return strs[0] + name + strs[1] + g } // 使用 console.log(changeGender`我叫${obj.name},我是${obj.gender}`)
爲字符串添加了常見的includes,startsWith,endsWith方法。
const str = 'my name is zhangsan' console.log(str.includes('name')) // 判斷字符串中是否包含name console.log(str.startsWith('my')) // 判斷字符串是否以my開頭 console.log(str.endsWith('zhangsan')) // 判斷字符串是否以zhangsan結尾
增強對象字面量的聲明方式,簡化聲明代碼。
let name = 'zhangsan' let person = { // 將同名的變量添加到對象上 name, // 簡化對象上方法屬性定義 getName() { return this.name }, // []表示計算屬性,計算獲得的值做爲屬性的屬性名 [Math.random()]: 18 }
能夠將多個對象的屬性賦值到目標對象上,有則替換,沒有則添加。
let obj = { name: 'zhangsan', age: 18 } let obj2 = { address: '北京' } let person = { name: '' } console.log(Object.assign(person, obj, obj2)) // 複製一個全新的對象 let copied = Object.assign(person)
Object.is能夠用於判斷值是否相等。
console.log(0 == false) // true, == 會先將值作轉換,而後比較 console.log(0 === false) // false, ===會執行嚴格的判斷 // === 沒法正確識別的狀況 console.log(+0 === -0) // true, console.log(NaN === NaN) // false , === 認爲每一個NaN都是獨立的一個值 // Object.is能夠正確判斷上述的兩種狀況 console.log(Object.is(+0, -0)) // false, console.log(Object.is(NaN, NaN)) // true
在函數聲明的時候,能夠用更簡單的方式給參數添加默認值。
// before function intro(name, age) { name = name || 'default' console.log(`my name is ${name}`) } // after, 默認值參數必須放在非默認值參數的後面 function intro(age, name = 'default') { console.log(`my name is ${name}`) }
對於不定個數參數函數,能夠用剩餘參數將某個位置之後的參數放入到一個數組中。
// before function add() { // arguments獲取全部變量, arguments是一個僞數組 return Array.from(arguments).reduce(function (pre, cur) { return pre + cur }, 0) } console.log(add(1, 2, 3)) // after function intro(name, ...args) { console.log(name, args) // zhangsan [ 18, '北京' ] } intro('zhangsan', 18, '北京')
和默認參數相反,參數展開能夠在調用函數的時候將數組中的每一項依次賦值給函數中相應位置的參數。
function intro(name, age, address) { console.log(name, age, address) } const arr = ['zhangsan', 18, '北京'] // before // 1. 利用獲取每一個位置的值實現 intro(arr[0], arr[1], arr[2]) // 2. 利用apply方法實現 intro.apply(intro, arr) // after intro(...arr)
箭頭函數能夠簡化函數的聲明,尤爲是在回調函數的聲明上。
const arr = ['zhangsan', 18, '北京'] // before arr.forEach(function (item) { console.log(item) }) // after arr.forEach(item => console.log(item))
箭頭函數與普通的function函數的this指向不一樣,function的this指向調用者的上下文,是在調用時指定,而箭頭函數的this是在聲明時指定,指向父級的上下文。
const obj = { name: 'lisi', getName() { console.log(this.name) } } var name = 'zhangsan' let getName = obj.getName getName() // zhangsan, window調用,this指向window obj.getName() // lisi, obj調用,this指向obj //---------------------------------------------- const obj2 = { name: 'lisi', getNameFn() { return () => { console.log(this.name) } } } obj2.getNameFn()() // lisi, 箭頭函數的this指向父級上下文,即getNameFn的上下文,因爲getNameFn由obj2調用,所以this指向obj2
能夠利用Promise寫出更優雅的異步代碼,規避回調地獄。
new Promise(resolve => { setTimeout(() => { resolve(1) }, 100) }).then(value => { setTimeout(() => { resolve(value + 1) }, 100) }).then(value => { setTimeout(() => { resolve(value + 1) }, 100) })
經過Proxy代理能夠實現對對象編輯獲取等操做的攔截,從而在操做以前實現某種操做(例如Vue3.0就是利用Proxy實現數據雙向綁定)。其和Object.defineProperty做用相似,可是相比Object.defineProperty,其語法更爲簡潔,並且做用範圍更廣(如Object.defineProperty無法監控數組項的增長和刪除,Proxy能夠)。
let obj = { name: 'zhangsan', age: 18 } let objProxy = new Proxy(obj, { get(target, property) { console.log(`獲取${property}值`) return target[property] ? target[property] : 'default' }, set(target, property, value) { if (property === 'age') { value = value > 25 ? 25 : value } target[property] = value } }) console.log(objProxy.address) // default objProxy.age = 18 console.log(objProxy.age) // 18 objProxy.age = 30 console.log(objProxy.age) // 25
Proxy對象實例化時,第二個參數能夠傳入更多handler,以下圖:
let arr = [1, 2, 3] let arrProxy = new Proxy(arr, { set(target, property, value) { value = value > 10 ? 10 : value return target[property] = value } }) arrProxy.push(11) console.log(arr) //[ 1, 2, 3, 10 ], 攔截成功,和push類似的shift,unshift,pop都可觸發
Reflect是ES2015新增的靜態工具類,包含一系列針對對象的操做API,目的是提供統一的對象操做方式,結束目前混雜的對象操做。
let obj = { name: 'zhangsan', age: 18 } // before // get console.log(obj.name) console.log(obj['name']) // set obj['address'] = '北京' // delete delete obj.address // after // get console.log(Reflect.get(obj, 'name')) // set Reflect.set(obj, 'address', '北京') // delete Reflect.deleteProperty(obj, 'address')
提供統一的操做api不只代碼美觀,並且更容易讓新手上路。
Reflect提供的方法和Proxy的代理方法是一一對應的,若是Proxy中沒有傳入相應的代理方法,那麼Proxy內部默認使用Reflect對應方法實現。
在ES2015以前,js可使用function和原型鏈實現類的聲明。
function Person(name) { // 實例屬性 this.name = name } // 實例方法 Person.prototype.intro = function () { console.log(`my name is ${this.name}`) } // 靜態方法 Person.create = function (name) { return new Person(name) } // 使用 let zhangsan = Person.create('zhangsan') zhangsan.intro()
ES2015中添加了class關鍵字,能夠用class關鍵字快速聲明類。
class Person { // 靜態屬性 static tag = 'Person' constructor(name) { // 實例屬性 this.name = name } // 實例方法 intro() { console.log(`my name is ${this.name}`) } // 靜態方法, 利用static關鍵字 static create(name) { return new Person(name) } } // 使用 let zhangsan = Person.create('zhangsan') zhangsan.intro()
ES2015在提供快速聲明類的class關鍵字以外,還提供了extends關鍵字實現類的繼承
class Student extends Person { constructor(name, number) { // 調用父類的構造方法 super(name) // 聲明本身的實例屬性 this.number = number } say() { // 調用父類實例方法 super.intro() console.log(`個人學號:${this.number}`) } } // 使用 let zhangsan = new Student('zhangsan', '10001') zhangsan.say()
具體關於class的知識點還有不少,再也不贅述。
Set是ES2015新增的數據結構,用來表示集合的概念,特色是Set內部的值是不重複的,經常利用這個特色爲數組去重。
Set基本使用以下:
// 聲明集合,能夠傳入默認值,不傳則是空集合 let s = new Set([1]) // 新增 s.add(2) // 獲取集合長度 console.log(s.size) // 遍歷 s.forEach(item => { console.log(item) }) // 刪除 s.delete(2) // 清空集合 s.clear()
如何去重?
// 簡單數據去重 let arr = [1, 2, 1, 'one', 'two', 'one'] console.log(Array.from(new Set(arr))) // 對象數組去重 let objArr = [ { name: 'zhangsan', age: 17 }, { name: 'lisi', age: 16 }, { name: 'zhangsan', age: 17 } ] function unique(arr) { // Set中判斷對象是否重複是判斷對象所指內存地址是否相同,因此不能直接將對象數組放入Set中 // 將對象數組轉爲字符串數組,方便對比。 let arrStrs = arr.map(item => JSON.stringify(item)) let uniqueStrs = Array.from(new Set(arrStrs)) return uniqueStrs.map(item => JSON.parse(item)) } console.log(unique(objArr))
Map是ES2015新增的數據結構,用來表示鍵值對的集合,能夠彌補對象字面量的不足。對象字面量只能使用字符串做爲鍵,即便使用計算屬性傳入非字符串做爲鍵值,對象內部也會將其轉爲字符串,而Map沒有這個限制,其鍵能夠是任何數據。
let obj = new Map() let person = { name: 'zhangsan' } // 插入值 obj.set(person, '北京') // 判斷值是否存在 console.log(obj.has(person)) obj.forEach((item, key) => { console.log(item, key) }) // 刪除 obj.delete(person) // 清空 obj.clear() // 獲取大小 console.log(obj.size)
Symbol是ES2015引入的新的原始數據類型(和string, number等並列),用來表示獨一無二的值(只要調用Symbol(),那麼生成的值就不一樣)。
console.log(Symbol() === Symbol()) // Symbol能夠接受一個參數做爲標記值,這個值只是用於表示Symbol生成的變量的含義(描述自身),方便調試。 // 即便傳入相同的參數,那麼調用屢次返回的值也不相同 console.log(Symbol('bar') === Symbol('bar'))
let obj = { name: 'zhangsan', [Symbol()]: '打籃球' } console.log(obj[Symbol()]) // undefined , 使用者沒法獲取obj裏面的Symbol鍵值 console.log(Object.keys(obj)) // [ 'name' ] object的key方法也獲取不到 console.log(Object.getOwnPropertySymbols(obj)) // 只有使用getOwnPropertySymbols方法可以獲取到對象上定義的全部Symbol類型的鍵
Symbol.for: 方法會根據給定的鍵 key
,來從運行時的 symbol 註冊表中找到對應的 symbol,若是找到了,則返回它,不然,新建一個與該鍵關聯的 symbol,並放入全局 symbol 註冊表中。
const s = Symbol('bar') console.log(Symbol.for('bar') === s) // false Symbol()建立的值不會保存在註冊表中 console.log(Symbol.for('foo') === Symbol.for('foo')) // true
Symbol.iterator: 能夠用做對象的鍵的值,因爲其永遠是不重複的,因此不擔憂被覆蓋。在後面的實現Iterable迭代器接口會用到。
ES2015新增的數據遍歷方式,能夠用for...of遍歷任何數據(只要其實現了Iterable接口)。與forEach相比,其能夠在內部添加break關鍵字隨時中止遍歷。
const arr = [1, 'one', false, NaN, undefined] // 遍歷數組 for (let item of arr) { console.log(item) // 終止遍歷 if (!item) { break } } // 遍歷Set const set = new Set(arr) for (let item of set) { console.log(item) } // 遍歷Map let map = new Map() map.set({ name: 'zhangsan' }, '北京') map.set('age', 18) for (let [key, value] of map) { console.log(key, value) }
上節中提到,若是想要for...of遍歷某種數據,那麼該數據必須實現Iterable接口。
Iterable接口要求實現一個方法,該方法返回一個迭代器對象(iterator), 迭代器對象包含next方法,next方法返回一個包含value,done兩個鍵值的對象,value保存下一次遍歷時的數據,done用於表示迭代器是否完成。
const todos = { life: ['吃飯', '睡覺'], course: ['語文', '數學', '英語'], // 實現Iterable接口 [Symbol.iterator]: function () { let allTodos = [...this.life, ...this.course] let index = 0 return { next: function () { return { value: allTodos[index], done: index++ >= allTodos.length } } } } } for(let item of todos) { console.log(item) }
上述代碼體現了編程模式中經常使用的迭代器模式。
迭代器模式指的是提供一種方法順序訪問一個聚合對象中的各類元素,而又不暴露該對象的內部表示。
也就是說在todos對象中用了life和course兩個字段存儲須要作的事情,而外面在使用該對象的時候,不用關心todos對象內部如何存儲,只須要經過for...of遍歷獲取全部數據,這樣就下降了數據定義和數據使用的耦合度。當todos新增一個新的字段存儲須要作的事情時,只須要修改todos,而不須要需改使用者。
Generator生成器是ES2015新增的一種異步編程解決方案,用於解決異步編程中回調函數嵌套的問題,其一般使用*和yeild兩個關鍵字。
Generator生成器生成的方法是惰性執行的,只有調用者在調用next方法後其纔會執行,遇到yield關鍵字又中止。
function* createIdMaker() { let id = 0 while (true) { yield id++ } } const idMaker = createIdMaker() console.log(idMaker.next().value) // 0 console.log(idMaker.next().value) // 1 console.log(idMaker.next().value) // 2
const todos = { life: ['吃飯', '睡覺'], course: ['語文', '數學', '英語'], // 實現Iterable接口 [Symbol.iterator]: function* () { const all =[...this.life, ...this.course] for(let item of all) { yield item } } } for (let item of todos) { console.log(item) }
詳情見上一篇異步編程
Modules是ES2015提供的標準化模塊系統,模塊化爲你提供了一種更好的方式來組織變量和函數。你能夠把相關的變量和函數放在一塊兒組成一個模塊。todo
等js模塊化部分學完以後再補充
ES2016相對於ES2015是一個小的版本,只提供了以下的小特性。
提供includes方法方便查找數組中是否存在某一項。
const arr = [1, 'one', false, NaN, undefined] // before console.log(arr.indexOf(1) > -1) // true console.log(arr.indexOf(NaN) > -1) // false 對於NaN的查找出錯 // after console.log(arr.includes(NaN))// true,能夠正常查找
方便在大量的數學運算中使用。
// before console.log(Math.pow(2, 10)) // after console.log(2 ** 10)
ES2017和ES2016同樣,也是小版本。
和Object.keys相對應,獲取對象的全部值。
const obj = { name: 'zhangsan', age: 18 } Object.values(obj).forEach(item => { console.log(item) });
獲取鍵值對的數組,至關於將Object.keys和Object.values組合。
const obj = { name: 'zhangsan', age: 18 } Object.entries(obj).forEach(([key, value]) => { console.log(key, value) });
用於獲取對象的屬性描述信息,能夠用於補充解決Object.assign的問題。
const obj = { firstName: 'zhang', lastName: 'san', get fullName() { return this.firstName + ' ' + this.lastName } } let copied = Object.assign({}, obj) copied.firstName = 'li' console.log(copied.fullName) // zhangsan // Object.assign 在拷貝計算屬性時,將計算屬性的值拷貝過來,致使拷貝後的對象中計算屬性有問題
使用getOwnPropertyDescriptors就能夠避免這種拷貝問題。
const obj = { firstName: 'zhang', lastName: 'san', get fullName() { return this.firstName + ' ' + this.lastName } } let copied = {} Object.defineProperties(copied, Object.getOwnPropertyDescriptors(obj)) copied.firstName = 'li' console.log(copied.fullName)
用於在字符串的前面或者後面填充必定數量的某種字符,可使字符串顯示的更加美觀。
const obj = { number: '1', age: '18' } Object.entries(obj).forEach(([key, value]) => { console.log(`${key.padEnd(10, '-')}|${value.padStart(3, '0')}`) }) //number----|001 //age-------|018
容許像數組和對象那樣,在聲明和調用函數時在末尾加上逗號,只是爲了方便部分人的書寫習慣,沒有實在乎義。
const arr = [1, 2, 3,] // 在數組末尾能夠加入逗號,不影響數組聲明 // 聲明函數時能夠在參數的末尾加入逗號 function add(a, b,) { return a + b } // 調用函數時能夠在參數的末尾加入逗號 console.log(add(1, 2,))
新增的異步編程解決方案,一樣是用於解決回調函數嵌套的問題。詳情見上一篇異步編程。
和數組的展開和剩餘類似,ES2018容許在對象上使用展開和剩餘。
const obj = { one: 1, two: 2, three: 3, four: 4, five: 5 } const { one, four, ...rest } = obj // one => 1, four => 4 // rest => { two: 2, three: 3, five: 5} const obj2 = { foo: 'bar', ...rest } // obj2 => { foo: 'bar', two: 2, three: 3, five: 5} // 展開時,同名屬性會覆蓋 const obj3 = { foo: 'bar', two: 200, ...rest } // obj3 => { foo: 'bar', two: 2, three: 3, five: 5} const obj4 = { foo: 'bar', ...rest, two: 200 } // obj4 => { foo: 'bar', two: 200, three: 3, five: 5}
// 環視 const intro = '張三是張三,張三丰是張三丰,張三不是張三丰,張三丰也不是張三' // 向後否認 正向確定 只有在張三後面不是豐的時候,纔會用李四替代張三 const res1 = intro.replace(/張三(?!豐)/g, '李四') // 向後確定 正向確定 只有在張三後面跟着豐的時候,纔會用李四替代張三 const res2 = intro.replace(/張三(?=豐)/g, '李四') // 向前確定 反向確定 只有在00前面是A的時候,纔會用88替代00 const res3 = 'A00 B00'.replace(/(?<=A)00/g, '88') // 向前否認 反向確定 只有在00前面不是A的時候,纔會用88替代00 const res4 = 'A00 B00'.replace(/(?<!A)00/g, '88')
爲正則組添加別名,方便查找正則組
const date = '2020-05-20' const reg = /(?<year>\d{4})-(?<mouth>\d{2})-(?<day>\d{2})/ const res = reg.exec(date) console.log(res) // 能夠在groups對象下,經過別名year獲取值 console.log(res.groups.year) // 2020
添加finally方法,不論Promise是resolve仍是reject,finally都會被執行。
new Promise((resolve, reject) => { setTimeout(() => { const now = Date.now() now * 2 ? resolve(now) : reject(now) }, 1000) }) .then(now => { console.log('resolved', now) }) .catch(now => { console.log('rejected', now) }) .finally(now => { console.log('finally', now) })
const arr = [ { id: 1, value: 'A' }, { id: 1, value: 'B' }, { id: 1, value: 'C' }, { id: 1, value: 'D' }, { id: 1, value: 'E' }, { id: 1, value: 'F' }, { id: 1, value: 'G' }, { id: 1, value: 'H' }, { id: 1, value: 'I' }, { id: 1, value: 'J' }, { id: 4, value: 'K' }, { id: 1, value: 'L' }, { id: 1, value: 'B' }, { id: 1, value: 'C' }, { id: 1, value: 'D' }, { id: 1, value: 'E' }, { id: 1, value: 'F' }, { id: 1, value: 'G' }, { id: 1, value: 'H' }, { id: 1, value: 'I' }, { id: 1, value: 'J' }, { id: 4, value: 'K' }, { id: 1, value: 'L' }, ] // 舊版本的 ES 排序事後的結果可能不固定 console.log(arr.sort(function (a, b) { return a.id - b.id }))
簡化try...catch語法
try { throw new Error() } // catch後面能夠省略參數e catch{ }
function foo(data) { // ??表示當data爲undefined或者null的時候取100 let result = data ?? 100 // 簡化下面的寫法 let result1 = data === null || data === undefined ? 100 : data console.log(result) }
const list = [ { title: 'foo', author: { name: 'zce', email: 'w@zce.me' } }, { title: 'bar' } ] list.forEach(item => { // 若是author屬性不存在,那麼 item.author?.name 至關於item.author console.log(item.author?.name) }) // 還能夠用下面相似的寫法 // obj?.prop 獲取對象屬性值 // obj?.[expr] 獲取對象屬性值 // arr?.[index] 獲取數組指定下標值 // func?.(args) 方法調用