咱們編寫js代碼時常常遇到複雜邏輯判斷的狀況,一般你們能夠用if/else或者switch來實現多個條件判斷,但這樣會有個問題,隨着邏輯複雜度的增長,代碼中的if/else/switch會變得愈來愈臃腫,愈來愈看不懂,那麼如何更優雅的寫判斷邏輯,本文帶你試一下。es6
/** * @param {number} status 活動狀態:1 開團進行中 2 開團失敗 3 商品售罄 4 開團成功 5 系統取消 */ const statusChange = (status)=>{ if(status == 1){ sendLog('processing') jumpTo('IndexPage') }else if(status == 2 && status == 3){ sendLog('fail') jumpTo('FailPage') }else if(status == 4){ sendLog('success') jumpTo('SuccessPage') }else if(status == 5){ sendLog('cancel') jumpTo('CancelPage') }else { sendLog('other') jumpTo('Index') } } const sendLog = (log) => { document.write('sendLog:'+log+'<br/>'); } const jumpTo = (page) => { document.write('jumpTo:'+page+'<br/>'); document.write('----------------------<br/>') }
/** * @param {number} status 活動狀態:1 開團進行中 2 開團失敗 3 商品售罄 4 開團成功 5 系統取消 */ const statusChange = (status)=>{ switch (status){ case 1: sendLog('processing') jumpTo('IndexPage') break case 2: case 3: sendLog('fail') jumpTo('FailPage') break case 4: sendLog('success') jumpTo('SuccessPage') break case 5: sendLog('cancel') jumpTo('CancelPage') break default: sendLog('other') jumpTo('Index') break } }
const actions = { '1': ['processing','IndexPage'], '2': ['fail','FailPage'], '3': ['fail','FailPage'], '4': ['success','SuccessPage'], '5': ['cancel','CancelPage'], 'default': ['other','Index'], } /** * @param {number} status 活動狀態:1開團進行中 2開團失敗 3 商品售罄 4 開團成功 5 系統取消 */ const statusChange = (status)=>{ let action = actions[status] || actions['default'], logName = action[0], pageName = action[1] sendLog(logName) jumpTo(pageName) }
const actions = new Map([ [1, ['processing','IndexPage']], [2, ['fail','FailPage']], [3, ['fail','FailPage']], [4, ['success','SuccessPage']], [5, ['cancel','CancelPage']], ['default', ['other','Index']] ]) /** * @param {number} status 活動狀態:1 開團進行中 2 開團失敗 3 商品售罄 4 開團成功 5 系統取消 */ const statusChange = (status)=>{ let action = actions.get(status) || actions.get('default') sendLog(action[0]) jumpTo(action[1]) }
這樣寫用到了es6裏的Map對象,Map對象和Object對象有什麼區別呢?json
一個對象一般都有本身的原型,因此一個對象總有一個"prototype"鍵。
一個對象的鍵只能是字符串,但一個Map的鍵能夠是任意值。
你能夠經過size屬性很容易地獲得一個Map的鍵值對個數,而對象的鍵值對個數只能手動確認。數組
例子,if-else寫法緩存
/* * @param {string} identity 身份標識:guest客態 master主態 */ const statusChange = (status,identity)=>{ if(identity == 'guest'){ if(status == 1){ //do sth }else if(status == 2){ //do sth }else if(status == 3){ //do sth }else if(status == 4){ //do sth }else if(status == 5){ //do sth }else { //do sth } }else if(identity == 'master') { if(status == 1){ //do sth }else if(status == 2){ //do sth }else if(status == 3){ //do sth }else if(status == 4){ //do sth }else if(status == 5){ //do sth }else { //do sth } } }
將上述判斷用map方式實現數據結構
const actions = new Map([ ['guest_1', ()=>{ console.log('guest_1'); }], ['guest_2', ()=>{ console.log('guest_2'); }], ['guest_3', ()=>{ console.log('guest_3'); }], ['guest_4', ()=>{ console.log('guest_4'); }], ['guest_5', ()=>{ console.log('guest_5'); }], ['master_1', ()=>{ console.log('master_1'); }], ['master_2', ()=>{ console.log('master_2'); }], ['master_3', ()=>{ console.log('master_3'); }], ['master_4', ()=>{ console.log('master_4'); }], ['master_5', ()=>{ console.log('master_5'); }], ['default', ()=>{ console.log('default'); }], ]) const statusChange = (identity,status)=>{ let action = actions.get(`${identity}_${status}`) || actions.get('default') action.call(this) }
上述代碼核心邏輯是:把兩個條件拼接成字符串,並經過以條件拼接字符串做爲鍵,以處理函數做爲值的Map對象進行查找並執行,這種寫法在多元條件判斷時候尤爲好用。
固然上述代碼若是用Object對象來實現也是相似的:ide
const actions = { 'guest_1': ()=>{ console.log('guest_1'); }, 'guest_2': ()=>{ console.log('guest_2'); }, 'guest_3': ()=>{ console.log('guest_3'); }, 'guest_4': ()=>{ console.log('guest_4'); }, 'guest_5': ()=>{ console.log('guest_5'); }, 'master_1': ()=>{ console.log('master_1'); }, 'master_2': ()=>{ console.log('master_2'); }, 'master_3': ()=>{ console.log('master_3'); }, 'master_4': ()=>{ console.log('master_4'); }, 'master_5': ()=>{ console.log('master_5'); }, 'default': ()=>{ console.log('default'); }, } const statusChange = (identity,status)=>{ let action = actions[`${identity}_${status}`] || actions['default'] action.call(this) }
const actions = new Map([ [{identity:'guest',status:1},()=>{ console.log('guest_1'); }], [{identity:'guest',status:2},()=>{ console.log('guest_2'); }], [{identity:'guest',status:3},()=>{ console.log('guest_3'); }], [{identity:'guest',status:4},()=>{ console.log('guest_4'); }], ]) const statusChange = (identity,status)=>{ let action = [...actions].filter(([key,value])=>(key.identity == identity && key.status == status)) action.forEach(([key,value])=>value.call(this)) }
咱們如今再將難度升級一點點,假如guest狀況下,status1-4的處理邏輯都同樣怎麼辦,最差的狀況是這樣:函數
const actions = new Map([ [{identity:'guest',status:1},()=>{/* functionA */}], [{identity:'guest',status:2},()=>{/* functionA */}], [{identity:'guest',status:3},()=>{/* functionA */}], [{identity:'guest',status:4},()=>{/* functionA */}], [{identity:'guest',status:5},()=>{/* functionB */}], //... ])
好一點的寫法是將處理邏輯函數進行緩存:post
const actions = ()=>{ const functionA = ()=>{/*do sth*/} const functionB = ()=>{/*do sth*/} return new Map([ [{identity:'guest',status:1},functionA], [{identity:'guest',status:2},functionA], [{identity:'guest',status:3},functionA], [{identity:'guest',status:4},functionA], [{identity:'guest',status:5},functionB], //... ]) } const statusChange = (identity,status)=>{ let action = [...actions].filter(([key,value])=>(key.identity == identity && key.status == status)) action.forEach(([key,value])=>value.call(this)) }
假如判斷條件變得特別複雜,好比identity有3種狀態,status有10種狀態,那你須要定義30條處理邏輯,而每每這些邏輯裏面不少都是相同的,這彷佛也是筆者不想接受的,那能夠這樣實現:this
const actions = ()=>{ const functionA = ()=>{/*do sth*/} const functionB = ()=>{/*do sth*/} return new Map([ [/^guest_[1-4]$/,functionA], [/^guest_5$/,functionB], ]) } const statusChange = (identity,status)=>{ let action = [...actions()].filter(([key,value])=>(key.test(`${identity}_${status}`))) action.forEach(([key,value])=>value.call(this)) }
這裏Map的優點更加凸顯,能夠用正則類型做爲key了,這樣就有了無限可能,假如需求變成,凡是guest狀況都要發送一個日誌埋點,不一樣status狀況也須要單獨的邏輯處理,那咱們能夠這樣寫:prototype
onst actions = ()=>{ const functionA = ()=>{/*do sth*/} const functionB = ()=>{/*do sth*/} const functionC = ()=>{/*send log*/} return new Map([ [/^guest_[1-4]$/,functionA], [/^guest_5$/,functionB], [/^guest_.*$/,functionC], //... ]) } const statusChange = (identity,status)=>{ let action = [...actions].filter(([key,value])=>(key.test(`${identity}_${status}`))) action.forEach(([key,value])=>value.call(this)) }
也就是說利用數組循環的特性,符合正則條件的邏輯都會被執行,那就能夠同時執行公共邏輯和單獨邏輯,由於正則的存在,你能夠打開想象力解鎖更多的玩法,本文就不贅述了。
Map 數據結構相似於對象,也是鍵值對的集合,可是「鍵」的範圍不限於字符串,各類類型的值(包括對象)均可以看成鍵。也就是說,Object 結構提供了「字符串—值」的對應,Map 結構提供了「值—值」的對應,是一種更完善的 Hash 結構實現。若是你須要「鍵值對」的數據結構,Map 比 Object 更合適。
例子
const m = new Map(); const o = {p: 'Hello World'}; m.set(o, 'content') m.get(o) // "content" m.has(o) // true m.delete(o) // true m.has(o) // false
Map 任何具備 Iterator 接口、且每一個成員都是一個雙元素的數組的數據結構做爲參數,也就是說,數組,Set和Map均可以用來生成新的 Map。
注:遍歷器(Iterator) 接口的目的,就是爲全部數據結構,提供了一種統一的訪問機制,即for...of循環(詳見下文)。當使用for...of循環遍歷某種數據結構時,該循環會自動去尋找 Iterator 接口。
原生具有 Iterator 接口的數據結構以下:Array、Map、Set、String、TypedArray、函數的 arguments 對象、NodeList 對象
// 數組做爲參數 const map = new Map([ ['name', '張三'], ['title', 'Author'] ]); map.size // 2 map.has('name') // true map.get('name') // "張三" map.has('title') // true map.get('title') // "Author" // set數據結構做爲參數 const set = new Set([ ['foo', 1], ['bar', 2] ]); const m1 = new Map(set); m1.get('foo') // 1 // map做爲參數 const m2 = new Map([['baz', 3]]); const m3 = new Map(m2); m3.get('baz') // 3
若是對同一個鍵屢次賦值,後面的值將覆蓋前面的值。
const map = new Map(); map .set(1, 'aaa') .set(1, 'bbb'); map.get(1) // "bbb"
若是讀取一個未知的鍵,則返回undefined。
new Map().get('asfddfsasadf') // undefined
注意,只有對同一個對象的引用,Map 結構纔將其視爲同一個鍵。這一點要很是當心。
const map = new Map(); map.set(['a'], 555); map.get(['a']) // undefined
上面代碼的set和get方法,表面是針對同一個鍵,但實際上這是兩個值,內存地址是不同的,所以get方法沒法讀取該鍵,返回undefined。
同理,一樣的值的兩個實例,在 Map 結構中被視爲兩個鍵。
const map = new Map(); const k1 = ['a']; const k2 = ['a']; map .set(k1, 111) .set(k2, 222); map.get(k1) // 111 map.get(k2) // 222
上面代碼中,變量k1和k2的值是同樣的,可是它們在 Map 結構中被視爲兩個鍵。
由上可知,Map 的鍵其實是跟內存地址綁定的,只要內存地址不同,就視爲兩個鍵。這就解決了同名屬性碰撞(clash)的問題,咱們擴展別人的庫的時候,若是使用對象做爲鍵名,就不用擔憂本身的屬性與原做者的屬性同名。
若是 Map 的鍵是一個簡單類型的值(數字、字符串、布爾值),則只要兩個值嚴格相等,Map 將其視爲一個鍵
好比0和-0就是一個鍵,布爾值true和字符串true則是兩個不一樣的鍵。
另外,undefined和null也是兩個不一樣的鍵。雖然NaN不嚴格相等於自身,但 Map 將其視爲同一個鍵。
let map = new Map(); map.set(-0, 123); map.get(+0) // 123 map.set(true, 1); map.set('true', 2); map.get(true) // 1 map.set(undefined, 3); map.set(null, 4); map.get(undefined) // 3 map.set(NaN, 123); map.get(NaN) // 123
Map 結構的實例有如下屬性和操做方法。
(1)size 屬性
size屬性返回 Map 結構的成員總數。
const map = new Map(); map.set('foo', true); map.set('bar', false); map.size // 2
(2)set(key, value)
set方法設置鍵名key對應的鍵值爲value,而後返回整個 Map 結構。若是key已經有值,則鍵值會被更新,不然就新生成該鍵。
const m = new Map(); m.set('edition', 6) // 鍵是字符串 m.set(262, 'standard') // 鍵是數值 m.set(undefined, 'nah') // 鍵是 undefined
set方法返回的是當前的Map對象,所以能夠採用鏈式寫法。
let map = new Map() .set(1, 'a') .set(2, 'b') .set(3, 'c');
(3)get(key)
get方法讀取key對應的鍵值,若是找不到key,返回undefined。
const m = new Map(); const hello = function() {console.log('hello');}; m.set(hello, 'Hello ES6!') // 鍵是函數 m.get(hello) // Hello ES6!
(4)has(key)
has方法返回一個布爾值,表示某個鍵是否在當前 Map 對象之中。
const m = new Map(); m.set('edition', 6); m.set(262, 'standard'); m.set(undefined, 'nah'); m.has('edition') // true m.has('years') // false m.has(262) // true m.has(undefined) // true
(5)delete(key)
delete方法刪除某個鍵,返回true。若是刪除失敗,返回false。
const m = new Map(); m.set(undefined, 'nah'); m.has(undefined) // true m.delete(undefined) m.has(undefined) // false
(6)clear()
clear方法清除全部成員,沒有返回值。
let map = new Map(); map.set('foo', true); map.set('bar', false); map.size // 2 map.clear() map.size // 0
遍歷方法
Map 結構原生提供三個遍歷器生成函數和一個遍歷方法。
keys():返回鍵名的遍歷器。
values():返回鍵值的遍歷器。
entries():返回全部成員的遍歷器。
forEach():遍歷 Map 的全部成員。
須要特別注意的是,Map 的遍歷順序就是插入順序。
const map = new Map([ ['F', 'no'], ['T', 'yes'], ]); for (let key of map.keys()) { console.log(key); } // "F" // "T" for (let value of map.values()) { console.log(value); } // "no" // "yes" for (let item of map.entries()) { console.log(item[0], item[1]); } // "F" "no" // "T" "yes" // 或者 for (let [key, value] of map.entries()) { console.log(key, value); } // "F" "no" // "T" "yes" // 下面的方法等同於使用map.entries() for (let [key, value] of map) { console.log(key, value); } // "F" "no" // "T" "yes"
上面代碼最後的那個例子,表示 Map 結構的默認遍歷器接口(Symbol.iterator屬性),就是entries方法。
map[Symbol.iterator] === map.entries // true Map 結構轉爲數組結構,比較快速的方法是使用擴展運算符(...)。 const map = new Map([ [1, 'one'], [2, 'two'], [3, 'three'], ]); [...map.keys()] // [1, 2, 3] [...map.values()] // ['one', 'two', 'three'] [...map.entries()] // [[1,'one'], [2, 'two'], [3, 'three']] [...map] // [[1,'one'], [2, 'two'], [3, 'three']]
結合數組的map方法、filter方法,能夠實現 Map 的遍歷和過濾(Map 自己沒有map和filter方法)。
const map0 = new Map() .set(1, 'a') .set(2, 'b') .set(3, 'c'); const map1 = new Map( [...map0].filter(([k, v]) => k < 3) ); // 產生 Map 結構 {1 => 'a', 2 => 'b'} const map2 = new Map( [...map0].map(([k, v]) => [k * 2, '_' + v]) ); // 產生 Map 結構 {2 => '_a', 4 => '_b', 6 => '_c'}
此外,Map 還有一個forEach方法,與數組的forEach方法相似,也能夠實現遍歷。
map.forEach(function(value, key, map) { console.log("Key: %s, Value: %s", key, value); }); // %$表示字符串輸出 // Key: 1, Value: a // Key: 2, Value: b
forEach方法還能夠接受第二個參數,用來綁定this。
const reporter = { report: function(key, value) { console.log("Key: %s, Value: %s", key, value); } }; map.forEach(function(value, key, map) { this.report(key, value); }, reporter);
上面代碼中,forEach方法的回調函數的this,就指向reporter。
與其餘數據結構的互相轉換
(1)Map 轉爲數組
前面已經提過,Map 轉爲數組最方便的方法,就是使用擴展運算符(...)。
const myMap = new Map() .set(true, 7) .set({foo: 3}, ['abc']); [...myMap] // [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
(2)數組 轉爲 Map
將數組傳入 Map 構造函數,就能夠轉爲 Map。
new Map([ [true, 7], [{foo: 3}, ['abc']] ]) // Map { // true => 7, // Object {foo: 3} => ['abc'] // }
(3)Map 轉爲對象
若是全部 Map 的鍵都是字符串,它能夠無損地轉爲對象。
function strMapToObj(strMap) { let obj = Object.create(null); for (let [k,v] of strMap) { obj[k] = v; } return obj; } const myMap = new Map() .set('yes', true) .set('no', false); strMapToObj(myMap) // { yes: true, no: false }
若是有非字符串的鍵名,那麼這個鍵名會被轉成字符串,再做爲對象的鍵名。
(4)對象轉爲 Map
function objToStrMap(obj) { let strMap = new Map(); for (let k of Object.keys(obj)) { strMap.set(k, obj[k]); } return strMap; } objToStrMap({yes: true, no: false}) // Map {"yes" => true, "no" => false}
(5)Map 轉爲 JSON
Map 轉爲 JSON 要區分兩種狀況。一種狀況是,Map 的鍵名都是字符串,這時能夠選擇轉爲對象 JSON。
function strMapToJson(strMap) { return JSON.stringify(strMapToObj(strMap)); } let myMap = new Map().set('yes', true).set('no', false); strMapToJson(myMap) // '{"yes":true,"no":false}'
另外一種狀況是,Map 的鍵名有非字符串,這時能夠選擇轉爲數組 JSON。
function mapToArrayJson(map) { return JSON.stringify([...map]); } let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']); mapToArrayJson(myMap) // '[[true,7],[{"foo":3},["abc"]]]'
(6)JSON 轉爲 Map
JSON 轉爲 Map,正常狀況下,全部鍵名都是字符串。
function jsonToStrMap(jsonStr) { return objToStrMap(JSON.parse(jsonStr)); } jsonToStrMap('{"yes": true, "no": false}') // Map {'yes' => true, 'no' => false}
可是,有一種特殊狀況,整個 JSON 就是一個數組,且每一個數組成員自己,又是一個有兩個成員的數組。這時,它能夠一一對應地轉爲 Map。這每每是 Map 轉爲數組 JSON 的逆操做。
function jsonToMap(jsonStr) { return new Map(JSON.parse(jsonStr)); } jsonToMap('[[true,7],[{"foo":3},["abc"]]]') // Map {true => 7, Object {foo: 3} => ['abc']}
JavaScript 複雜判斷的更優雅寫法