摘要:用做緩存的高階函數用高階函數的好處是無需暴露不同要求的緩存對象在外面,形成一個閉包。函數內部調用函數得到操作后的值,并緩存在對象中,如果再對同一個值進行操作時,則直接從緩存中取,無需再調用函數計算。
以下摘取的函數,在 shared 目錄下公用的工具方法。文件在 util.js 中,githu地址。
提取了一些常用通用的函數進行剖析,主要包含以下內容:
1. 創建一個被凍結的空對象創建一個被凍結的空對象
判斷是否是 undefined 或 null
判斷是否不是 undefined 和 null
判斷是否是原始類型
判斷是否是對象類型
判斷有效的數組下標
判斷是否是一個 Promise 對象
刪除數組中指定元素
用做緩存的高階函數
遞歸判斷一個對象是否和另個一個對象完全相同
函數只執行一次
自定義 bind 函數
export const emptyObject = Object.freeze({})
一旦創建不能給這個對象添加任何屬性。
2. 判斷是否是 undefined 或 nullfunction isUndef (v) { return v === undefined || v === null }
在源碼中很多地方會判斷一個值是否被定義,所以這里直接抽象成一個公共函數。
傳入任意值,返回是一個布爾值。
function isDef (v) { return v !== undefined && v !== null }
當傳入的值,既不是 undefined 也不是 null 返回true。
4. 判斷是否是原始類型function isPrimitive (value) { return ( typeof value === "string" || typeof value === "number" || typeof value === "symbol" || typeof value === "boolean" ) }
在js中提供了兩大類數據類型:
原始類型(基礎類型):String、Number、Boolean、Null、Undefined、Symbol
對象類型:Object、Array、Function
5. 判斷是否是對象類型function isObject (obj: mixed) { return obj !== null && typeof obj === "object" }
傳入的值排除掉 null,因為在js中 null 使用運算符 typeof 得到的值是 object,這是一個 bug。因為歷史原因放棄修復了。具體可以參考這里查看
6. 判斷有效的數組下標function isValidArrayIndex (val) { const n = parseFloat(String(val)); // 轉成數字 // 下標大于等于0,并且不是小數,并且是有限的數 return n >= 0 && Math.floor(n) === n && isFinite(val) }
可以傳入任意值,先調用 String 轉成字符串,目的是防止傳入的值為 Symbol 類型,那樣直接調用 parseFloat 會報錯,例如:
let test = Symbol("test"); console.log(parseFloat(test)) 控制臺捕獲錯誤:Uncaught TypeError: Cannot convert a Symbol value to a string
原因是在調用 parseFloat 時,內部會調用內置的 ToString 方法,可以參考這里。而內置的 ToString 方法在遇到 Symbol 類型的值時,會拋出 TypeError 錯誤,可以參考這里。
跟使用一些隱式轉換遇到的問題一樣,例如使用 + 號:
let test = "" + Symbol("text"); 控制臺捕獲錯誤:Uncaught TypeError: Cannot convert a Symbol value to a string
都是因為內部會調用內置的 ToString 方法造成的。
而如果手動調用 toString 方法或者調用 String,轉換為字符串,則不會報錯:
let test = Symbol("test"); console.log(test.toString()); // "Symbol(test)" console.log(String(test)) // "Symbol(test)"
接下來判斷 n >= 0 ,數組的下標不能小于0,這樣就會排除掉小于0的數,以及 NaN
并且 Math.floor(n) === n 一個數向下取整并且還等于自己,那只能是正整數,排除掉小數,因為數組的下標不能是小數。
并且用 isFinite 來判定一個數字是否是有限數
console.log(isFinite(Infinity)); // false console.log(isFinite(-Infinity)); // false console.log(isFinite(123)); // true7. 判斷是否是一個 Promise 對象
function isPromise (val) { return ( isDef(val) && typeof val.then === "function" && typeof val.catch === "function" ) }
當一個對象存在 then 方法,并且也存在 catch 方法,可以判定為 Promise 對象。
8. 刪除數組中指定元素這個方法有效的避免了進行刪除數組某一項時,都要進行查找位置再刪除的重復工作。
function remove (arr, item){ if (arr.length) { const index = arr.indexOf(item) if (index > -1) { return arr.splice(index, 1) } } }
先判斷數組長度,如果數組是空的,則沒必要進行刪除操作
用 indexOf 方法查找到元素在數組中的位置,如果找到返回元素所在的位置下標,如果不存在,則返回-1
index>-1 代表存在數組中,則調用 splice 進行刪除,并返回刪除的元素組成的數組,也就是 splice 的返回值。
9. 用做緩存的高階函數用高階函數的好處是無需暴露不同要求的緩存對象在外面,形成一個閉包。下面這個函數的技巧,應用在工作中,可以提高代碼運行的效率。
function cached(fn) { // 創建一個緩存對象 const cache = Object.create(null) return (function cachedFn (str) { // 先從緩存對象中找,要操作的值,是否已經有了操作結果 const hit = cache[str] // 如果有,則直接返回;沒有,則調用函數對值進行操作,并把操作結果存在緩存對象中 return hit || (cache[str] = fn(str)) }) }
調用 cached 時會傳入一個 fn 函數,這個函數對某些值進行操作,操作之后會產生返回值
在 cached 函數先定義一個沒有原型的對象,會比用 {} 高效,因為不需要繼承一大堆 Object.prototype 上的屬性。
執行完 cached 會返回一個函數 cachedFn,將來接收需要操作的值。函數 cachedFn 內部調用 fn 函數得到操作后的值,并緩存在對象 cache 中,如果再對同一個值進行操作時,則直接從緩存中取,無需再調用函數計算。
例如以下運用,函數的作用是把字符串的首字母大寫。
const capitalize = cached((str) => { return str.charAt(0).toUpperCase() + str.slice(1) })
先調用 cached 傳入一個函數,這個函數是對字符串進行首字母大寫的操作,并返回首字母大寫的字符串結果,可以說創建了一個計算函數。
cached 的返回值是函數,也就是上面的 cachedFn 函數。
這時我們就可以調用 capitalize 對字符串進行首字母大寫了。
capitalize("test"); // "Test" capitalize("test"); // "Test" capitalize("test"); // "Test"
第一次調用 capitalize 函數,先從緩存對象中取值,沒有,則調用計算函數進行計算結果返回,同時存入緩存對象中。這時的緩存對象為:
{test: "Test"}
再多次調用 capitalize 時,從緩存對象中取值,命中,直接返回,無需再進行計算操作。
10. 遞歸判斷一個對象是否和另個一個對象完全相同判斷兩個對象是否相同,主要是判斷兩個對象包含的值都是一樣的,如果包含的值依然是個對象,則繼續遞歸調用判斷是否相同。
function isObject (obj){ return obj !== null && typeof obj === "object" } function looseEqual (a, b) { // 如果是同一個對象,則相同 if (a === b) return true // 判斷是否是對象 const isObjectA = isObject(a) const isObjectB = isObject(b) // 兩者都是對象 if (isObjectA && isObjectB) { try { // 判斷是否是數組 const isArrayA = Array.isArray(a) const isArrayB = Array.isArray(b) // 兩者都是數組 if (isArrayA && isArrayB) { // 長度要一樣,同時每一項都要相同,遞歸調用 return a.length === b.length && a.every((e, i) => { return looseEqual(e, b[i]) }) } else if (a instanceof Date && b instanceof Date) { // 如果都是時間對象,則需要保證時間戳相同 return a.getTime() === b.getTime() } else if (!isArrayA && !isArrayB) { // 兩者都不是數組,則為對象 // 拿到兩者的key值,存入數組 const keysA = Object.keys(a) const keysB = Object.keys(b) // 屬性的個數要一樣,遞歸的判斷每一個值是否相同 return keysA.length === keysB.length && keysA.every(key => { return looseEqual(a[key], b[key]) }) } else { return false } } catch (e) { return false } } else if (!isObjectA && !isObjectB) { // 兩者都不是對象 // 轉成字符串后,值是否一致 return String(a) === String(b) } else { return false } }
判斷兩個值是否相同,無論是原始類型還是對象類型,如果相同,則直接返回true。
如果兩個都會對象,則分為兩種情況,數組和對象。
都是數組,則保證長度一致,同時調用 every 函數遞歸調用函數,保證每一項都一樣
是時間對象,則保證時間戳相同
是對象,則先取出 key 組成的數組,兩者 key 的個數要相同;再遞歸調用比較 value 值是否相同
以上都不滿足,直接返回false
如果兩者都不是對象,轉成字符串后進行比較。
以上都不滿足,直接返回false
例子:
let a1 = [1,2,3,{a:1,b:2,c:[1,2,3]}]; let b1 = [1,2,3,{a:1,b:2,c:[1,2,3]}]; console.log(looseEqual(a1,b1)); // true let a2 = [1,2,3,{a:1,b:2,c:[1,2,3,4]}]; let b2 = [1,2,3,{a:1,b:2,c:[1,2,3]}]; console.log(looseEqual(a2,b2)); // false11. 函數只執行一次
同樣利用高階函數,在閉包內操作標識的真假,來控制執行一次。
function once (fn) { let called = false return function () { if (!called) { called = true fn.apply(this, arguments) } } }
傳入要執行一次的函數 fn
設置標識為 false
返回一個函數
實際運用:
function test(){ console.log("我只被執行一次"); } let test2 = once(test); test2(); // 我只被執行一次 test2(); test2(); test2();
調用 once 函數后,會返回一個函數,賦值給 test2
第一次調用 test2 后,在函數的尼內部,called 初次為 false, 所以可以執行函數 test,然后把標識 called 設置為true,就類似關閉了大門,下次不再執行。
之后在調用 test2 , test 將不再執行。
12. 自定義 bind 函數function polyfillBind (fn, ctx) { function boundFn (a) { const l = arguments.length return l ? l > 1 ? fn.apply(ctx, arguments) : fn.call(ctx, a) : fn.call(ctx) } boundFn._length = fn.length return boundFn }
自定義的 bind 函數的場景,都是用來兼容不支持原生 bind 方法的環境。 在自己模擬的 bind 函數中,實際上調用的是 call 或 apply。
這個方法寫的相對簡單,如果更深入了解,可以戳此查看這篇文章
如有偏差歡迎指正學習,謝謝。
如果對你有幫助,請關注【前端技能解鎖】:
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/105346.html
摘要:工程實踐立足實踐,提示實際水平內聯函數與性能很多關于性能優化的文章都會談及內聯函數,其也是常見的被詬病為拖慢性能表現的元兇之一不過本文卻是打破砂鍋問到底,論證了內聯函數并不一定就會拖慢性能,過度的性能優化反而會有損于應用性能。 showImg(https://segmentfault.com/img/remote/1460000011481413?w=1240&h=825); 前端每周...
摘要:延伸閱讀學習與實踐資料索引與前端工程化實踐前端每周清單半年盤點之篇前端每周清單半年盤點之與篇前端每周清單半年盤點之篇 前端每周清單專注前端領域內容,以對外文資料的搜集為主,幫助開發者了解一周前端熱點;分為新聞熱點、開發教程、工程實踐、深度閱讀、開源項目、巔峰人生等欄目。歡迎關注【前端之巔】微信公眾號(ID:frontshow),及時獲取前端每周清單;本文則是對于半年來發布的前端每周清單...
摘要:前端每周清單專注前端領域內容,以對外文資料的搜集為主,幫助開發者了解一周前端熱點分為新聞熱點開發教程工程實踐深度閱讀開源項目巔峰人生等欄目。 前端每周清單專注前端領域內容,以對外文資料的搜集為主,幫助開發者了解一周前端熱點;分為新聞熱點、開發教程、工程實踐、深度閱讀、開源項目、巔峰人生等欄目。歡迎關注【前端之巔】微信公眾號(ID:frontshow),及時獲取前端每周清單;本文則是對于...
好久沒更新過Vue的小文章,上次做了一個基于Vue+Mint-ui的移動端AppDemo,集成了推送功能,然后通過cordova打包生成apk,移動端表現還不錯,今天把這個小東西分享出來,希望有更多的小伙伴能夠用Vue去做一些有意思的東西,本人才疏學淺,有說的不對的地方,還請大家多多指教。下面按照慣例放上demo地址和源碼地址,希望大家能給我點下star:Demo(進去需要先注冊才能登錄,用的lo...
閱讀 600·2021-10-08 10:20
閱讀 1490·2021-09-23 11:22
閱讀 3214·2019-08-30 15:55
閱讀 1582·2019-08-28 18:25
閱讀 1857·2019-08-28 18:14
閱讀 1230·2019-08-26 11:37
閱讀 2893·2019-08-26 10:18
閱讀 2420·2019-08-23 18:39