全面解析vue3源码函数工具库
本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
这是源码共读的第2期,链接:juejin.cn/post/708499…
hey🖐! 我是pino😊😊。一枚小透明,期待关注➕ 点赞,共同成长~
本系列会精读一些小而美的开源作品或者框架的核心源码。
其实经常看一下各个源码库中的工具函数对我们日常的开发工作是很有帮助的,因为这些函数基本上覆盖了我们工作中90%的场景,既能增强javascript基本功底,还能更快的进行开发,更好的进行摸鱼😂,而且一些优秀的代码风格也是非常值得我们学习的。
如果喜欢本篇文章,下面还有vue2的函数工具库以及axios的函数工具库🤪,卷起来吧!
对象冻结
const EMPTY_OBJ = __DEV__ ? Object.freeze({}) : {}
数组冻结对象
const EMPTY_ARRAY = __DEV__ ? Object.freeze([]) : []
NO
export const NO = () => false
NOOP
export const NOOP = () => {}
isOn
判断字符串是否以on
开头,并且on
后面第一个字符不为小写字母
const onRE = /^on[^a-z]/
const isOn = key => onRE.test(key)
isOn('onClick'); // true
isOn('onchange'); // false
isOn('on3click'); // true
isModelListener
isModelListener
监听器,判断是否为onUpdate:
开头的字符串
const isModelListener = key => key.startsWith('onUpdate:')
// 例子:
isModelListener('onUpdate:change'); // true
isModelListener('atUpdate:change'); // false
// startsWith 是 ES6 提供的方法
扩展
startsWith
是ES6中新增的字符串方法,用于匹配字符串开头的字符,返回布尔值,表示参数字符串是否在原字符串的头部。
同样还新增了includes
和endsWith
方法:
includes()
:返回布尔值,表示是否找到了参数字符串。startsWith()
:返回布尔值,表示参数字符串是否在原字符串的头部。endsWith()
:返回布尔值,表示参数字符串是否在原字符串的尾部。
let s = 'Hello pino!';
s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true
extend
合并,用于合并两个对象,此处vue3中只是将Object.assign
重新赋值
const extend = Object.assign
let obj = {name:'pino', age:'18'}
let data = extend({name: '小黄瓜'}, obj)
console.log(data) // {name: 'pino', age: '18'}
Object.assign
这个方法具有覆盖的特性,也就是说后面的对象如果拥有与第一个对象相同的属性,那么后面对象中的同名属性将会覆盖第一个对象中的同名属性。
这一点也可以从上面的例子中看出来。
remove
remove
方法实现了删除元素的功能,其实就是将indexOf
(查找)和splice
方法封装了起来
const remove = function(array, item) {
// 查找下标
let index = array.indexOf(item)
if(index > -1) {
// 删除元素
array.splice(index, 1)
}
}
let array = [1, 2, 3, 4]
remove(array, 2) // [1, 3, 4]
hasOwn
hasOwn
方法用于判断属性是否为自身所拥有的
// 重新定义js原生的hasOwnProperty方法
const hasOwnProperty = Object.prototype.hasOwnProperty
// 封装函数
const hasOwn = (val,key) => hasOwnProperty.call(val, key)
js对象中的属性是可以通过__proto__
继承原型对象中的属性进行使用,而Object.prototype.hasOwnProperty
就是为了区分一个属性是否为自身所拥有的属性。
const obj = {};
obj.name = 'pino';
obj.hasOwnProperty('name') // true
// toString属性为继承而来
obj.hasOwnProperty('toString') // false
判断类型方法合集
定义方法
重新定义Object.prototype.toString
方法,用于下文中的判断类型:
当时用Object.prototype.toString
方法时,会返回一个对象类型的字符串,例如:[object Object]
const objectToString = Object.prototype.toString
const toTypeString = value => objectToString.call(value)
下文中的toTypeString
函数皆为此函数。
- isMap(判读是否为Map类型)
const isMap = val => toTypeString(val) === '[object Map]'
- isSet(判断是否为Set类型)
const isSet = val => toTypeString(val) === '[object Set]'
- isDate(判断是否为时间对象)
const isDate = val => toTypeString(val) === '[object Date]'
- isFunction(判断是否为函数类型)
const isFunction = val => typeof val === 'function'
- isString(判断字符串类型)
const isString = val => typeof val === 'string'
- isSymbol(判断是否为Symbol类型)
const isSymbol = val => typeof val === 'symbol'
- isObject(判断是否为对象类型)
const isObject = val => val !== null && typeof val === 'object'
- isPlainObject(判断是否为纯对象)
const isPlainObject = val => toTypeString(val) === '[object Object]'
- isPromise(判断是否为Promise)
Promise
对象中存在两个函数then
和catch
const isPromise = val => {
return isObject(val) && isFunction(val.then) && isFunction(val.catch)
}
toRawType
用于处理使用Object.prototype.toString
方法返回的字符串,例如
[object Object] --> Object
const toRawType = value => {
return toTypeString(value).slice(8, -1)
}
isIntegerKey
判断是不是数字型的字符串key
值
它需要满足以下几个条件:
- key的类型为string
- key不为NaN
- key不能以'-'开头
- key必须为二进制
export const isIntegerKey = key =>
isString(key) &&
key !== 'NaN' &&
key[0] !== '-' &&
'' + parseInt(key, 10) === key
isIntegerKey('a'); // false
isIntegerKey('1'); // true
isIntegerKey('011'); // false
isIntegerKey('11'); // true
扩展
parseInt
可以传递第二个参数,用于规定进制数。
parseInt('010',10) // 10
makeMap & isReservedProp
传入一个以逗号分隔的字符串,生成一个对象,并且返回一个函数检测key
值在不在这个对象中。
makeMap
第二个参数为是否大写选项
function makeMap(str,expectsLowerCase) {
// 创建一个对象,使用create创建指原型链指向null
const map = Object.create(null)
const list = str.split(',')
// 遍历字符串数组,以字符串为key,true为value,设置对象
for (let i = 0; i < list.length; i++) {
map[list[i]] = true
}
// 根据传入expectsLowerCase的值(true/false),返回相应的函数
return expectsLowerCase ? val => !!map[val.toLowerCase()] : val => !!map[val]
}
// 调用makeMap函数,返回一个判断key值的函数
const isReservedProp = makeMap(
',key,ref,ref_for,ref_key,' +
'onVnodeBeforeMount,onVnodeMounted,' +
'onVnodeBeforeUpdate,onVnodeUpdated,' +
'onVnodeBeforeUnmount,onVnodeUnmounted'
)
isReservedProp('key'); // true
isReservedProp('ref'); // true
isReservedProp('onVnodeBeforeMount'); // true
扩展
!!的作用是转换为boolean
值
在js中一个!是指取反操作,例如:
let a = 'hello pino'
console.log(!a) // false
当 a 是 0 -0 NaN false null ''(空字符串) undefined 时,!a 是 true
var b = 'hello pino'
console.log(!!b) // true
在 ! 的基础上再次取反(!),其实相当于只是强转为 boolean
等价于 Boolean(b)
,写法上更简洁
结合上文,当 b 不是 0 -0 NaN false null ''(空字符串) undefined 时,!!b 是 true
cacheStringFunction(缓存函数)
创建一个空对象,返回一个数组,当执行返回函数的时候,首先使用传入的参数来判断是否已经存在于对象中,如果已经存在,则直接返回value
值(也就是结果),如果没有找到,那么执行函数,将参数为key
,执行结果为value
,保存到对象中。
const cacheStringFunction = function(fn) {
// 创建空对象
const cache= Object.create(null)
// 返回一个函数
return (str => {
const hit = cache[str]
// 返回值/保存执行结果
return hit || (cache[str] = fn(str))
})
}
以下几个函数均使用了缓存函数的用法:
camelize
将连字符转化为转驼峰 'on-click' => 'onClick'
// 匹配-及后面的字符串
const camelizeRE = /-(\w)/g
const camelize = cacheStringFunction(str => {
return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''))
})
hyphenate
将小驼峰转化为连字符字符串 'onClick' => 'on-click'
const hyphenateRE = /\B([A-Z])/g
const hyphenate = cacheStringFunction((str: string) =>
str.replace(hyphenateRE, '-$1').toLowerCase()
)
// 举例:onClick => on-click
const a = hyphenate('onClick');
console.log(a); // 'on-click'
capitalize(字符串首字母大写)
用于将字符串首字母转化为大写 'click' => 'Click'
const capitalize = cacheStringFunction(
(str: string) => str.charAt(0).toUpperCase() + str.slice(1)
)
capitalize('click') // Click
toHandlerKey
将字符串转化为以on开头的小驼峰 'click' => 'onClick'
const toHandlerKey = str => str ? `on${capitalize(str)}` : ``
toHandlerKey('change') // onChange
hasChanged
用于判断两个值是否相等(是否发生改变),使用js原生的Object.is
方法,判断两个值是否相等
const hasChanged = (value, oldValue) =>
!Object.is(value, oldValue)
invokeArrayFns
用于执行一个函数数组,其中第二个参数是可选的,用于传递给正在执行的函数
const invokeArrayFns = (fns, arg) => {
// 遍历数组,执行函数
for (let i = 0; i < fns.length; i++) {
fns[i](arg)
}
}
例如:
const ary = [
function(val){
console.log(val + '是好人');
},
function(val){
console.log('爱吃瓜的' + val);
}
]
invokeArrayFns(ary, 'pino');
def
用于创建一个对象的默认属性
const def = (obj, key, value) => {
// 使用传入的calue值进行初始化
Object.defineProperty(obj, key, {
configurable: true,
enumerable: false,
value
})
}
对象的所有的属性在任何时候都可以被修改、插入、删除。在ES5中,我们可以设置属性是否可以被改变或是被删除,ES5中引入了属性描述符的概念,我们可以通过它对所定义的属性有更大的控制权。这些属性描述符(特性)包括:
- value——当试图获取属性时所返回的值。
- writable——该属性是否可写。
- enumerable——该属性在for in循环中是否会被枚举。
- configurable——该属性是否可被删除。
- set()——该属性的更新操作所调用的函数。
- get()——获取属性值时所调用的函数。
另外,数据描述符(其中属性为:enumerable
,configurable
,value
,writable
)与存取描述符(其中属性为enumerable
,configurable
,set()
,get()
)之间是有互斥关系的。在定义了set()
和get()
之后,描述符会认为存取操作已被 定义了,其中再定义value
和writable
会引起错误。
toNumber
将一个值转换为Number
类型,加入了对转换后NaN
的判断
const toNumber = val => {
const n = parseFloat(val);
// 如果为NaN,直接返回初始值
return isNaN(n) ? val : n;
};
toNumber('111'); // 111
toNumber('a111'); // 'a111'
parseFloat('a111'); // NaN
本文参考
写在最后 ⛳
未来可能会更新实现mini-vue3
和javascript
基础知识系列,希望能一直坚持下去,期待多多点赞🤗🤗,一起进步!🥳🥳
转载自:https://juejin.cn/post/7117191006423875597