♥️♥️♥️ 李哈哈要奋发啦~冲冲冲!!!相信只要坚持学习会有所回报的!♥️♥️♥️
1. 前言
- 本文参加了由公众号@若川视野发起的每周源码共度活动,点击了解详情一起参与。
- 这是源码共度的第2期,链接:juejin.cn/post/708744…
- github仓库:github.com/vuejs/core/…
2. 工具函数
2.1 babelParserDefaultPlugins解析默认插件
/**
* List of @babel/parser plugins that are used for template expression
* transforms and SFC script transforms. By default we enable proposals slated
* for ES2020. This will need to be updated as the spec moves forward.
* Full list at https://babeljs.io/docs/en/next/babel-parser#plugins
*/
const babelParserDefaultPlugins = [
'bigInt',
'optionalChaining',
'nullishCoalescingOperator'
];
2.2 EMPTY_OBJ 空对象
export const EMPTY_OBJ = (process.env.NODE_ENV !== 'production')
? Object.freeze({})
: {};
// export const EMPTY_OBJ: { readonly [key: string]: any } = __DEV__
// ? Object.freeze({})
// : {}
Object.freeze()用来冻结对象,为浅冻结,只对第一层有效。被冻结的对象有以下几个特性:
- 不能添加新属性
- 不能删除已有属性
- 不能修改已有属性的值
- 不能修改原型
- 不能修改已有属性的可枚举性、可配置性、可写性
注意:Object.freeze()返回值就是被冻结的对象,该对象完全等于传入的对象,所以我们一般不需要接受、收返回值。也可冻结数组,key就是下标。
如何实现深冻结?
function deepFreeze(obj){
//获取所有属性
let propNames = Object.getOwnPropertyNames(obj);
//遍历
propNames.forEach(key => {
let prop = obj[key]
if(key instanceof Object && prop !== null) {
deepFreeze(prop)
}
})
//冻结自身
return Object.freeze(obj)
}
应用场景
提高性能。提升的效果随着数据量的递增而递增,对于纯展示的大数据,都可以使用Object.freeze提升性能。
2.3 EMPTY_Arr 空数组
const EMPTY_ARR = (process.env.NODE_ENV !== 'production') ? Object.freeze([]) : [];
// export const EMPTY_ARR = __DEV__ ? Object.freeze([]) : []
2.4 NOOP空函数的使用
const NOOP = () => { };
作用:
- 方便判断
//摘取自源码
if (render && instance.render === NOOP) {
instance.render = render;
}
- 方便压缩
如果没有NOOP方法,那么在很多地方我们可能都要再定义一个匿名的空函数,这样的匿名函数就会导致无法被压缩,降低了代码的压缩率。
- 避免代码出错
- 提高代码的可读性
2.5 NO 永远返回false的函数
/**
* Always return false.
*/
const NO = () => false;
// 默认返回false的函数赋值
源码中的使用:
2.6 判断字符串是不是on开头
const onRE = /^on[^a-z]/;
const isOn = (key) => onRE.test(key);
//export const isOn = (key: string) => onRE.test(key)
2.7 isModelListener 监听器
判断字符串是不是以“onUpdate:”开头:
const isModelListener = (key) => key.startsWith('onUpdate:');
//export const isModelListener = (key: string) => key.startsWith('onUpdate:')
// 例子:
isModelListener('onUpdate:change'); // true
isModelListener('1onUpdate:change'); // false
// startsWith 是 ES6 提供的方法
2.8 extends 继承 合并
const extend = Object.assign;
// 例子:
const target = { a: 1, b: 2 };
const source = { b: 4, c: 5};
const returnTarget = extend(target, source);
console.log(target); // { a: 1, b: 4, c: 5}
console.log(target === returnTarget); // true
2.9 remove移除数组某个元素
const remove = (arr, el) => {
const i = arr.indexOf(el);
if (i > -1) {
arr.splice(i, 1);
}
};
// export const remove = <T>(arr: T[], el: T) => {
// const i = arr.indexOf(el)
// if (i > -1) {
// arr.splice(i, 1)
// }
// }
splice 其实是一个很耗性能的方法。删除数组中的一项,其他元素都要移动位置。
2.10 hasOwn判断是否有某个属性
const hasOwnProperty = Object.prototype.hasOwnProperty;
const hasOwn = (val, key) => hasOwnProperty.call(val, key);
// export const hasOwn = (
// val: object,
// key: string | symbol
// ): key is keyof typeof val => hasOwnProperty.call(val, key)
2.11-2.23数据类型
const isArray = Array.isArray;
const isMap = (val) => toTypeString(val) === '[object Map]';
const isSet = (val) => toTypeString(val) === '[object Set]';
const isDate = (val) => val instanceof Date;
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 isPromise = (val) => {
return isObject(val) && isFunction(val.then) && isFunction(val.catch);
};
const objectToString = Object.prototype.toString;
const toTypeString = (value) => objectToString.call(value);
const toRawType = (value) => {
// extract "RawType" from strings like "[object RawType]"
return toTypeString(value).slice(8, -1);
};
//判断是不是纯粹的对象
const isPlainObject = (val) => toTypeString(val) === '[object Object]';
// export const isArray = Array.isArray
// export const isMap = (val: unknown): val is Map<any, any> =>
// toTypeString(val) === '[object Map]'
// export const isSet = (val: unknown): val is Set<any> =>
// toTypeString(val) === '[object Set]'
// export const isDate = (val: unknown): val is Date => val instanceof Date
// export const isFunction = (val: unknown): val is Function =>
// typeof val === 'function'
// export const isString = (val: unknown): val is string => typeof val === 'string'
// export const isSymbol = (val: unknown): val is symbol => typeof val === 'symbol'
// export const isObject = (val: unknown): val is Record<any, any> =>
// val !== null && typeof val === 'object'
// export const isPromise = <T = any>(val: unknown): val is Promise<T> => {
// return isObject(val) && isFunction(val.then) && isFunction(val.catch)
// }
// export const objectToString = Object.prototype.toString
// export const toTypeString = (value: unknown): string =>
// objectToString.call(value)
// export const toRawType = (value: unknown): string => {
// // extract "RawType" from strings like "[object RawType]"
// return toTypeString(value).slice(8, -1)
// }
// export const isPlainObject = (val: unknown): val is object =>
// toTypeString(val) === '[object Object]'
拓展: 数据类型的判断
2.24 isIntegerKey 判断是不是数字型的字符串key值
传入一个以逗号分隔的字符串,生成一个 map(键值对),并且返回一个函数检测 key 值在不在这个 map 中。第二个参数是小写选项。
const isIntegerKey = (key) => isString(key) &&
key !== 'NaN' &&
key[0] !== '-' &&
'' + parseInt(key, 10) === key;
// export const isIntegerKey = (key: unknown) =>
// isString(key) &&
// key !== 'NaN' &&
// key[0] !== '-' &&
// '' + parseInt(key, 10) === key
// 例子:
isIntegerKey('a'); // false
isIntegerKey('0'); // true
isIntegerKey('011'); // false
isIntegerKey('11'); // true
// 其中 parseInt 第二个参数是进制。
// 字符串能用数组取值的形式取值。
// 还有一个 charAt 函数,但不常用
'abc'.charAt(0) // 'a'
// charAt 与数组形式不同的是 取不到值会返回空字符串'',数组形式取值取不到则是 undefined
2.25 makeMap && isReservedProp
/**
* Make a map and return a function for checking if a key
* is in that map.
* IMPORTANT: all calls of this function must be prefixed with
* \/\*#\_\_PURE\_\_\*\/
* So that rollup can tree-shake them if necessary.
*/
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];
}
// export function makeMap(
// str: string,
// expectsLowerCase?: boolean
// ): (key: string) => boolean {
// const map: Record<string, boolean> = Object.create(null)
// const list: Array<string> = str.split(',')
// for (let i = 0; i < list.length; i++) {
// map[list[i]] = true
// }
// return expectsLowerCase ? val => !!map[val.toLowerCase()] : val => !!map[val]
// }
const isReservedProp = /*#__PURE__*/ makeMap(
// the leading comma is intentional so empty string "" is also included
',key,ref,' +
'onVnodeBeforeMount,onVnodeMounted,' +
'onVnodeBeforeUpdate,onVnodeUpdated,' +
'onVnodeBeforeUnmount,onVnodeUnmounted');
// 保留的属性
isReservedProp('key'); // true
isReservedProp('ref'); // true
isReservedProp('onVnodeBeforeMount'); // true
// ......
isReservedProp('onVnodeUnmounted'); // true
知识点
- makeMap返回的是函数,函数传入的参数就是要判断是否存在的key;
- Object.create(null)新创建的对象除了自身属性a之外,原型链上没有任何属性,也就是没有继承Object的任何东西。
{}新创建的对象继承了Object自身的方法,如hasOwnProperty、toString等,在新对象上可以直接使用。详见司想君的文章分享。
2.26 cacheStringFunction 缓存
const cacheStringFunction = (fn) => {
const cache = Object.create(null);
return ((str) => {
const hit = cache[str];
return hit || (cache[str] = fn(str));
});
};
这个高阶函数也是和上面 MakeMap 函数类似。只不过接收参数的是函数。
内部使用闭包来缓存之前的计算结果,如果再次调用时发现已经计算过,则返回之前的结果。
源码用法:
// \w 是 0-9a-zA-Z_ 数字 大小写字母和下划线组成
// () 小括号是 分组捕获
const camelizeRE = /-(\w)/g;
/**
* @private
*/
// 连字符 - 转驼峰 on-click => onClick
const camelize = cacheStringFunction((str) => {
return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : '')); //(_, c)的第一个参数代表匹配到的字符串,第二个参数代表第一个括号匹配的字符串
});
// \B 是指 非 \b 单词边界。
const hyphenateRE = /\B([A-Z])/g;
/**
* @private
*/
// 驼峰 - 转连字符
const hyphenate = cacheStringFunction((str) => str.replace(hyphenateRE, '-$1').toLowerCase());
// 举例:onClick => on-click
const hyphenateResult = hyphenate('onClick');
console.log('hyphenateResult', hyphenateResult); // 'on-click'
/**
* @private
*/
// 首字母转大写
const capitalize = cacheStringFunction((str) => str.charAt(0).toUpperCase() + str.slice(1));
/**
* @private
*/
// click => onClick
const toHandlerKey = cacheStringFunction((str) => (str ? `on${capitalize(str)}` : ``));
const result = toHandlerKey('click');
console.log(result, 'result'); // 'onClick'
学习点:
replace()第二个参数是函数的时候,函数的第一个参数是。参考MDN-String.prototype.replace()
3.27 hasChanged 判断是不是有变化
const hasChanged = (value, oldValue) => !Object.is(value, oldValue);
Object.js()ES6语法,用于比较两个数是否相同,详见阮一峰的文章
2.28 invokeArrayFns 执行数组里的函数
数组中存放函数,这种写法方便统一执行多个函数。
const invokeArrayFns = (fns, arg) => {
for (let i = 0; i < fns.length; i++) {
fns[i](arg);
}
};
// export const invokeArrayFns = (fns: Function[], arg?: any) => {
// for (let i = 0; i < fns.length; i++) {
// fns[i](arg)
// }
// }
2.29 def 定义对象属性
const def = (obj, key, value) => {
Object.defineProperty(obj, key, {
configurable: true,
enumerable: false,
value
});
};
// export const def = (obj: object, key: string | symbol, value: any) => {
// Object.defineProperty(obj, key, {
// configurable: true,
// enumerable: false,
// value
// })
// }
学习点:
Object.defineProperty(obj, prop, descripor) vue2双向绑定原理。详见MDN描述
接收3个参数:
- obj:要定义属性的对象
- prop:要定义或修改的属性的名称或 Symbol
- desCriptor:要定义或修改的属性描述符
属性描述符:
- configurable:该属性是否可被改变(删除)
- enumerable: 该属性在
for in循环中是否会被枚举 - writable:该属性是否可写
- value:该属性对应的值
- get():属性的getter函数
- set():属性的setter函数
描述符默认值:
- 拥有布尔值的键
configurable、enumerable和writable的默认值都是false。 - 属性值和函数的键
value、get和set字段的默认值为undefined。
2.30 toNumber转数字
const toNumber = (val) => {
const n = parseFloat(val);
return isNaN(n) ? val : n;
};
toNumber('111'); // 111
toNumber('a111'); // 'a111'
parseFloat('a111'); // NaN
isNaN(NaN); // true
ES6中的Number.isNaN()方法用来检查一个值是否为NaN。
2.31 getGlobalThis 全局对象
let _globalThis;
const getGlobalThis = () => {
return (_globalThis ||
(_globalThis =
typeof globalThis !== 'undefined'
? globalThis
: typeof self !== 'undefined'
? self
: typeof window !== 'undefined'
? window
: typeof global !== 'undefined'
? global
: {}));
};
获取全局 this 指向。
如果存在 globalThis 就用 globalThis。MDN globalThis
初次执行肯定是 _globalThis 是 undefined。所以会执行后面的赋值语句。
Node环境下,使用global。
如果都不存在,使用空对象。可能是微信小程序环境下。
下次执行就直接返回 _globalThis,不需要第二次继续判断了。这种写法值得我们学习。
总结
通过学习源码 shared 模块下的几十个工具函数,发现也没有很难,与平时自己写的比较,其代码更加规范,简洁明了,值得学习!