如下摘取的函數,在 shared 目錄下公用的工具方法。文件在 util.js 中,githu地址。前端
提取了一些經常使用通用的函數進行剖析,主要包含如下內容:vue
- 建立一個被凍結的空對象
- 判斷是不是 undefined 或 null
- 判斷是否不是 undefined 和 null
- 判斷是不是原始類型
- 判斷是不是對象類型
- 判斷有效的數組下標
- 判斷是不是一個 Promise 對象
- 刪除數組中指定元素
- 用作緩存的高階函數
- 遞歸判斷一個對象是否和另個一個對象徹底相同
- 函數只執行一次
- 自定義 bind 函數
export const emptyObject = Object.freeze({})
一旦建立不能給這個對象添加任何屬性。git
function isUndef (v) { return v === undefined || v === null }
在源碼中不少地方會判斷一個值是否被定義,因此這裏直接抽象成一個公共函數。
傳入任意值,返回是一個布爾值。github
function isDef (v) { return v !== undefined && v !== null }
當傳入的值,既不是 undefined 也不是 null 返回true。segmentfault
function isPrimitive (value) { return ( typeof value === 'string' || typeof value === 'number' || typeof value === 'symbol' || typeof value === 'boolean' ) }
在js中提供了兩大類數據類型:api
function isObject (obj: mixed) { return obj !== null && typeof obj === 'object' }
傳入的值排除掉 null,由於在js中 null 使用運算符 typeof 獲得的值是 object,這是一個 bug。由於歷史緣由放棄修復了。具體能夠參考這裏查看數組
function isValidArrayIndex (val) { const n = parseFloat(String(val)); // 轉成數字 // 下標大於等於0,而且不是小數,而且是有限的數 return n >= 0 && Math.floor(n) === n && isFinite(val) }
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 方法形成的。app
而若是手動調用 toString 方法或者調用 String,轉換爲字符串,則不會報錯:
let test = Symbol('test'); console.log(test.toString()); // "Symbol(test)" console.log(String(test)) // "Symbol(test)"
console.log(isFinite(Infinity)); // false console.log(isFinite(-Infinity)); // false console.log(isFinite(123)); // true
function isPromise (val) { return ( isDef(val) && typeof val.then === 'function' && typeof val.catch === 'function' ) }
當一個對象存在 then 方法,而且也存在 catch 方法,能夠斷定爲 Promise 對象。
這個方法有效的避免了進行刪除數組某一項時,都要進行查找位置再刪除的重複工做。
function remove (arr, item){ if (arr.length) { const index = arr.indexOf(item) if (index > -1) { return arr.splice(index, 1) } } }
用高階函數的好處是無需暴露不一樣要求的緩存對象在外面,造成一個閉包。下面這個函數的技巧,應用在工做中,能夠提升代碼運行的效率。
function cached(fn) { // 建立一個緩存對象 const cache = Object.create(null) return (function cachedFn (str) { // 先從緩存對象中找,要操做的值,是否已經有了操做結果 const hit = cache[str] // 若是有,則直接返回;沒有,則調用函數對值進行操做,並把操做結果存在緩存對象中 return hit || (cache[str] = fn(str)) }) }
例如如下運用,函數的做用是把字符串的首字母大寫。
const capitalize = cached((str) => { return str.charAt(0).toUpperCase() + str.slice(1) })
這時咱們就能夠調用 capitalize 對字符串進行首字母大寫了。
capitalize('test'); // "Test" capitalize('test'); // "Test" capitalize('test'); // "Test"
第一次調用 capitalize 函數,先從緩存對象中取值,沒有,則調用計算函數進行計算結果返回,同時存入緩存對象中。這時的緩存對象爲:
{test: 'Test'}
再屢次調用 capitalize 時,從緩存對象中取值,命中,直接返回,無需再進行計算操做。
判斷兩個對象是否相同,主要是判斷兩個對象包含的值都是同樣的,若是包含的值依然是個對象,則繼續遞歸調用判斷是否相同。
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 } }
若是兩個都會對象,則分爲兩種狀況,數組和對象。
例子:
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)); // false
一樣利用高階函數,在閉包內操做標識的真假,來控制執行一次。
function once (fn) { let called = false return function () { if (!called) { called = true fn.apply(this, arguments) } } }
實際運用:
function test(){ console.log('我只被執行一次'); } let test2 = once(test); test2(); // 我只被執行一次 test2(); test2(); test2();
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。
這個方法寫的相對簡單,若是更深刻了解,能夠戳此查看這篇文章
若有誤差歡迎指正學習,謝謝。
若是對你有幫助,請關注【前端技能解鎖】: