蓝狮注册登陆Vue 源码中的工具函数

前言
在 vue 源码中,封装了很多工具函数,学习这些函数,一方面学习大佬们的实现方式,另一方面是温习基础知识,希望大家在日常工作中,简单的函数也可以自己封装,蓝狮注册开户提高编码能力。

本次涉及的工具函数 1-16 在 Vue3 的源码中,路径是 core/packages/shared/src/index.ts。

17-22 在 Vue2 的源码中,路径是 vue/src/shared/util.ts。

  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,不同地方用同一个对象,不会有问题么?
首先,很多初始化操作,后续都会重新赋值,EMPTY_OBJ 只是作为占位使用。其次,因为 Object.freeze 的原因,无法修改 EMPTY_OBJ,所以任何引用这个对象的地方,都不会受到影响。
为什么判断是 DEV(process.env.NODE_ENV !== ‘production’) 的时候才使用 Object.freeze?
Object.freeze 更多的是 Vue 源码开发者在调试时使用,可以通过报错,防止对空对象操作,更快发现源码问题。也因此,开发环境最终会避免了对 EMPTY_OBJ 的赋值操作,所以在生产环境使用 Object.freeze 意义不大。

  1. EMPTY_ARR 空数组
    const EMPTY_ARR = DEV ? Object.freeze([]) : []
  2. NOOP 空函数
    const NOOP = () => {}
    依旧作为兜底和占位使用:
  3. NO 永远返回 false 的函数
    const NO = () => false
    源码中的使用:
  4. isOn 判断字符串是不是 on 开头,并且 on 后首字母不是小写字母
    const onRE = /^on[^a-z]/;
    const isOn = (key) => onRE.test(key);

// 示例
isOn(‘onChange’); // true
isOn(‘onchange’); // false
isOn(‘on3change’); // true

  1. 类型判断
    const isArray = Array.isArray

const 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([]) // true
isPlainObject([]) // false

const isPromise = (val) => {
return isObject(val) && isFunction(val.then) && isFunction(val.catch)
}

  1. toRawType 提取数据原始类型
    const toRawType = (value) => {
    return Object.prototype.toString.call(value).slice(8, -1)
    }

// 示例
toRawType(”); ‘String’
toRawType([]); ‘Array’
源码中的使用:

  1. isIntegerKey 判断是不是数字型的字符串
    const isIntegerKey = (key) => isString(key) &&
    key !== ‘NaN’ &&
    key[0] !== ‘-‘ &&
    ” + parseInt(key, 10) === key;

// 例子:
isIntegerKey(‘a’); // false
isIntegerKey(‘0’); // true
isIntegerKey(‘011’); // false
isIntegerKey(’11’); // true
isIntegerKey(‘-11’); // false
isIntegerKey(11); // false
isIntegerKey(‘NaN’); // false

  1. makeMap 将字符串分隔成 map,区分大小写,返回一个函数来判断 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];
    }
  2. 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’); // true
isReservedProp(‘onVnodeBeforeMount’); // true
isReservedProp(”); // true
isReservedProp(‘ ‘); // false
如果有 /#PURE/ 这个标志,说明他是纯函数,如果没有调用它,打包工具会直接通 tree-shaking 把它删除,减少代码体积。

  1. isBuiltInDirective 是否是内置指令
    const isBuiltInDirective = /#PURE/ makeMap(
    ‘bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text,memo’
    )
  2. cacheStringFunction 将函数变为可缓存结果的函数
    const cacheStringFunction = (fn) => {
    const cache = Object.create(null);
    return ((str) => {
    const hit = cache[str];
    return hit || (cache[str] = fn(str));
    });
    };
  3. 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 => onClickA
    camelize(‘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-a
hyphenate(‘onClickA’);

  1. hasChanged 判断是不是有变化
    const hasChanged = (value, oldValue) => value !== oldValue && (value === value || oldValue === oldValue);

// 示例
hasChanged(1, 1); // false
hasChanged(1, 2); // true
hasChanged(+0, -0); // false
hasChanged(NaN, NaN); // false
// 场景:watch 监测值是不是变化了

// 扩展 Object.is & ===
Object.is(+0, -0); // false
Object.is(NaN, NaN); // true

+0 === -0 // true
NaN === NaN // false

  1. invokeArrayFns 执行数组里的函数
    const invokeArrayFns = (fns, arg) => {
    for (let i = 0; i < fns.length; i++) {
    fnsi;
    }
    };

// 示例
const arr = [
function(val){
console.log(val + ‘张三’);
},
function(val){
console.log(val + ‘李四’);
},
function(val){
console.log(val + ‘王五’);
},
]
invokeArrayFns(arr, ‘我是:’);
源码中的使用:

  1. toNumber 转数字
    const toNumber = (val) => {
    const n = parseFloat(val);
    return isNaN(n) ? val : n;
    };

toNumber(‘111’); // 111
toNumber(‘a111’); // ‘a111’
toNumber(’11a11′); // ’11’
toNumber(NaN); // NaN

// isNaN vs Number.isNaN
// isNaN 判断是不是数字 is Not a Number
// Number.isNaN 判断是不是 NaN
isNaN(NaN); // true
isNaN(‘a’); // true
Number.isNaN(NaN); // true
Number.isNaN(‘a’); // false

// Number.isNaN 的 polyfill
if (!Number.isNaN) {
Number.isNaN = function (n) {
// 方法一
return (window.isNaN(n) && typeof n === ‘number’);
// 方法二 利用只有 NaN 不跟自己相等的特性
return n !== n;
};
}

  1. isPrimitive 是否为原始数据
    function isPrimitive(value) {
    return (
    typeof value === ‘string’ ||
    typeof value === ‘number’ ||
    typeof value === ‘symbol’ ||
    typeof value === ‘boolean’
    )
    }
  2. isValidArrayIndex 是否为有效的数组下标,整数并且不是无穷大
    function isValidArrayIndex(val) {
    const n = parseFloat(String(val))
    return n >= 0 && Math.floor(n) === n && isFinite(val)
    }
    // isFinite 如果参数是 NaN,正无穷大或者负无穷大,会返回 false,蓝狮注册登陆其他返回 true
  3. 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

  1. toArray 类数组转化为数组
    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
    }
  2. once 只执行一次
    function once(fn) {
    let called = false
    return function () {
    if (!called) {
    called = true
    fn.apply(this, arguments)
    }
    }
    }
  3. isNative 是否为原生系统函数
    function isNative(Ctor) {
    return typeof Ctor === ‘function’ && /native code/.test(Ctor.toString())
    }
    来源:https://segmentfault.com/a/1190000042073070
0 Comments
Leave a Reply