本文將介紹ES2016以及以後版本中經常使用的知識點以及該知識點與以前版本用法的對比,更多的瞭解知識點背後的原理,從而更加深入的理解ES6+到底好在哪裏。前端
let,const都是用來聲明變量,用來替代老語法的var關鍵字,與var不一樣的是,它們會建立一個塊級做用域(通常一個花括號內是一個新的做用域)
場景1:經典面試題vue
// 使用var for (var i = 0; i < 5; i++) { setTimeout(function () { console.log(i); }); } // => 5 5 5 5 5 // 使用let for (let i = 0; i < 5; i++) { setTimeout(function () { console.log(i); }); } // => 0 1 2 3 4
知識點:node
setTimeout(function () { console.log('ab') }, 5 * 1000) //這裏改成0ms for (let i = 0; i <= 5000000; i++) { if (i === 5000000) { console.log(i) } }
說明:setTimeout裏的函數在setTimeout執行的時候,就開始計時,計時完成(這裏就是5s以後)才進入任務隊列,當主執行棧執行完就開始執行任務隊列。es6
場景2:const使用面試
const a; //Uncaught SyntaxError: Missing initializer in const declaration
const a=1; a=2; //Uncaught TypeError: Assignment to constant variable.
const obj = { a: 1 } obj.a = 2 obj = { b: 1 } //Uncaught TypeError: Assignment to constant variable.
let a = 1 console.log(window.a)//undefined var b=1; console.log(window.b)//1
解構賦值主要就是 數組的解構賦值和 對象的解構賦值,至於其餘數據類型的解構賦值都與這兩種有關係,對象的解構賦值的內部機制,是先找到 同名屬性,而後再賦給對應的變量。真正被賦值的是後者,而不是前者。
注意點:ajax
場景1:內部機制vuex
let { name: nameOne, test: [{ name: nameTwo }] } = { name: 'wx', test: [{ name: 'test' }] } //簡化版 let { name, test } = { name: 'wx', test: [{ name: 'test' }] }
說明:這裏等號左邊真正聲明的變量實際上是nameOne
和nameTwo
,而後根據它們所對應的位置找到等號右邊對應的值,從而進行賦值。編程
場景2:對象的方法json
// 例一 let { sin, cos } = Math; // 例二 const { log } = console; log('hello') // hello //vuex中action中的方法 //不使用對象解構: const actions = { getMainTaskList(ctx) { ctx.commit('setMainTask') }, } //使用對象解構: const actions = { getMainTaskList({ commit, state, dispatch }) { commit('setMainTask', { mainTaskData: JSON.parse(res) }) }, }
場景3:嵌套解構的對象segmentfault
let obj = { p: [ 'Hello', { y: 'World' } ] }; // 之前的寫法: let x = obj.p[0] let y = obj.p[1].y // 解構寫法: let { p: [x, { y }] } = obj; x // "Hello" y // "World"
場景4:繼承的原型屬性
const obj1 = {} const obj2 = { foo: 'bar' } Object.setPrototypeOf(obj1, obj2)//ES6推薦設置原型方法 const { foo } = obj1 foo // "bar"
// 老式寫法 const local = 'wx-18' const splitLocale = locale.split("-"); const language = splitLocale[0]; const country = splitLocale[1]; // 解構賦值寫法 const [language, country] = locale.split('-');
//字符串 const [a, b, c, d, e] = 'hello'; //數值和布爾值 let {toString: s} = 123; s === Number.prototype.toString // true console.log(s);//function toString() //[查看詳細的解釋](https://segmentfault.com/q/1010000005647566) //undefined和null 注意:因爲`undefined`和`null`沒法轉爲對象,因此對它們進行解構賦值,都會報錯。
擴展運算符主要是兩大類:數組和對象的擴展運算符。使用三個點點(...),後面跟含有 iterator接口的數據結構
場景1:複製數組
// ES5的複製數組-複製了指針(淺) const a1 = [1, 2]; const a2 = a1; a2[0] = 2; // ES5的複製數組-深拷貝 const a1 = [1, 2]; const a2 = a1.concat();//a1.slice() a2[0] = 2; // ES6複製數組-深拷貝 const a1 = [1,2] const a2 = [...a1] //const [...a2] = a1 a2[0] = 2;
場景2:合併數組
//ES5合併數組 const arr1 = ['a', 'b']; const arr2 = ['c']; const arr3 = arr1.concat(arr2) //["a", "b", "c"] // ES6合併數組 const arr1 = ['a', 'b']; const arr2 = ['c']; const arr3 = [...arr1,...arr2] //["a", "b", "c"]
場景3:與解構賦值結合使用
const [first, ...rest] = [1, 2, 3, 4, 5]; console.log(first) //1 console.log(rest) //[2, 3, 4, 5] //將擴展運算符用於數組賦值,只能放在參數的最後一位,不然會報錯 const [...rest,last] = [1, 2, 3, 4, 5];//Uncaught SyntaxError: Rest element must be last element
場景4:類對象轉數組
任何定義了遍歷器( Iterator)接口的對象,均可以用擴展運算符轉爲真正的數組。
// 例1: let nodeList = document.querySelectorAll('div');//類數組對象,而且部署了iterator接口 let arr = [...nodeList]; // 例2: let arrayLike = { '0': 'a', '1': 'b', length: 2 }; // Uncaught TypeError: object is not iterable (cannot read property Symbol(Symbol.iterator)) let arr = [...arrayLike]; //固然能夠經過`Array.from(arrayLike)`將它轉爲真正的數組 let arr = Array.from(arrayLike)
let obj1 = { a:1, b:1, } let obj2 = { ...obj1, c:1 } console.log(obj2);//{a: 1, b: 1, c: 1} //vuex中 computed: { ...mapGetters('raplaceBattery', { runningTask: 'runningMainTask' }) }, methods:{ ...mapActions('raplaceBattery', [ 'getMainTaskList' ]), }
數組和對象的遍歷方式有不少種,經常使用的for循環,for...in循環以及ES6提出的for...of循環,這些方式有各自的使用場景以及優缺點。
如下遍歷的數組都爲:let arr = [1,2,3,4]
方式1:for
//寫法比較麻煩,可是經常使用 for (let i = 0; i < arr.length; i++) { console.log('for循環:',arr[i]); } //使用break跳出循環,return會報錯 for (let i = 0; i < arr.length; i++) { if(i === 2) break console.log('for循環:',arr[i]);//1,2 }
方式2:forEach
arr.forEach(ele => { console.log('forEach循環:',ele); }); //缺點:沒法跳出循環,break直接會報錯,return只是跳出本次循環 arr.forEach(ele => { if(ele === 2) return; console.log('forEach循環:',ele);//1,2,4 });
方式3:map
返回新的數組,不改變原數組
let mapArr = arr.map((item,index,arr)=>{ if(index === 2) return;//只是跳出本次循環, return item+1 }) console.log(arr);//[1, 2, 3, 4] console.log(mapArr);//[2, 3, undefined, 5] //map有一個選填的返回函數裏面的this的參數 let mapArr = arr.map(function (e) { return e + this; // 此處的 this爲10 }, 10); console.log(mapArr);//[11, 12, 13, 14]
這裏爲何要用回調函數而不用箭頭函數?
答:由於箭頭函數裏的this指向和函數裏的this指向不一樣。請了解箭頭函數的this指向(後續)
方式4:filter,find,findIndex
filter:返回新數組,不改變原數組,主要用於過濾
find:返回 第一個判斷條件的 元素,不是數組
findIndex:返回 第一個判斷條件的 元素的索引
let filterArr = arr.filter((item,index,arr)=>{ return item > 1 }) console.log(filterArr);//[2, 3, 4] //find let findArr = arr.find((value,index,arr)=>{ return value > 2 }) console.log(findArr);//3 //findIndex let findIndexEle = arr.findIndex((val,i,arr)=>{ return val > 2 }) console.log(findIndexEle);// 2
方式5:every和some(返回布爾值)
every:只要有一個不符合,返回false,全符合,返回true
some:只要有一個符合,返回true,全不符合,返回false
let everyBool = arr.every((val, i, arr) => { return val < 3 }) console.log(everyBool);//false let someBool = arr.some((val,i,arr)=>{ return val > 2 }) console.log(someBool);//true
方式6:keys(),values(),entries()
ES6提供的新方法用於遍歷數組,都返回 遍歷器對象
let keysArr = arr.keys() console.log(keysArr);//Array Iterator {} //這種方式不能夠哦 arr.keys().forEach(ele => { console.log(ele);//Uncaught TypeError: arr.keys(...).forEach is not a function }); //for...of能夠 for (const index of arr.keys()) { console.log(index);//0,1,2,3 } for (const val of arr.values()) { console.log(val);//1,2,3,4 } for (const [index,ele] of arr.entries()) { console.log(index,ele); } //0 1 //1 2 //2 3 //3 4
方法7:for...of循環
遍歷全部部署了iterator接口的數據結構的方法,如:數組,Set,Map,類數組對象,如 arguments 對象、DOM NodeList 對象,Generator 對象,字符串
場景1:用於set和map結構
遍歷set結構返回的是 一個值,遍歷map結構返回的是一個數組
遍歷的順序是各個成員添加的順序
let setArr = new Set([1,2,3,3]) let mapObj = new Map().set('b',1).set('a',2) for (const item of setArr) { console.log(item);//1,2,3 } for (const item of mapObj) { console.log(item);//item是數組,別搞錯了 } //["b", 1] //["a", 2] for (const [item, index] of mapObj) { console.log(item, index); } //b 1 //a 2
場景2:類數組對象(必須部署了Iterator接口)
//字符串 const str = 'wx' for (const item of str) { console.log(item);//w,x } //DOM NodeList對象 let ps = document.querySelectorAll('div') for (const p of ps) { console.log(p); } //arguments對象 function writeArgs(){ for (const item of arguments) { console.log(item); } } writeArgs('1',2)//1,2 //rest的方式 function writeArgs(...rest){ for (const item of rest) { console.log(item); } } writeArgs('1',2)//1,2 //沒有部署的類數組對象,經過Array.from轉爲數組 let likeArr = {length:2,0:'1',1:'2'} for (const item of Array.from(likeArr)) { console.log(item);//1,2 }
場景3:普通對象
普通對象不能使用for...of遍歷,由於沒有部署Iterator接口,通常使用for...in進行鍵名遍歷
let obj = { a:1, b:2, c:3 } for (const item of obj) { console.log(item);//Uncaught TypeError: obj is not iterable //你非要使用for...of呢?你調皮了 // 方式1: for (const item of Object.keys(obj)) { console.log(item);//a,b,c } // 方式2:使用Generator函數進行包裝 function* packObj(obj){ for (const key of Object.keys(obj)) { yield [key,obj[key]] } } for (const [key,value] of packObj(obj)) { console.log(key,value); } } //a 1 //b 2 //c 3
如下遍歷的對象都爲:
let obj = { a:1, b:2, c:3, [Symbol('mySymbol')]: 4, } Object.setPrototypeOf(obj,{name:'wx'}) ////給原型加了name屬性 //Object.getOwnPropertyDescriptor(obj,'enumTemp').enumerable = false //這種方式無效 Object.defineProperty(obj, 'enumTemp', { value: 5, enumerable: false })//添加一個不可枚舉屬性enumTemp
1. for...in
特色:遍歷對象 自身的和 繼承的可枚舉屬性(不含Symbol屬性), 不建議使用
for (const key in obj) { console.log(key);//a,b,c,name }
2. Object.keys(obj),Object.entries(obj)
特色:只包括對象自身的可枚舉屬性--(不含Symbol屬性,不含繼承的), 推薦使用
Object.keys(obj).forEach(item=>{ console.log(item);//a,b,c }) for (const [item,index] of Object.entries(obj)) { console.log(item,index); } //a 1 //b 2 //c 3
3. Object.getOwnPropertyNames(obj)
特色:包括對象自身全部的屬性(包括不可枚舉的)--(不含Symbol屬性,不含繼承的)
console.log(Object.getOwnPropertyNames(obj)); //["a", "b", "c", "enumTemp"]
4. Object.getOwnPropertySymbols(obj)
特色:返回數組包括自身全部symbol屬性的鍵名
console.log(Object.getOwnPropertySymbols(obj));//[Symbol(mySymbol)] let sym = Object.getOwnPropertySymbols(obj).map(item=>obj[item]) console.log(sym);//[4]
它們都是爲了操做 對象而設計的API,而且主要用於 監聽數據變化以及 響應式能力
1. Object.defineProperty
具體更詳細的瞭解請查看:Object.defineProperty
//基本形式 let obj = {}; Object.defineProperty(obj, "num", { value: 1, writable: true, enumerable: true, configurable: true }); console.log(obj.num) //1 //get,set形式 let obj = {}; let value = 2; Object.defineProperty(obj, 'num', { get: function () { console.log('執行了 get 操做') return value }, set: function (newVal) { console.log('執行了 set操做'); value = newVal }, enumerable: true, configurable: true }) console.log(obj.num); // 執行了 get 操做 //2
2. Proxy
Tips:vue3.0使用Proxy代替Object.defineProperty實現數據響應式。
它是一個 「攔截器」,訪問一個對象以前,都必須先通過它,對比defineProperty只有get,set兩種處理攔截操做,而Proxy有13種,極大加強了處理能力,而且也消除了Object.defineProperty存在的一些侷限問題
它爲什麼優秀呢?
//一、基礎用法 let proxy = new Proxy({}, { get(target, key, receiver) { console.log('get 操做'); console.log(receiver);//Proxy {name: "wx"} return target[key] }, set(target, key, val, receiver) { console.log('set 操做'); console.log(receiver);//Proxy {} target[key] = val } }) proxy.name = 'wx' //set 操做 console.log(proxy.name); //get 操做 //wx //二、has用法,攔截propKey in proxy的操做,返回一個布爾值 let handler = { has(target, key) { if (key[0] === '_') { return false } return key in target } } let target = { prop: 'foo', _prop: 'foo' }; let proxy = new Proxy(target, handler) console.log('_prop' in proxy)//false //三、deleteProperty方法攔截delete操做,返回布爾值 let target = { prop: 'foo', _prop: 'foo' }; let proxy = new Proxy(target, { deleteProperty(target, key) { if (key[0] === '_') { return false } delete target[key] return true } }) console.log(delete proxy['_prop']) //false console.log(target) //{prop: "foo", _prop: "foo"}//這裏並無刪除‘_prop’屬性 //四、apply方法攔截函數的調用,call和apply操做,它有三個參數,分別是:目標對象,目標對象的上下文(this)和目標對象的參數數組 let target = () => 'target function' let proxy = new Proxy(target, { apply(target, ctx, args) { return 'apply proxy' } }) console.log(proxy()) //apply proxy
3. Reflect
它和Proxy同樣,也是用來處理對象,常常與proxy搭配使用,用來保證這些方法 原生行爲的正常執行,因此它是服務於proxy的。
//一、基本用法 let obj = { 'a': 1, 'b': 2 } let proxy = new Proxy(obj, { get(target, key) { console.log('get', target, key); return Reflect.get(target, key); }, deleteProperty(target, key) { console.log('delete' + key); return Reflect.deleteProperty(target, key); }, has(target, key) { console.log('has' + key); return Reflect.has(target, key); } }); console.log(proxy.b); //get {a: 1, b: 2} b //2 //原生行爲 //二、has用法 let myObject = { foo: 1, }; // 舊寫法 console.log('foo' in myObject);// true // 新寫法 console.log(Reflect.has(myObject, 'foo'));// true //三、deleteProperty用法 let myObject = { foo: 1, b:2 }; // 舊寫法 console.log(delete myObject['foo'])//true console.log(myObject);//{b: 2} // 新寫法 console.log(Reflect.deleteProperty(myObject,'b'));//true console.log(myObject);//{}
Promise是解決異步編程提出的新的解決方案,它相對於以前的 回調函數方案在不少方面作了改進,更加的合理和強大。
理解Promise以前建議先了解 異步, 事件循環以及 回調函數
回調函數是以前用來解決異步編程經常使用的方式,大概的流程是:前端發出一個請求,進入瀏覽器的http請求線程,等收到響應後,將該回調函數推入異步隊列,等處理完主線程的任務後會逐個讀取異步隊列中的回調並開始執行。
一、第三方庫信任問題及錯誤處理
ajax('http://localhost:3000',()=>{ console.log('執行回調'); })
上面這個例子是最多見的一個回調例子,可是它有什麼問題呢?試想一下,若是這個請求在網絡環境很差的狀況下,進行了超時重試的操做,那麼這個回調函數就會執行屢次,另一種狀況,要是這個請求失敗了,那麼它失敗的錯誤信息怎麼拿到,這些都是回調函數可能存在的問題,這明顯不是咱們但願看到的結果。
二、回調地獄
//引用《你不知道的JavaScript(中卷)》 listen("click", function handler(evt) { setTimeout(function request() { ajax("http://some.url.1", function response(text) { if (text == "hello") { handler(); } else if (text == "world") { request(); } }); }, 500); });
總結:
它是一個構造函數,用來生成promise實例對象,它有兩個參數(是函數),通常推薦命名爲:resolve和reject
特色:
(1)、它有三種狀態:pending
(進行中)、fulfilled
(已成功)、rejected
(已失敗)
(2)、它的狀態改變只有兩種可能:pending
到fulfilled
或者pending
到rejected
(3)、promise新建後會當即執行
// 基礎用法 let p = new Promise((resolve, reject) => { if (true) { return resolve('1') //resolve(new Promise()) // 後面寫的代碼沒用了 } else { reject('error') } }) p.then((val) => { console.log('fulfilled:', val); }).catch(e => { //推薦使用catch進行錯誤處理,能夠檢測到promise內部發生的錯誤 console.log('catch', e); }).finally(()=>{...})
//對比兩種方式 // bad request(url, function (err, res, body) { if (err) handleError(err); fs.writeFile('1.txt', body, function (err) { request(url2, function (err, res, body) { if (err) handleError(err) }) }) }); // good request(url) .then(function (result) { return writeFileAsynv('1.txt', result) }) .then(function (result) { return request(url2) }) .catch(function (e) { handleError(e) });
1. Promise.all([...])
將多個Promise(p1,p2,p3)實例包裝成一個新的promise實例,這個新實例的狀態由p1,p2,p3共同決定。
什麼時候用它?當多個任務之間沒有必然聯繫,它們的順序並不重要,可是必須都要完成才能執行後面的操做
const p1 = request("http://some.url.1/"); const p2 = request("http://some.url.2/"); Promise.all([p1, p2]).then(([p1Result, p2Result]) => { // 這裏p1和p2所有響應以後 return request("http://some.url.3/") }).then((result) => { console.log(result); }).catch(e => { console.log(e); })
2. Promise.race([...])
它與Promise.all([...])相似,只是它只關心誰先第一個完成Promise協議,一旦有一個完成,它就完成。即各個promise之間(p1,p2,p3...)存在「競爭」關係
const p = Promise.race([ request('/fetch-request'), new Promise((resolve, reject) => { setTimeout(() => { reject(new Error('request timeout')) }), 5 * 1000 }) ]); p.then(console.log) .catch(console.error);
上面的代碼中,5秒以內沒法從request
中返回結果,p的狀態就會變爲reject
,從而執行catch
3. Promise.resolve()和Promise.reject()
有時候須要將現有的對象 轉化爲promise對象進行處理,如deferred
或者thenable
對象
注意:當即resolve()的promise對象,是在「 本輪事件循環」結束時執行,而不是等到下一輪才執行
//deferred轉爲promise const deferToPromise = Promise.resolve($.ajax('/a.json')) //thenable轉promise let thenable = { then(resolve, reject) { resolve(11) } }; let p1 = Promise.resolve(thenable); p1.then((val) => { console.log(val);//11 }); //純對象轉promise let obj = { a:1, b:2 } const p = Promise.resolve(obj) p.then((v)=>{ console.log(v);//{a: 1, b: 2} }).finally(()=>{ console.log('aaa'); })
它被稱爲異步編程的終極解決方案,它進一步 優化了promise的寫法,用 同步的方式書寫異步代碼,而且可以更優雅的實現異步代碼的順序執行。
它返回一個promise對象。
任何一個await
語句後面的 Promise 對象變爲reject
狀態,那麼整個async
函數都會中斷執行嗎?。
// 基本用法 async function request(){ const data = await requestApi() // const data1 = await requestApiOther() return data } request().then((val)=>{ console.log(val); }).catch((e)=>{ console.log(e); })
重點關注:
async function f() { await Promise.reject('出錯了'); await Promise.resolve('hello world'); }
上面的這種狀況,後面的await
不會執行,可是你就想讓後面的也執行呢?
async function f() { // 方式:1: try { await Promise.reject('出錯了'); } catch (e) { console.log(e) } // 方式2: // await Promise.reject('出錯了').catch(e => { // console.log(e); // }) return await Promise.resolve('hello world'); } f().then((v) => { console.log(v); }).catch(e => { console.log(e); })
推薦使用:
一、將await
使用try...catch
包裹
async function f(){ try { await someRequest() } catch (error) { console.log(error); } }
二、多個await
後面的異步操做若是不存在依賴關係,最好同時觸發
async function f(){ try { await Promise.all([getFoo(), getBar()]); } catch (error) { } } f().then(([foo,bar])=>{ // handle }).catch(e=>{ console.log(e); })
三、多個await
後面的異步操做若是存在依賴關係,請參照如下寫法
function fetchData() { return Promise.resolve('1') } function fetchMoreData(val) { return new Promise((resolve, reject) => { resolve(val * 2) }) } function fetchMoreData2(val) { return new Promise((resolve, reject) => { resolve(val * 3) }) } // good function fetch() { return fetchData() .then(val1 => { return fetchMoreData(val1) }) .then(val2 => { return fetchMoreData2(val2) }) } fetch().then(val => { console.log(val) //6 }) // better async function fetch() { const value1 = await fetchData() const value2 = await fetchMoreData(value1) return fetchMoreData2(value2) };
建議在項目中多使用ES7的async...await
寫法,它簡潔,優雅,可維護性高。
參考資料連接: