# vue3.x 源码摸索
- vnode 到真实 DOM
- vue3.x 中的工具函数
- EMPTY_OBJ 空对象
- EMPTY_ARR 空数组
- NOOP 空函数
- NO 永远返回 false 的函数
- 判断字符串是不是 on 开头,并且 on 后首字母是大写字母
- 判断是否是 isModelListener 监听器
- extend 继承合并
- remove 移除数组的一项
- hasOwn 是不是自己本身所拥有的属性
- isArray 判断数组
- isMap 判断是不是 Map 对象
- isSet 判断是不是 Set 对象
- isDate 判断是不是 Date 对象
- isFunction 判断是不是函数
- isString 判断是不是字符串
- isSymbol 判断是不是 Symbol
- isObject 判断是不是对象
- isPromise 判断是不是 Promise
- objectToString 对象转字符串
- toTypeString 对象转字符串
- toRawType 对象转字符串,截取后几位
- isPlainObject 判断是不是纯粹的对象
- isIntegerKey 判断是不是数字型的字符串 key 值
- makeMap && isReservedProp
- cacheStringFunction 缓存
- 连字符 - 转驼峰 on-click => onClick
- 驼峰 转 连字符 - onClick => on-click
- 首字母转大写
- click => onClick
- hasChanged 判断是不是有变化
- invokeArrayFns 执行数组里的函数
- def 定义对象属性
- toNumber 转数字
- getGlobalThis 全局对象
- 参考
# vnode 到真实 DOM
整个过程的思维导图(超级详细包含代码)
在线查看-processon 思维导图 (opens new window)
# vue3.x 中的工具函数
# EMPTY_OBJ 空对象
const EMPTY_OBJ = process.env.NODE_ENV !== 'production' ? Object.freeze({}) : {}
1
开发环境需要报错信息,冻结对象
生产环境不需要报错信息,不冻结对象
# EMPTY_ARR 空数组
const EMPTY_ARR = process.env.NODE_ENV !== 'production' ? Object.freeze([]) : []
1
# NOOP 空函数
const NOOP = () => {}
1
- 方便判断
- 方便压缩
# NO 永远返回 false 的函数
const NO = () => false
1
方便压缩
# 判断字符串是不是 on 开头,并且 on 后首字母是大写字母
const onRE = /^on[^a-z]/
const isOn = (key) => onRE.test(key)
1
2
2
# 判断是否是 isModelListener 监听器
判断是不是'onUpdate:'开头既可
const isModelListener = (key) => key.startsWith('onUpdate:')
1
# extend 继承合并
const extend = Object.assign
1
# remove 移除数组的一项
const remove = (arr, el) => {
const i = arr.indexOf(el)
if (i > -1) {
arr.splice(i, 1)
}
}
1
2
3
4
5
6
2
3
4
5
6
splice 其实是一个很耗性能的方法。删除数组中的一项,其他元素都要移动位置。
引申:axios InterceptorManager 拦截器源码 中,拦截器用数组存储的。
但实际移除拦截器时,只是把拦截器置为 null 。而不是用 splice 移除。
最后执行时为 null 的不执行,同样效果。
axios 拦截器这个场景下,不得不说为性能做到了很好的考虑。
# hasOwn 是不是自己本身所拥有的属性
const hasOwnProperty = Object.prototype.hasOwnProperty
const hasOwn = (val, key) => hasOwnProperty.call(val, key)
1
2
2
# isArray 判断数组
const isArray = Array.isArray
1
# isMap 判断是不是 Map 对象
const isMap = (val) => toTypeString(val) === '[object Map]'
1
# isSet 判断是不是 Set 对象
const isSet = (val) => toTypeString(val) === '[object Set]'
1
# isDate 判断是不是 Date 对象
const isDate = (val) => val instanceof Date
1
# isFunction 判断是不是函数
const isFunction = (val) => typeof val === 'function'
1
# isString 判断是不是字符串
const isString = (val) => typeof val === 'string'
1
# isSymbol 判断是不是 Symbol
const isSymbol = (val) => typeof val === 'symbol'
1
# isObject 判断是不是对象
const isObject = (val) => val !== null && typeof val === 'object'
1
# isPromise 判断是不是 Promise
const isPromise = (val) =>
isObject(val) && isFunction(val.then) && isFunction(val.catch)
1
2
2
# objectToString 对象转字符串
const objectToString = Object.prototype.toString
1
# toTypeString 对象转字符串
const toTypeString = (val) => objectToString.call(val)
1
# toRawType 对象转字符串,截取后几位
const toRawType = (val) => toTypeString(val).slice(8, -1)
1
# isPlainObject 判断是不是纯粹的对象
const isPlainObject = (val) => toTypeString(val) === '[object Object]'
1
# isIntegerKey 判断是不是数字型的字符串 key 值
const isIntegerKey = (val) =>
isString(key) &&
key !== 'NaN' &&
key[0] !== '-' &&
key === '' + parseInt(key, 10)
1
2
3
4
5
2
3
4
5
# makeMap && isReservedProp
判断一个属性是否为保留属性
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]
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# cacheStringFunction 缓存
const cacheStringFunction = (fn) => {
const cache = Object.create(null)
return (str) => {
const hit = cache[str]
return hit || (cache[str] = fn(str))
}
}
1
2
3
4
5
6
7
2
3
4
5
6
7
# 连字符 - 转驼峰 on-click => onClick
const camelizeRE = /-(\w)/g
const camelize = cacheStringFunction((str) =>
str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''))
)
1
2
3
4
2
3
4
# 驼峰 转 连字符 - onClick => on-click
const hyphenateRE = /\B([A-Z])/g
const hyphenate = cacheStringFunction((str) =>
str.replace(hyphenateRE, '-$1').toLowerCase()
)
1
2
3
4
2
3
4
# 首字母转大写
const capitalize = cacheStringFunction(
(str) => str.charAt(0).toUpperCase() + str.slice(1)
)
1
2
3
2
3
# click => onClick
const toHandleKey = cacheStringFunction((str) =>
str ? `on${capitalize(str)}` : ''
)
1
2
3
2
3
# hasChanged 判断是不是有变化
const hasChanged = (val, oldVal) => !Object.is(val, oldVal)
1
# invokeArrayFns 执行数组里的函数
const invokeArrayFns = (fns, arg) => {
for (let i = 0; i < fns.length; i++) {
fns[i](arg)
}
}
1
2
3
4
5
2
3
4
5
# def 定义对象属性
const def = (obj, key, value) => {
Object.defineProperty(obj, key, {
configurable: true,
enumerable: false,
value
})
}
1
2
3
4
5
6
7
2
3
4
5
6
7
数据描述符(其中属性为:enumerable,configurable,value,writable)与存取描述符(其中属性为 enumerable,configurable,set(),get())之间是有互斥关系的。在定义了 set()和 get()之后,描述符会认为存取操作已被 定义了,其中再定义 value 和 writable 会引起错误。
# toNumber 转数字
const toNumer = (val) => {
const n = parseFloat(val)
return isNaN(n) ? val : n
}
1
2
3
4
2
3
4
# getGlobalThis 全局对象
let _globalThis
const getGlobalThis = () => {
return (
_globalThis ||
(_globalThis =
typeof globalThis !== 'undefined'
? globalThis
: typeof self !== 'undefined'
? self
: typeof window !== 'undefined'
? window
: typeof global !== 'undefined'
? global
: {})
)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 参考
初学者也能看懂的 Vue3 源码中那些实用的基础工具函数 (opens new window)
据说 99% 的人不知道 vue-devtools 还能直接打开对应组件文件?本文原理揭秘 (opens new window)