【若川视野 x 源码共读】第24期 | Vue2工具函数
前言
本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
源码
Object.freeze 冻结对象
var emptyObject = Object.freeze({});
Object.freeze()方法以一个对象为参数,冻结这个对象;它可以保留对象的可枚举性,可配置性,可写性和原型不被修改;它返回被冻结的对象,但不创建冻结副本。
同时,它也只能冻结对象的第一层数据,无法深冻结,类似浅拷贝。
我们可以通过Object.isFrozen()对某个数据进行判断,是否冻结。
源码中,将emptyObject
定义为一个默认的全局变量。
isUndef 判断数据是否未定义
function isUndef (v) {
return v === undefined || v === null;
}
isDef 判断数据是否已定义
function isDef(value) {
return value !== undefined && value !== null;
}
该方法,一度想过对isUndef进行取反校验
isPrimitive 判断数据是否是原始值
function isPrimitive (value) {
return (
typeof value === 'string' ||
typeof value === 'number' ||
// $flow-disable-line
typeof value === 'symbol' ||
typeof value === 'boolean'
)
}
有没有觉得很奇怪,缺少了null
和undefined
,起初一直以为这个方法是用来校验是否为基本类型,但是看了一下源码中使用该方法的地方,传入的数据不存在这两种类型,因此该方法就不做这两种类型的判断了,否则函数命名可能就是isBaseType
isObject 判断数据是否是对
function isObject (obj) {
return obj !== null && typeof obj === 'object'
}
没有对常规对象做精细化校验,或许是对引用类型进行若判断
isTrue 判断数据是否是true
function isTrue (v) {
return v === true
}
isFalse 判断数据是否是false
function isFalse (v) {
return v === false
}
isPlainObject 判断数据是否是普通对象
function isPlainObject (obj) {
return _toString.call(obj) === '[object Object]'
}
isValidArrayIndex 判断数据是否是可用的数组索引
function isValidArrayIndex (val) {
var n = parseFloat(String(val));
return n >= 0 && Math.floor(n) === n && isFinite(val)
}
isRegExp 判断数据是否是正则表达式
function isRegExp (v) {
return _toString.call(v) === '[object RegExp]'
}
isPromise 判断数据是否是Promise
function isPromise (val) {
return (
isDef(val) &&
typeof val.then === 'function' &&
typeof val.catch === 'function'
)
}
toRawType 获取数据类型
var _toString = Object.prototype.toString;
function toRawType (value) {
return _toString.call(value).slice(8, -1);
}
该方法也弥补了isObject
的不足
满足数组索引值的条件,大于等于0 且 是一个正整数 且 是一个有限制
toString 将数据转化为String
function toString (val) {
return val == null
? ''
: Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)
? JSON.stringify(val, null, 2)
: String(val)
}
toNumber 将数据转化为Number
function toNumber (val) {
const n = parseFloat(val)
return isNaN(n) ? val : n
}
toArray 将数据转为数组
将支持length属性且可读取每一个下标位置的数据(String
, Array
, arguments
),转为数组,且支持转换起始位置
function toArray (list, start) {
start = start || 0
let i = list.length - start
const ret = new Array(i)
while (i--) {
ret[i] = list[i + start]
}
return ret
}
remove 删除数组中的某一项
function remove (arr, item) {
if (arr.length) {
const index = arr.indexOf(item)
if (index > -1) {
return arr.splice(index, 1)
}
}
}
hasOwn 判断数据是否有某个属性
const hasOwnProperty = Object.prototype.hasOwnProperty
export function hasOwn (obj, key) {
return hasOwnProperty.call(obj, key)
}
cached 缓存
function cached (fn) {
const cache = Object.create(null)
return (function cachedFn (str) {
const hit = cache[str]
return hit || (cache[str] = fn(str))
})
}
这个高阶函数比较有意思,同时设计的也很巧妙,通过执行cached
方法,创建了一个公共的缓存函数和缓存数据闭包cache
,每次执行公共的函数时,会优先读取cache
里是否有这个数据,有的话直接返回,否则执行处理函数后,并缓存数据,提高了同一个数据的处理效率。
camelize 将中划线的变量名转为小驼峰
const camelizeRE = /-(\w)/g
function camelize = cached((str) => {
return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
})
// first-name -> firstName
capitalize 将变量名首字母大写
function capitalize = cached((str) => {
return str.charAt(0).toUpperCase() + str.slice(1)
})
// firstName -> FirstName
hyphenate 将小驼峰变量名转为中划线的变量名
const hyphenateRE = /\B([A-Z])/g
function hyphenate = cached((str) => {
return str.replace(hyphenateRE, '-$1').toLowerCase()
})
// firstName -> first-name
polyfillBind Bind补充
// 补充Bind
function polyfillBind (fn, ctx) {
function boundFn (a) {
const l = arguments.length
return l
? l > 1
? fn.apply(ctx, arguments)
: fn.call(ctx, a) // 更多的时候会使用arguments[0],源码更简洁
: fn.call(ctx)
}
boundFn._length = fn.length; // fn函数的形参数量
return boundFn
}
// 原生Bind
function nativeBind (fn, ctx) {
return fn.bind(ctx)
}
// 校验浏览器是否支持Bind,优化Bind
const bind = Function.prototype.bind
? nativeBind
: polyfillBind
这里比较绕,需要理解call
、apply
、bind
的异同之处。
extend 合并两个数据
function extend (to, _from) {
for (const key in _from) {
to[key] = _from[key]
}
return to
}
toObject 将对象数组中每一项合至一个对象
function toObject (arr) {
const res = {}
for (let i = 0; i < arr.length; i++) {
if (arr[i]) {
extend(res, arr[i])
}
}
return res
}
noop 空函数
function noop (a, b, c) {}
no 返回false
const no = (a, b, c) => false
identity 返回相同数据
const identity = (_) => _
genStaticKeys 从编译器模块生成包含静态键的字符串
function genStaticKeys (modules) {
return modules.reduce((keys, m) => {
return keys.concat(m.staticKeys || [])
}, []).join(',')
}
looseEqual 校验两个值是否大致相同
这个是工具函数里代码最多的一个函数
function looseEqual (a, b) {
//如果两个值完全相等,返回true
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) {
// 如果两个都不是数组,且属相数量一样,则递归比较每一项
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 {
/* istanbul ignore next */
return false
}
} catch (e) {
/* istanbul ignore next */
return false
}
} else if (!isObjectA && !isObjectB) {
// 如果两个值都不是对象,则判断转为字符串后是否相等
return String(a) === String(b)
} else {
return false
}
}
looseIndexOf 返回数组中存在某个数据的下标
function looseIndexOf (arr, val) {
for (let i = 0; i < arr.length; i++) {
if (looseEqual(arr[i], val)) return i
}
return -1
}
once 函数只执行一次
通过闭包声明变量called
,校验当前函数是否执行
function once (fn) {
let called = false
return function () {
if (!called) {
called = true
fn.apply(this, arguments)
}
}
}
makeMap 生成一个属性Map
校验某个数据中是否存在key属性,这个方法类似于catched
function makeMap (str, expectsLowerCase) {
const map = Object.create(null)
const list = str.split(',')
for (let i = 0; i < list.length; i++) {
map[list[i]] = true
}
return expectsLowerCase
? val => map[val.toLowerCase()]
: val => map[val]
}
isBuiltInTag 校验是否是内置tag
export const isBuiltInTag = ('slot,component', true)
isReservedAttribute 校验是否是保留属性
export const isReservedAttribute = makeMap('key,ref,slot,slot-scope,is')
知识拓展
JSON.stringify
Bind、Apply、Call
感悟
看完源码之后,印象比较深刻的还是catched
和makeMap
工具函数,将某一类的数据统一化处理,实现了代码设计的高内聚、低耦合,提高了代码的可拓展性和简洁性
转载自:https://juejin.cn/post/7170354853045075976