如下摘取的函数,在 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。
这个方法写的相对简单,若是更深刻了解,能够戳此查看这篇文章
若有误差欢迎指正学习,谢谢。
若是对你有帮助,请关注【前端技能解锁】: