22個Vue 源碼中的工具函數
點擊上方 前端Q,關注公眾號
回復加群,加入前端Q技術交流群
作者:諾頓
原文鏈接:https://segmentfault.com/a/1190000042073070
前言
1、 EMPTY_OBJ 空對象
const EMPTY_OBJ = __DEV__? Object.freeze({}): {}
注意:
Object.freeze 只能淺凍結,如果屬性是對象,對屬性的屬性的修改就無法凍結了
const obj = { name: '張三', info: { a: 1, b: 2}};Object.freeze(obj);obj.name = '李四';console.log(obj); // { name: '張三', info: { a: 1, b: 2 } }obj.info.a = 66;console.log(obj); // { name: '張三', info: { a: 66, b: 2 } }
源碼中的使用:

使用的地方有的是 options,有的是 props,不同地方用同一個對象,不會有問題么?
首先,很多初始化操作,后續(xù)都會重新賦值,EMPTY_OBJ 只是作為占位使用。其次,因為 Object.freeze 的原因,無法修改 EMPTY_OBJ,所以任何引用這個對象的地方,都不會受到影響。為什么判斷是 __DEV__(process.env.NODE_ENV !== 'production') 的時候才使用 Object.freeze?
Object.freeze 更多的是 Vue 源碼開發(fā)者在調試時使用,可以通過報錯,防止對空對象操作,更快發(fā)現源碼問題。也因此,開發(fā)環(huán)境最終會避免了對 EMPTY_OBJ 的賦值操作,所以在生產環(huán)境使用 Object.freeze 意義不大。
2、EMPTY_ARR 空數組
const EMPTY_ARR = __DEV__ ? Object.freeze([]) : []3、 NOOP 空函數
const NOOP = () => {}
4、 NO 永遠返回 false 的函數
const NO = () => false

5、isOn 判斷字符串是不是 on 開頭,并且 on 后首字母不是小寫字母
const onRE = /^on[^a-z]/;const isOn = (key) => onRE.test(key);// 示例isOn('onChange'); // trueisOn('onchange'); // falseisOn('on3change'); // true6、類型判斷
const isArray = Array.isArrayconst isFunction = (val) => typeof val === 'function'const isString = (val) => typeof val === 'string'const isSymbol = (val) => typeof val === 'symbol'const isObject = (val) => val !== null && typeof val === 'object'const toTypeString = (value) => Object.prototype.toString.call(value)const isMap = (val) => toTypeString(val) === '[object Map]'const isSet = (val) => toTypeString(val) === '[object Set]'const isDate = (val) => toTypeString(val) === '[object Date]'const isPlainObject = (val) => Object.prototype.toString.call(val) === '[object Object]'// isPlainObject 判斷是不是普通對象(排除正則、數組、日期、new Boolean、new Number、new String 這些特殊的對象)isObject([]) // trueisPlainObject([]) // falseconst isPromise = (val) => { return isObject(val) && isFunction(val.then) && isFunction(val.catch)}
7、 toRawType 提取數據原始類型
const toRawType = (value) => { return Object.prototype.toString.call(value).slice(8, -1)}// 示例toRawType(''); 'String'toRawType([]); 'Array'


8、isIntegerKey 判斷是不是數字型的字符串
const isIntegerKey = (key) => isString(key) &&key !== 'NaN' &&key[0] !== '-' && '' + parseInt(key, 10) === key;// 例子:isIntegerKey('a'); // falseisIntegerKey('0'); // trueisIntegerKey('011'); // falseisIntegerKey('11'); // trueisIntegerKey('-11'); // falseisIntegerKey(11); // falseisIntegerKey('NaN'); // false
9、makeMap 將字符串分隔成 map,區(qū)分大小寫,返回一個函數來判斷 map 中是否含有某個 key
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];}
10、isReservedProp 是否是保留屬性
const isReservedProp = /*#__PURE__*/ makeMap(// the leading comma is intentional so empty string "" is also included',key,ref,ref_for,ref_key,' + 'onVnodeBeforeMount,onVnodeMounted,' + 'onVnodeBeforeUpdate,onVnodeUpdated,' + 'onVnodeBeforeUnmount,onVnodeUnmounted');// ['', 'key', 'ref', 'ref_for', 'ref_key', 'onVnodeBeforeMount', 'onVnodeMounted', 'onVnodeBeforeUpdate', 'onVnodeUpdated', 'onVnodeBeforeUnmount', 'onVnodeUnmounted']// 示例isReservedProp('key'); // trueisReservedProp('onVnodeBeforeMount'); // trueisReservedProp(''); // trueisReservedProp(' '); // false
11、 isBuiltInDirective 是否是內置指令
const isBuiltInDirective = /*#__PURE__*/ makeMap( 'bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text,memo')
12、 cacheStringFunction 將函數變?yōu)榭删彺娼Y果的函數
const cacheStringFunction = (fn) => { const cache = Object.create(null); return ((str) => { const hit = cache[str]; return hit || (cache[str] = fn(str));});};
13、 camelize & hyphenate 連字符與駝峰互轉
const camelizeRE = /-(\w)/g;const camelize = cacheStringFunction((str) => { return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''));});// 清爽版const camelize = str => str.replace(camelizeRE, (_, c) => { return c ? c.toUpperCase() : '';});// 舉例:on-click-a => onClickAcamelize('on-click-a');const hyphenateRE = /\B([A-Z])/g;const hyphenate = cacheStringFunction((str) => str.replace(hyphenateRE, '-$1').toLowerCase());// 清爽版const hyphenate = str => str.replace(hyphenateRE, '-$1').toLowerCase();// 仿照 camelize 寫法const hyphenate = str => str.replace(hyphenateRE, (_, c) => { return c ? `-${c.toLowerCase()}` : '';});// 舉例:onClickA => on-click-ahyphenate('onClickA');
14、 hasChanged 判斷是不是有變化
const hasChanged = (value, oldValue) => value !== oldValue && (value === value || oldValue === oldValue);// 示例hasChanged(1, 1); // falsehasChanged(1, 2); // truehasChanged(+0, -0); // falsehasChanged(NaN, NaN); // false// 場景:watch 監(jiān)測值是不是變化了// 擴展 Object.is & ===Object.is(+0, -0); // false Object.is(NaN, NaN); // true+0 === -0 // trueNaN === NaN // false
15、invokeArrayFns 執(zhí)行數組里的函數
const invokeArrayFns = (fns, arg) => { for (let i = 0; i < fns.length; i++) {fns[i](arg);}};// 示例const arr = [ function(val){ console.log(val + '張三');}, function(val){ console.log(val + '李四');}, function(val){ console.log(val + '王五');},]invokeArrayFns(arr, '我是:');

16、 toNumber 轉數字
const toNumber = (val) => {const n = parseFloat(val); return isNaN(n) ? val : n;};toNumber('111'); // 111toNumber('a111'); // 'a111'toNumber('11a11'); // '11'toNumber(NaN); // NaN// isNaN vs Number.isNaN// isNaN 判斷是不是數字 is Not a Number// Number.isNaN 判斷是不是 NaNisNaN(NaN); // trueisNaN('a'); // trueNumber.isNaN(NaN); // trueNumber.isNaN('a'); // false// Number.isNaN 的 polyfillif (!Number.isNaN) { Number.isNaN = function (n) { // 方法一 return (window.isNaN(n) && typeof n === 'number'); // 方法二 利用只有 NaN 不跟自己相等的特性 return n !== n;};}
17、isPrimitive 是否為原始數據
function isPrimitive(value) { return ( typeof value === 'string' || typeof value === 'number' || typeof value === 'symbol' || typeof value === 'boolean')}
18、 isValidArrayIndex 是否為有效的數組下標,整數并且不是無窮大
function isValidArrayIndex(val) { const n = parseFloat(String(val)) return n >= 0 && Math.floor(n) === n && isFinite(val)}// isFinite 如果參數是 NaN,正無窮大或者負無窮大,會返回 false,其他返回 true
19、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): fn.call(ctx)}boundFn._length = fn.length return boundFn}function nativeBind(fn, ctx) { return fn.bind(ctx)}const bind = Function.prototype.bind ? nativeBind : polyfillBind
20、 toArray 類數組轉化為數組
function toArray(list, start) { start = start || 0let i = list.length - startconst ret = new Array(i) while (i--) {ret[i] = list[i + start]} return ret}
21、 once 只執(zhí)行一次
function once(fn) { let called = falsereturn function () { if (!called) {called = truefn.apply(this, arguments)}}}
22、 isNative 是否為原生系統(tǒng)函數
function isNative(Ctor) { return typeof Ctor === 'function' && /native code/.test(Ctor.toString())}

往期推薦



最后
歡迎加我微信,拉你進技術群,長期交流學習...
歡迎關注「前端Q」,認真學前端,做個專業(yè)的技術人...


評論
圖片
表情
