学习目标
熟悉 vue 各种工具函数
思考如何为我所用
工具函数
1.1 EMPTY_OBJ 空对象
Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改,冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改已有属性的值,不能修改对象的原型。
作用:对于纯展示数据中,使用 Object.freeze 能有效提升性能。vue源码中对冻结的对象,不会做 getter 和 setter 的转换( **observer **),即无响应式。当然也可以在 created 中定义,vue 也不会对对象进行响应式处理。
const EMPTY_OBJ = (process.env.NODE_ENV !== 'production')
? Object.freeze({})
: {};
// 1、冻结对象是浅冻结。如果需要深冻结,需要递归处理
let freeze1 = Object.freeze({
name: '堆'
});
freeze1.name = '栈'
console.log(freeze1.name); // '堆'
let freeze1_1 = Object.freeze({
obj: {
name: '堆'
}
});
freeze1_1.obj.name = '栈'
console.log(freeze1_1.obj.name); // '栈'
// 2、返回和传入的参数相同的对象
let freeze2 = {
name: '堆'
}
let freeze3 = Object.freeze(freeze2);
console.log(freeze3 === freeze2); // true
// 3、冻结的是指,对象可重新赋值,堆指针重新分配引用
let freeze3 = {
name: '堆'
}
let freeze4 = Object.freeze(freeze3);
console.log(freeze3 == freeze4); // true
freeze3 = {
name: '栈'
}
console.log(freeze3 == freeze4); // false
// 4、被冻结后,对象的Property发生变化
let freeze5 = {
name: '堆'
}
Object.freeze(freeze5)
let freeze6 = Object.getOwnPropertyDescriptor(freeze5, 'name')
console.log(freeze6)
// {value:"堆", writable:false, enumerable:true, configurable:false}
1.2 EMPTY_OBJ 空数组
const EMPTY_ARR = (process.env.NODE_ENV !== 'production') ? Object.freeze([]) : [];
// 1、数组和对象一样,都是引用类型
const freeze1 = Object.freeze([])
freeze1.push(1) // 报错
// 2、无法修改
const freeze2 = Object.freeze([])
freeze2.length = 3;
console.log(freeze2.length); // 0
1.3 NOOP 空函数
export const NOOP = () => {}
// NOOP: No Operation的缩写,意为无操作。在各种语言、框架中都有定义这样的空函数。
// 例:jQuery.noop()。
// 作用:1.提高阅读性,给一些函数提供默认值。2.方便压缩(匿名函数无法被压缩)
1.4 NO 返回false函数
/**
* Always return false.
*/
export const NO = () => false
// 作用:1.提高阅读性,给一些Boolean参数提供默认值。
1.5 isOn 判断字符串是否为on开头,而且on后面首字母不是小写
const onRE = /^on[^a-z]/
const isOn = (key) => onRE.test(key);
// ^匹配字符串的开始位置,但是在方括号表达式中,表示非
// [a-z]表示一个区间,匹配所有的小写字母
// [^a-z]表示一个区间,匹配所有非小写字母
// ^[a-z]表示一个区间,匹配以小写字母开头
isOn('onClick') // true
isOn('onclick') // false
1.6 isModelListener 判断字符串是否为onUpdate:开头
const isModelListener = (key) => key.startsWith('onUpdate:');
// 判断字符串以指定前缀开始, start: 查找的开始位置
// string.startsWith(searchStr, start)
'onUpdate:'.startsWith('onUpdate:') // true
'conUpdate:'.startsWith('onUpdate:') // false
'conUpdate:'.startsWith('onUpdate:', 1) // true
// startsWith实现原理
string.substring(position, position + searchStr.length) === searchStr;
'conUpdate:'.substring(1, 1 + 'onUpdate:'.length) == 'onUpdate:' // true
// 判断字符串以指定前缀开始, position: 字符串结尾处
// string.endsWith(searchStr, position)
'onUpdate:'.endsWith('onUpdate:') // true
'onUpdate:'.endsWith('date', 8) // true
// endsWith实现原理:
string.substr(position - searchStr.length, searchStr.length) === searchStr)
'onUpdate:'.substr(8 - 'date'.length, 'date'.length) == 'date' // true
1.7 extend 对象合并
export const extend = Object.assign
// Object.assign(target, source1, source2)
// 将对象source 属性复制到 target 上, 并返回target
const target = { a: 1, b: 1};
const source1 = { b: 2, c: 2};
const source2 = { c: 3};
extend(target, source1, source2) // {a:1, b:2, c:3}
// 注意:Object.assign是浅拷贝
const target = { a: 1, b: 2};
const source1 = { c: {d: 1}};
extend(target, source1) // {a:1, b:2, c:{d:1}}
source1.c.d = 100
console.log(target) // {a:1, b:2, c:{d:100}}
1.8 remove 数组移除指定的一项
const remove = (arr, el) => {
const i = arr.indexOf(el);
if (i > -1) {
arr.splice(i, 1);
}
};
const arr = [1,2,3]
remove(arr, 3)
console.log(arr) // [1, 2]
1.9 hasOwn 检测一个属性是否为自己的自有属性
const hasOwnProperty = Object.prototype.hasOwnProperty;
const hasOwn = (val, key) => hasOwnProperty.call(val, key);
const A = {
A1: {
A2: {
name: 1
}
}
};
console.log(hasOwn(A, 'A1')); // true
console.log(hasOwn(A, 'A2')); // false
console.log(hasOwn(A.A1, 'A2')); // true
console.log(hasOwn(A.A1.A2, 'name')); // true
// 本身自有属性,无法检查原型链中是否包含某个属性
var B = {
__proto__: { c: 1 }
}
console.log(hasOwn(B, 'c')); // false
console.log(hasOwn(B.__proto__, 'c')); // true
1.10 isArray 判断是否为数组
const isArray = Array.isArray;
// 如果参数是数据,返回true,否则返回false。
console.log(isArray([])) // true
// Array.prototype 也是数组
Array.isArray(Array.prototype); // true
// 原型对象指向数组原型的对象能被分辨
Array.isArray({ __proto__: Array.prototype }); // false
// 对比: instanceof,判断右边参数的原型是否在左边参数的原型链上
// 不能判断 Array.prototype
Array.prototype.__proto__ === Object.prototype // true
Array.prototype instanceof Array // false
// 可以通过原型链进行误导
{ __proto__: Array.prototype } instanceof Array // true
1.11 isMap 判断是否为Map对象
const objectToString = Object.prototype.toString;
const toTypeString = (value) => objectToString.call(value);
const isMap = (val) => toTypeString(val) === '[object Map]';
// map es6新增的数据结构,相对比Object结构,
// 1、map能接受非字符串key
// 2、map键值是有序的,object无序的
// 3、map是可以迭代的,object需要获取key进行迭代
// 4、map不会修改原型链的键名,Object原型链有可能和对象上的键名冲突
const map = new Map([['first', '1'], ['second', '2']])
map.set("key", 'key')
console.log(map.get('key')); // key
console.log(map.has('key')); // true
console.log(map.has('first')); // true
console.log(isMap(map)); // true
// 属性:size set get has delete clear
// 方法:keys values entries forEach
es6语法可以看阮一峰的ECMAScript 6 入门
1.12 isSet 判断是否为set对象
const isSet = (val) => toTypeString(val) === '[object Set]';
// 例
const s = new Set();
isSet(s); // true
// 类似于数组,但是成员的值都是唯一的,没有重复的值。
// 数组去重方法1
const s = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
console.log([...s]) // 1,2,3,4,5
console.log(s.size) // 5
// 数组去重方法2
const s = Array.from(new Set([1, 2, 3, 4, 5, 5, 5, 5]))
console.log([...s]) // 1,2,3,4,5
console.log(s.size) // 5
// 字符串去重
const s = new Set('abccc');
console.log([...s]) // ['a', 'b', 'c']
console.log(s.size) // 3
// 属性:size
// 方法:add delete has clear
1.13 isDate 判断是否为Date对象
const isDate = (val) => val instanceof Date;
// instanceof,判断右边参数的原型是否在左边参数的原型链上
isDate(new Date()); // true
isDate({__proto__: new Date()}); // true
1.14 isFunction 判断是否为函数对象
const isFunction = (val) => typeof val === 'function';
const fun = function(){}
console.log(isFunction(fun)) // true
// 其他方法:不能判断class函数
// Object.prototype.toString.call(fn)=== '[object Function]'
1.15 isString 判断是否为字符串
const isString = (val) => typeof val === 'string';
// 例
const str = '1'
isString(str) // true
// 其他方法
// Object.prototype.toString.call(str) == "[object String]"
// str.constructor === String
1.16 isSymbol 判断是否为symbol对象
const isSymbol = (val) => typeof val === 'symbol';
// 例
const symbol = Symbol()
isSymbol(symbol) // true
// 其他方法
// Object.prototype.toString.call(Symbol()) == '[object Symbol]'
// symbol.constructor === Symbol
1.17 isObject 判断是否为对象
const isObject = (val) => val !== null && typeof val === 'object';
// 例
const obj = {}
isObject(obj) // true
// 不能辨别数组对象
const arr = []
isObject(arr) // true
// 其他方法
// Object.prototype.toString.call(obj) == '[object Object]'
// obj.constructor === Object
1.18 isPromise 判断是否为Promise
const isPromise = (val) => {
return isObject(val) && isFunction(val.then) && isFunction(val.catch);
};
// 例
const promise = new Promise(()=>{})
isPromise(promise) // true
1.19 objectToString 获取原型对象toString
const objectToString = Object.prototype.toString;
// 例: 用于判断类型
console.log(objectToString) // ƒ toString() { [native code] }
1.20 toTypeString 获取值类型的方法
const toTypeString = (value) => objectToString.call(value);
// 例: 用于判断类型
toTypeString({}) // '[object Object]'
toTypeString(null) // '[object Null]'
toTypeString(undefined) // '[object Undefined]'
1.21 toRawType 获取原始值类型的方法
const toRawType = (value) => {
// extract "RawType" from strings like "[object RawType]"
return toTypeString(value).slice(8, -1);
};
// 例
toRawType({}) // 'Object'
toRawType('') // 'String'
1.22 isPlainObject 是否为纯普通对象
const isPlainObject = (val) => toTypeString(val) === '[object Object]';
# 例
isPlainObject({}) // true
isPlainObject([]) // false
1.23 isIntegerKey 是否为整数数字字符串
const isIntegerKey = (key) => isString(key) &&
key !== 'NaN' &&
key[0] !== '-' &&
'' + parseInt(key, 10) === key;
// 例
isIntegerKey(1) // false
isIntegerKey('1') // true
1.24 isReservedProp 判断字符串是否为map键名称
/**
* 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];
}
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('KEY') // false
1.25 cacheStringFunction 闭包缓存数据
const cacheStringFunction = (fn) => {
const cache = Object.create(null);
return ((str) => {
const hit = cache[str];
return hit || (cache[str] = fn(str));
});
};
1.26 camelize 连字符转小写驼峰
const camelizeRE = /-(\w)/g;
// 开头为-,\w 代表 0-9 a-z A-Z。如:on-click v-if
const camelize = cacheStringFunction((str) => {
return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''));
});
camelize('on-click') // 'onClick'
1.27 hyphenate 小写驼峰转连字符
const hyphenateRE = /\B([A-Z])/g;
// \B 匹配非单词边界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。
// \b 匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。
// 单词边界就是单词和符号之间的边界,单词是中文字符,英文字符,数字;符号可以是中文符号, 英文符号,空格,制表符,换行。
'edg yyds'.replace(/ yyds\b/g, '') // 'edg'
'edg yyds'.replace(/\bedg /g, '') // 'yyds'
'a b c'.replace(/\b[a-z]/g, (w) => w.toLocaleUpperCase()) // 'A B C'
const hyphenate = cacheStringFunction((str) => str.replace(hyphenateRE, '-$1').toLowerCase());
// -$1: $1 匹配的文本,-$1 在匹配的文本前加-
hyphenate('onClick') // 'on-click'
1.28 capitalize 首字母转大写
const capitalize = cacheStringFunction((str) => str.charAt(0).toUpperCase() + str.slice(1));
capitalize('onClick') // 'Onclick'
1.29 toHandlerKey 首字母转大写并加上on前缀
const toHandlerKey = cacheStringFunction((str) => str ? `on${capitalize(str)}` : ``);
toHandlerKey('click') // 'onClick'
1.30 hasChanged 比较值是否有变化
// compare whether a value has changed, accounting for NaN.
const hasChanged = (value, oldValue) => !Object.is(value, oldValue);
// 例
var edg = {a: 1}; // yyds = edg;
hasChanged(edg, yyds) // false
var yyds = {b: 1}
hasChanged(edg, yyds) // true
1.31 invokeArrayFns 执行数组中的函数
const invokeArrayFns = (fns, arg) => {
for (let i = 0; i < fns.length; i++) {
fns[i](arg);
}
};
1.32 def 对对象定义属性及通过重新定义赋值
const def = (obj, key, value) => {
Object.defineProperty(obj, key, {
configurable: true, // 配置描述符:可删除对象属性及属性重新定义赋值
enumerable: false, // 是否出现在for in 或者 Object.keys()的遍历中
value
});
};
// 例如
var person = {}
def(person, 'name', 'jack') // person.name ==> jack
person.name = 'rose' // person.name ==> jack
def(person, 'name', 'rose') // person.name ==> rose
// Object.defineProperty(obj, prop, desc)
// obj 当前对象 prop 属性名称 desc 属性描述(value,writable,enumerable,configurable,get,setter)
// 两种形式定义
let obj = {}
obj.name = 'jack'
// 等价于
Object.defineProperty(obj, 'name', {
configurable: true,
enumerable: true,
writable: true,
value: 'jack'
});
Object.defineProperty(obj, 'name', {
value: 'jack'
});
// 等价于
Object.defineProperty(obj, 'name', {
configurable: false,
enumerable: false,
writable: false,
value: 'jack'
});
1.33 toNumber 转数字
const toNumber = (val) => {
const n = parseFloat(val);
return isNaN(n) ? val : n;
};
// parseFloat() 函数可解析一个字符串,并返回一个浮点数。
// 该函数指定字符串中的首个字符是否是数字。如果是,则对字符串进行解析,直到到达数字的末端为止,然后以数字返回该数字,而不是作为字符串。
toNumber('rose') // 'rose'
toNumber('1') // 1
toNumber('1rose') // 1
toNumber('rose1') // 'rose1'
// isNaN判断传入的参数是否能转换成Number类型,如果失败返回false,并不是严格的判断是否等于NaN。
// Number.isNaN判断传入的参数是否等于NaN
isNaN('rose') // true
Number.isNaN('rose') // false
1.34 getGlobalThis 获取全局this指向
let _globalThis;
const getGlobalThis = () => {
return (_globalThis ||
(_globalThis =
typeof globalThis !== 'undefined'
? globalThis
: typeof self !== 'undefined'
? self
: typeof window !== 'undefined'
? window
: typeof global !== 'undefined'
? global
: {}));
};
// this 即表示作用域的执行上下文self
// window web的全局对象
// self 也可以获取web全局对象
// global node 环境下全局对象
总结
简简单单的工具函数,也收获蛮多,复习了基础知识、学习了如何调试代码,抱着大佬的大腿受益良多。源码共读群是很适合学习的地方,有大佬的指引,还有志同道合的伙伴。万丈高楼平地起。