本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
1、空对象 Object.freeze({})
const EMPTY_OBJ = (process.env.NODE_ENV !== 'production')
? Object.freeze({})
: {};
- 1)Object.freeze 是什么?
- Object.freeze 是 冻结对象,冻结的对象 最外层 无法修改。
- 2)为什么 在 非线上环境 中 EMPTY_OBJ 要用 Object.freeze?而在 线上环境 只需 {} 即可?
- 猜测:线上环境不会被修改,但是开发环境有可能会被修改
- 3)在哪些实际的业务场景中可以用到空对象?
- todo
2、空数组 Object.freeze([])
const EMPTY_ARR = (process.env.NODE_ENV !== 'production') ? Object.freeze([]) : [];
- 1)在哪些实际的业务场景中可以用到空数组?
- todo
3、空函数
const NOOP = () => {};
* 使用场景?todo
* 作用? 1) 方便判断, 2)方便压缩
- 使用场景?todo
- 作用? 1) 方便判断, 2)方便压缩
4、永远返回 false 的函数
const NO = () => false;
作用? 1)方便压缩
5、isOn 正则判断(判断字符串是不是 on 开头 且 on 后首字母不是小写字母)
知识点:regex 反向字符集 [^] 和 reg.text(str)
const onRE = /^on[^a-z]/;
const isOn = (key) => onRE.test(key);
6、是否是监听器 str.startsWith('')(判断字符串是不是以onUpdate:开头)
知识点:str.startsWith('xx')
const isModelListener = (key) => key.startsWith('onUpdate:');
7、合并函数 Object.assign
- 为什么不写成 const extend = (xx) => {Object.assign(xx)}?
- 因为 Object.assign 本身就是个函数
const extend = Object.assign;
8、remove 移除数组的一项 arr.splice(i, 1);
- 知识点:array 的 splice 方法
- 引申:
- 1)array的其他方法?
- todo
- 2)删除数组中的一项 的其他实现?及对比?
- todo
const remove = (arr, el) => {
const i = arr.indexOf(el);
if (i > -1) {
arr.splice(i, 1);
}
};
9、 hasOwn 是不是自己本身所拥有的属性
- 知识点:
- 1)Object.prototype.hasOwnProperty
- 2)call函数
- fn.call(target, params) 将 this 显式指定给第一个参数 target,并执行函数fn(params)。
const hasOwnProperty = Object.prototype.hasOwnProperty;
const hasOwn = (val, key) => hasOwnProperty.call(val, key);
10、isArray 判断是否是数组
const isArray = Array.isArray;
11、toString & typeof & instanceof 判断基本数据类型
typeof
- typeof 操作符返回一个字符串,表示未经计算的操作数的类型。
- 优点:能够检查undefined、number、boolean、string、function、symbol、bigint 7种类型
- 缺点:无法区分object、array、null、date,都是返回 object
instanceof
- instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上。
- 优点:能够检查 Object、Array、Function、Date 类型
- 缺点:检查不了number、boolean、string类型,结果都是返回false
object.prototype.toString.call
- 优点:任何类型都能准确的检查出来
- 缺点:写法复杂
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';
/**
* 非空对象?nonono,是因为 typeof null === 'object' 所以要把 null 剔掉
*/
const isObject = (val) => val !== null && typeof val === 'object';
const isPromise = (val) => {
return isObject(val) && isFunction(val.then) && isFunction(val.catch);
};
/**
* 为啥要 call 一下?这样写不行么?
* toTypeString = (value) => Object.prototype.toString(value)
* 不行,因为 toString方法是Object的原型链方法,这么写的结果就是 啥啥都是 object了
*/
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);
};
/**
* isObject 和 isPlainObject 有啥区别?
*
* isObject 使用的是 typeof 但是无法区分 object、array、null、date等
* isPlainObject 只识别 纯对象
*
*/
const isPlainObject = (val) => toTypeString(val) === '[object Object]';
12、isIntegerKey 判断是否是数字型的字符串key值
- 字符串类型、不为NaN、不是负数、且为十进制
- 最后一个判断 用了 '' + parseInt(key, 10) 是为了把数字转为 字符串
const isIntegerKey = (key) => isString(key) &&
key !== 'NaN' &&
key[0] !== '-' &&
'' + parseInt(key, 10) === key;
13、makeMap & isReservedProp 保留属性
- 如果让我实现我大概会写成
- const isReservedProp = (val, expectsLowerCase) => {
- const keys = ['key','ref','onVnodeBeforeMount', 'onVnodeMounted']
- return expectsLowerCase ? keys.include(val.toLowerCase()) : keys.include(val)
- }
- 问题:可能会重复、每次都要遍历数组查询性能差、无可复用性
const isReservedProp = /*#__PURE__*/ makeMap(
// the leading comma is intentional so empty string "" is also included
',key,ref,' +
'onVnodeBeforeMount,onVnodeMounted,' +
'onVnodeBeforeUpdate,onVnodeUpdated,' +
'onVnodeBeforeUnmount,onVnodeUnmounted');
14、cacheStringFunction 缓存
const cacheStringFunction = (fn) => {
// 创建 一个空的 cache 对象
const cache = Object.create(null);
// 返回 一个函数 这个函数接受 一个参数,并 返回一个 布尔值,表示 将传入的参数缓存起来,并告知 用户 缓存结果(成功 or 失败)
return ((str) => {
const hit = cache[str];
// debugger
return hit || (cache[str] = fn(str)); // 没存的话存一下 & 返回 true,存过了的话 直接返回 true
});
};
// 上述函数的调用例子:
// const fn1 = x => x + 9 // 可以理解为 fn1(x) = x + 9
// const cache1 = cacheStringFunction(fn1) // 声明一个 cache1 的 变换函数
// cache1(1) // 存储的cache 为 { 1: 10 },返回 true
// cache1(2) // 存储的cache 为 { 1: 10, 2: 11 },返回 true
// const fn2 = x => x * 2 // 可以理解为 fn2(x) = x * 2
// const cache2 = cacheStringFunction(fn2) // 声明一个 cache2 的 变换函数
// cache2(1) // 存储的cache 为 { 1: 2 },返回 true
// cache2(2) // 存储的cache 为 { 1: 2, 2: 4 },返回 true
/**
* \w: 匹配包括下划线的任何单词字符。等价于“[A-Za-z0-9_]”。
* -(pattern): 匹配pattern并获取这一匹配。且 pattern 前有 “-”
* a-bbb-ccccc // 共找到 5 处匹配:-b -c
*/
const camelizeRE = /-(\w)/g;
/** camelize('a-bbb-ccccc') => 'aBbbCcccc'
* @private
* str.replace(regexp|substr, newSubStr|function)
* repalce 的第一个参数是 regexp, 第二个参数是 function,函数的返回值作为替换字符串(由于是全局匹配模式,这个方法每次匹配都会被调用)
* function 的两个参数,第一个参数是匹配模式 -(\w) 的字符串,第二个参数是与模式中的子表达式(\w)匹配的字符串
*/
const camelize = cacheStringFunction((str) => {
return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''));
});
/**
* \B 匹配非单词边界。“er\B”能匹配“verb”中的“er”,但不能匹配“never”中的“er”。
* [A-Z] 字符范围。匹配指定范围内的任意字符。
* ABbCccDdE // 共找到 4 处匹配:B C D E
*/
const hyphenateRE = /\B([A-Z])/g;
/** hyphenate('ABbCccDdE') => a-bb-ccc-dd-e
* @private
* str.replace(regexp|substr, newSubStr|function)
* repalce 的第一个参数是 regexp, 第二个参数是 newSubStr, $n 为变量名(n为匹配到的字符串的索引)。
* 将所有匹配到的值添加前面加上短横线’-’:ABbCccDdE => A-Bb-Ccc-Dd-E
* 再将整体 str toLowerCase: A-Bb-Ccc-Dd-E => a-bb-ccc-dd-e
*/
const hyphenate = cacheStringFunction((str) => str.replace(hyphenateRE, '-$1').toLowerCase());
/** capitalize('aaaa') => Aaaa
* @private
* charAt() 方法从一个字符串中返回指定的字符
* str.slice(beginIndex[, endIndex]) 提取某个字符串的一部分,并返回一个新的字符串,且不会改动原字符串
*/
const capitalize = cacheStringFunction((str) => str.charAt(0).toUpperCase() + str.slice(1));
/** toHandlerKey('aaaa') => onAaaa
* @private
*/
const toHandlerKey = cacheStringFunction((str) => str ? `on${capitalize(str)}` : ``);
15、hasChanged 判断是不是有变化
/**
* Object.is(value1, value2); // 返回一个 Boolean 类型标示两个参数是否是同一个值。
* 之前的写法:
* const hasChanged = (value, oldValue) => value !== oldValue && (value === value || oldValue === oldValue);
* 不得不提的NaN:(value === value || oldValue === oldValue)
* 为什么会有这句 因为要判断 NaN。js中认为 NaN 是不变的,所以 NaN === NaN 为 false
* hasChanged(NaN, NaN); // false
* 不得不说,新的api 理解成本下降很多。。。
*/
// compare whether a value has changed, accounting for NaN.
const hasChanged = (value, oldValue) => !Object.is(value, oldValue);
16、invokeArrayFns 执行数组里的函数
/**
* 这有啥用?怎会会有 要 传入一个函数列表,拿同样的参数执行一遍的?
* 为什么这样写?我们一般都是一个函数执行就行。数组中存放函数,函数其实也算是数据。这种写法方便统一执行多个函数。
*/
const invokeArrayFns = (fns, arg) => {
for (let i = 0; i < fns.length; i++) {
fns[i](arg);
}
};
17、def 定义对象属性
/**
* Object.defineProperty(obj, prop, descriptor)
* 直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
*/
const def = (obj, key, value) => {
Object.defineProperty(obj, key, {
configurable: true,
enumerable: false,
value
});
};
18、toNumber 转数字
/**
* parseFloat(string)
* parseFloat() 函数解析一个参数(必要时先转换为字符串)并返回一个浮点数。
* isNaN 本意 是判断是不是 NaN 值,但是不准确的。使用 Number.isNaN('a') 更准确。
*/
const toNumber = (val) => {
const n = parseFloat(val);
return isNaN(n) ? val : n;
};
19、getGlobalThis 全局对象
/**
* 会全局缓存一个 _globalThis,避免重复判断
* 首次判断优先级:globalThis >> self >> window >> global >> {}
* 为啥最后还有个空对象??如果都不存在,使用空对象。可能是微信小程序环境下。
*
* 其他:
* self:在 Web Worker 中不能访问到 window 对象,但是我们却能通过 self 访问到 Worker 环境中的全局对象。
* global:Node环境下,使用global。
* this:为什么不是 this? 在松散模式下,可以在函数中返回 this 来获取全局对象,但是在严格模式和模块环境下,this 会返回 undefined。
* globalThis:
* - globalThis 提供了一个标准的方式来获取不同环境下的全局 this 对象(也就是全局对象自身)。
* - 不像 window 或者 self 这些属性,它确保可以在有无窗口的各种环境下正常工作。
* - 你可以安心的使用 globalThis,不必担心它的运行环境。
* - 为便于记忆,你只需要记住,全局作用域中的 this 就是 globalThis。
*/
let _globalThis;
const getGlobalThis = () => {
return (_globalThis ||
(_globalThis =
typeof globalThis !== 'undefined'
? globalThis
: typeof self !== 'undefined'
? self
: typeof window !== 'undefined'
? window
: typeof global !== 'undefined'
? global
: {}));
};