JS使用鏈?zhǔn)綄傩员磉_(dá)式取值和賦值的實(shí)現(xiàn)方法
什么是鏈?zhǔn)綄傩员磉_(dá)式?
例如,有這樣一個(gè)對(duì)象:
const obj = {a: {b: {c: 3} } };
我們要取到其中c的值,正常情況下我們有很多種方法可以取到值,例如直接點(diǎn)或者使用解構(gòu)等:
const c = obj.a.b.c; const {a: {b: {c} } } = obj;
但是有一些情況不是我們主動(dòng)去取值的,而是由某個(gè)方法或某個(gè)類(lèi)在其執(zhí)行時(shí)去取值。舉個(gè)例子:Vue2
的響應(yīng)式原理是劫持對(duì)象的 getter 和 setter ,在 getter 中收集依賴(lài),在 setter 中觸發(fā)依賴(lài),那如何確定哪些屬性是被依賴(lài)的呢,Watcher
這個(gè)類(lèi)就接受表達(dá)式為參數(shù),它來(lái)獲取屬性值,屬性被訪問(wèn)后觸發(fā)getter,該屬性的依賴(lài)就被收集了,在被更新時(shí)就會(huì)執(zhí)行回調(diào)。
const viewModel = {a: {n: { m } } }; new Watcher(viewModel, "a.n.m", (newVal, oldVal) => { console.log("新的值--》", newVal); console.log("老的值--》", oldVa1l); });
那么上例中的 a.n.m
就是鏈?zhǔn)綄傩员磉_(dá)式了,通過(guò)鏈?zhǔn)綄傩员磉_(dá)式來(lái)告訴方法要獲取對(duì)象的哪個(gè)屬性。
還有微信小程序的observer
監(jiān)聽(tīng)器,setData
方法等都應(yīng)用到了鏈?zhǔn)綄傩员磉_(dá)式,道理都一樣,通過(guò)鏈?zhǔn)綄傩员磉_(dá)式去取值或賦值。
鏈?zhǔn)饺≈?/h2>
先看一下要支持的數(shù)據(jù)類(lèi)型:
// 對(duì)象,使用 . 訪問(wèn) obj.a.b.c // 數(shù)組,使用下標(biāo)訪問(wèn),要支持連續(xù)訪問(wèn) arr[0][1][2] // 對(duì)象數(shù)組嵌套混合 obj.a[0].b
先把鏈?zhǔn)綄傩员磉_(dá)式(以下簡(jiǎn)稱(chēng)路徑)解析為字段數(shù)組,便于后續(xù)操作,例如:把 路徑obj.arr[0].a.b
解析成 ['obj', 'arr', 0, 'a', 'b']
。
// 解析路徑為字段數(shù)組 function parsePath(path: string) { const segments: string[] = path.split('.'); // 分割字段片段 let fileds: Array<number | string> = []; // 保存字段名 if (path.includes('[')) { // 如果包含數(shù)組下標(biāo),收集數(shù)組索引 類(lèi)似arr[0]這樣的格式 for (const segment of segments) { if (/^(\w+)(\[(\w+)\])+/.test(segment)) { // 匹配 類(lèi)似 arr[0][1] 這種格式 const arrIndexs: number[] = []; for (const item of segment.matchAll(/(\w*)\[(\w+)\]/g)) { if (item[1]) fileds.push(item[1]); // 第一個(gè)匹配的括號(hào),即數(shù)組字段名 arrIndexs.push(~~item[2]); // 第二個(gè)匹配的括號(hào),即數(shù)組索引 } fileds.push(...arrIndexs); } else { // 如果是被'.'分割完的字段直接push fileds.push(segment); } } } else { // 無(wú)數(shù)組值時(shí)無(wú)需遍歷,提高性能 fileds = segments; } return fileds; }
注意一個(gè)細(xì)節(jié),數(shù)組合并的方法有性能差異,在寫(xiě)工具、框架等對(duì)性能要求高的情況下,更推薦使用push
, Array.prototype.push.apply(array1, array2)
和array1.push(…array2)
都行,這倆差距很微小。
數(shù)組元素量級(jí)大而合并次數(shù)少時(shí),性能對(duì)比:
concat() > push() > […array1,…array2]數(shù)組元素少但合并次數(shù)多時(shí),性能對(duì)比:
push() > concat() > […array1,…array2]push()
方法適合10萬(wàn)級(jí)以下元素的數(shù)組合并,次數(shù)越多越有優(yōu)勢(shì),但push()怕數(shù)組元素多,超過(guò)12萬(wàn)左右就會(huì)報(bào)錯(cuò),導(dǎo)致無(wú)法合并數(shù)組concat()
方法適合數(shù)組元素量級(jí)大,但是合并次數(shù)少的情況,當(dāng)數(shù)組合并頻繁的時(shí)候性能表現(xiàn)略差;[…array1, …array2]
方法無(wú)論是大量級(jí)數(shù)組合并還是數(shù)組頻繁合并,都不占優(yōu)勢(shì),單從性能方面來(lái)說(shuō),是最差的一種,莫非是因?yàn)樗獎(jiǎng)?chuàng)建數(shù)組產(chǎn)生了較大開(kāi)銷(xiāo)。
綜合對(duì)比來(lái)說(shuō): push() > concat() > […array1,…array2]
一般情況下,用push()方法合并數(shù)組是最快的方法,concat()方法可以支撐大量級(jí)數(shù)組合并,而[…array1,…array2]擴(kuò)展運(yùn)算符可讀性較好,不考慮性能時(shí)可以用;
插播一下 String.prototype.matchAll()
方法,大家可能有不理解的地方:
matchAll()
方法返回一個(gè)包含所有匹配正則表達(dá)式的結(jié)果及分組捕獲組的迭代器。
const arr = [...'arr[0][1]'.matchAll(/(\w*)\[(\w+)\]/g)]; // arr[0]:??["arr[0]",?"arr",?"0",?index:?0,?input:?"arr[0][1]",?groups:?undefined] // arr[1]:??["[1]",?"",?"1",?index:?6,?input:?"arr[0][1]",?groups:?undefined]
把路徑解析為字段數(shù)組之后,就可以用 reduce
方法來(lái)鏈?zhǔn)饺≈盗耍?/p>
// 鏈?zhǔn)饺≈? function getValByPath(target: object, path: string): any { if (!(/[\\.\\[]/.test(path))) return target[path]; // 如果沒(méi)有 . 或 [ 符號(hào)說(shuō)明非鏈?zhǔn)?,直接返回屬性? const fileds = getPathFileds(path); const val = fileds.reduce((pre, cur) => pre?.[cur], target); // 取不到的時(shí)候返回undefined return val; }
上面方法沒(méi)有做類(lèi)型檢查以及默認(rèn)值等,根據(jù)你自己的需要來(lái)就行。
到這里,我們就可以用getValByPath
方法根據(jù)鏈?zhǔn)綄傩员磉_(dá)式來(lái)獲取對(duì)象的屬性值了。
鏈?zhǔn)劫x值
賦值相對(duì)來(lái)說(shuō)更麻煩一些
// 鏈?zhǔn)劫x值 function updateValByPath(target: object, path: string, value): void { if (!(/[\\.\\[]/.test(path))) return target[path] = value; // 如果沒(méi)有 . 或 [ 符號(hào)說(shuō)明非鏈?zhǔn)?,直接賦值 const fileds = getPathFileds(path); // cosnt obj = {a: {b: {c: 6}}}; // 獲取值引用 ,例如更新obj對(duì)象的c值,需要獲取{c: 6}對(duì)象的引用,即obj.a.d = {c: 6},拿到引用后 ref.c = 8,就 {c: 6} 更新成 {c: 8} 了 const ref = fileds.slice(0, -1).reduce((pre, cur) => pre[cur], target); // 只遍歷到倒數(shù)第二個(gè)字段,因?yàn)檫@個(gè)字段就是被修改對(duì)象的引用 if (ref) return ref[`${fileds.at(-1)}`] = value; // 拿到引用后,更新最后一個(gè)字段 // 如果引用對(duì)象不存在,提醒開(kāi)發(fā)者不要更新不存在的屬性 console.warn(`updated property "${path}" is not registered in data, you will not be able to get the value synchronously through "this.data"`); }
大家已經(jīng)發(fā)現(xiàn)了,上面的方法只能更新引用存在的情況,即被更新數(shù)據(jù)的父級(jí)對(duì)象存在,如果要支持更復(fù)雜的情況,需要在被更新屬性沒(méi)有父級(jí)屬性時(shí)幫它創(chuàng)建父級(jí)對(duì)象,可能是一個(gè)對(duì)象類(lèi)型也可能是一個(gè)數(shù)組類(lèi)型,這將額外消耗很多內(nèi)存和性能,而且本身隨意操作一個(gè)對(duì)象沒(méi)有的屬性就不符合嚴(yán)謹(jǐn)?shù)拇a規(guī)范,不利于維護(hù),大家感興趣的話可以自己在此基礎(chǔ)上擴(kuò)展,支持設(shè)置不存在的屬性。
以上就是詳解JS如何使用鏈?zhǔn)綄傩员磉_(dá)式取值和賦值的詳細(xì)內(nèi)容,更多關(guān)于JS鏈?zhǔn)饺≈岛唾x值的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JS實(shí)現(xiàn)自定義狀態(tài)欄動(dòng)畫(huà)文字效果示例
這篇文章主要介紹了JS實(shí)現(xiàn)自定義狀態(tài)欄動(dòng)畫(huà)文字效果,涉及javascript結(jié)合時(shí)間函數(shù)動(dòng)態(tài)設(shè)置IE狀態(tài)欄文字顯示效果相關(guān)操作技巧,需要的朋友可以參考下2017-10-10拿捏javascript對(duì)象增刪改查應(yīng)用及示例
“撩過(guò)”c++的對(duì)象,“拿捏”了python的對(duì)象,那么今天我們看看javascript中的對(duì)象到底是什么,看能不能一次性拿下,不行的話就多來(lái)幾次,想做“海王”就多物色幾門(mén)語(yǔ)言的對(duì)象,多new幾個(gè),最終你會(huì)發(fā)現(xiàn)都差不多2022-03-03Array.prototype.concat不是通用方法反駁[譯]
ECMAScript 5.1規(guī)范中指出,數(shù)組方法concat是通用的(generic).本文反駁了這一結(jié)論,因?yàn)閷?shí)際上并不是這樣的2012-09-09在javascript中隨機(jī)數(shù) math random如何生成指定范圍數(shù)值的隨機(jī)數(shù)
本篇文章給大家介紹在javascript中隨機(jī)數(shù)math random如何生成指定范圍數(shù)值的隨機(jī)數(shù),由于math.random生成了一個(gè)偽隨機(jī)數(shù),之后還要經(jīng)過(guò)我們的后期處理。對(duì)隨機(jī)數(shù)math random感興趣的朋友一起了解了解吧2015-10-10ie下$.getJSON出現(xiàn)問(wèn)題的解決方法
ie下$.getJSON出現(xiàn)問(wèn)題是常有的事,下面為大家介紹下具體該如何解決,需要的朋友可以參考下2014-02-02淺談Javascript如何實(shí)現(xiàn)勻速運(yùn)動(dòng)
這篇文章主要介紹了淺談Javascript如何實(shí)現(xiàn)勻速運(yùn)動(dòng)的方法及相關(guān)代碼,需要的朋友可以參考下2014-12-12使用layui實(shí)現(xiàn)的左側(cè)菜單欄以及動(dòng)態(tài)操作tab項(xiàng)方法
今天小編就為大家分享一篇使用layui實(shí)現(xiàn)的左側(cè)菜單欄以及動(dòng)態(tài)操作tab項(xiàng)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-09-09js獲取當(dāng)前頁(yè)的URL與window.location.href簡(jiǎn)單方法
下面小編就為大家?guī)?lái)一篇js獲取當(dāng)前頁(yè)的URL與window.location.href簡(jiǎn)單方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-02-02