Vue 源码学习 - shared 目录公共方法
shared目录中共有以下文件:
- codeframe.ts
- domAttrConfig.ts
- domTagConfig.ts
- escapeHtml.ts
- general.ts
- globalsAllowList.ts
- looseEqual.ts
- makeMap.ts
- normalizeProp.ts
- patchFlags.ts
- shapeFlags.ts
- slotFlags.ts
- toDisplayString.ts
- typeUtils.ts
内容很长,保持耐心。
makeMap.ts
文件
makeMap
函数
/*! #__NO_SIDE_EFFECTS__ */
export function makeMap(
str: string,
expectsLowerCase?: boolean,
): (key: string) => boolean {
const set = new Set(str.split(','))
return expectsLowerCase
? val => set.has(val.toLowerCase())
: val => set.has(val)
}
makeMap
函数用于根据传入的字符串创建一个 Set
对象,并返回一个用于检查给定的键是否在该集合中的函数。参数 expectsLowerCase
决定了在检查时是否将键转换为小写。使用 Set
而不是数组进行判断的优势在于:
- 查找操作平均时间复杂度为 O(1),效率更高,尤其在元素数量较多时。
- 元素具有唯一性,能自动去重。
looseEqual.ts
文件
looseCompareArrays
函数
function looseCompareArrays(a: any[], b: any[]) {
if (a.length!== b.length) return false
let equal = true
for (let i = 0; equal && i < a.length; i++) {
equal = looseEqual(a[i], b[i])
}
return equal
}
该函数用于对两个数组进行宽松比较,如果两个数组长度不同则直接返回 false
,否则逐个比较数组元素,只要有一个元素不相等就停止比较并返回 false
,全部相等则返回 true
。
looseEqual
函数
export function looseEqual(a: any, b: any): boolean {
if (a === b) return true
let aValidType = isDate(a)
let bValidType = isDate(b)
if (aValidType || bValidType) {
return aValidType && bValidType? a.getTime() === b.getTime() : false
}
aValidType = isSymbol(a)
bValidType = isSymbol(b)
if (aValidType || bValidType) {
return a === b
}
aValidType = isArray(a)
bValidType = isArray(b)
if (aValidType || bValidType) {
return aValidType && bValidType? looseCompareArrays(a, b) : false
}
aValidType = isObject(a)
bValidType = isObject(b)
if (aValidType || bValidType) {
/* istanbul ignore if: this if will probably never be called */
if (!aValidType ||!bValidType) {
return false
}
const aKeysCount = Object.keys(a).length
const bKeysCount = Object.keys(b).length
if (aKeysCount!== bKeysCount) {
return false
}
for (const key in a) {
const aHasKey = a.hasOwnProperty(key)
const bHasKey = b.hasOwnProperty(key)
if (
(aHasKey &&!bHasKey) ||
(!aHasKey && bHasKey) ||
!looseEqual(a[key], b[key])
) {
return false
}
}
}
return String(a) === String(b)
}
looseEqual
函数用于对两个值进行宽松的相等性比较。它会根据值的类型进行不同的处理,包括日期、符号、数组、对象等。对于对象,会比较属性的个数和属性值。
looseIndexOf
函数
export function looseIndexOf(arr: any[], val: any): number {
return arr.findIndex(item => looseEqual(item, val))
}
looseIndexOf
函数通过调用 looseEqual
函数来查找数组中与给定值宽松相等的元素的索引。
这些函数在处理不同类型的数据的相等性判断和数组操作中发挥了重要作用,为 Vue 框架的其他部分提供了基础的比较和查找功能。
globalsAllowList.ts
文件
isGloballyAllowed
函数
import { makeMap } from './makeMap'
const GLOBALS_ALLOWED =
'Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,' +
'decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,' +
'Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,console,Error,Symbol'
export const isGloballyAllowed: (key: string) => boolean =
/*#__PURE__*/ makeMap(GLOBALS_ALLOWED)
isGloballyAllowed
函数通过调用 makeMap
函数,基于预定义的全局允许的字符串列表 GLOBALS_ALLOWED
创建了一个用于判断给定字符串是否在允许列表中的函数。这有助于在特定的上下文中对全局相关的标识符进行有效和便捷的检查和控制。 咱不清楚此函数用在Vue3源码中的何处。
general.ts
文件
export const EMPTY_OBJ: { readonly [key: string]: any } = __DEV__
? Object.freeze({})
: {}
EMPTY_OBJ
:在开发环境(__DEV__
为真)时是一个冻结的空对象,在生产环境时是一个普通的空对象。
export const EMPTY_ARR: readonly never[] = __DEV__? Object.freeze([]) : []
EMPTY_ARR
:开发环境下是冻结的空数组,生产环境下是普通空数组。
export const NOOP = (): void => {}
NOOP
:这是一个空函数,不执行任何具体操作。
/**
* Always return false.
*/
export const NO = () => false
NO
:总是返回 false
的函数。
export const isOn = (key: string): boolean =>
key.charCodeAt(0) === 111 /* o */ &&
key.charCodeAt(1) === 110 /* n */ &&
// uppercase letter
(key.charCodeAt(2) > 122 || key.charCodeAt(2) < 97)
isOn
:根据输入字符串 key
的前几个字符的编码来判断是否符合特定条件,用于检测是否与某种特定的格式匹配。
export const isModelListener = (key: string): key is `onUpdate:${string}` =>
key.startsWith('onUpdate:')
isModelListener
:判断输入的字符串 key
是否以 'onUpdate:'
开头。
export const extend: typeof Object.assign = Object.assign
extend
:直接引用了 Object.assign
方法。
export const remove = <T>(arr: T[], el: T): void => {
const i = arr.indexOf(el)
if (i > -1) {
arr.splice(i, 1)
}
}
remove
:用于从给定的数组 arr
中移除指定的元素 el
。
const hasOwnProperty = Object.prototype.hasOwnProperty
export const hasOwn = (
val: object,
key: string | symbol,
): key is keyof typeof val => hasOwnProperty.call(val, key)
hasOwn
:基于 Object.prototype.hasOwnProperty
来判断对象 val
是否具有指定的属性 key
。
export const isArray: typeof Array.isArray = Array.isArray
isArray
:使用 Array.isArray
来判断一个值是否为数组。
export const isMap = (val: unknown): val is Map<any, any> =>
toTypeString(val) === '[object Map]'
isMap
:通过获取值的类型字符串并与 '[object Map]'
比较来判断是否为 Map
。
export const isSet = (val: unknown): val is Set<any> =>
toTypeString(val) === '[object Set]'
isSet
:通过类型字符串判断是否为 Set
。
export const isDate = (val: unknown): val is Date =>
toTypeString(val) === '[object Date]'
isDate
:根据类型字符串判断是否为 Date
类型。
export const isRegExp = (val: unknown): val is RegExp =>
toTypeString(val) === '[object RegExp]'
isRegExp
:通过类型字符串判断是否为正则表达式类型。
export const isFunction = (val: unknown): val is Function =>
typeof val === 'function'
isFunction
:通过 typeof
操作符判断是否为函数类型。
export const isString = (val: unknown): val is string => typeof val ==='string'
isString
:通过 typeof
判断是否为字符串类型。
export const isSymbol = (val: unknown): val is symbol => typeof val ==='symbol'
isSymbol
:通过 typeof
判断是否为符号类型。
export const isObject = (val: unknown): val is Record<any, any> =>
val!== null && typeof val === 'object'
isObject
:判断值不为 null
且类型为 object
。
export const isPromise = <T = any>(val: unknown): val is Promise<T> => {
return (
(isObject(val) || isFunction(val)) &&
isFunction((val as any).then) &&
isFunction((val as any).catch)
)
}
isPromise
:判断一个值是否为 Promise
,通过检查其类型以及是否具有 then
和 catch
方法。
export const objectToString: typeof Object.prototype.toString =
Object.prototype.toString
objectToString
:引用了 Object.prototype.toString
方法。
export const toTypeString = (value: unknown): string =>
objectToString.call(value)
toTypeString
:调用 objectToString.call
来获取输入值的类型字符串。
export const toRawType = (value: unknown): string => {
// extract "RawType" from strings like "[object RawType]"
return toTypeString(value).slice(8, -1)
}
toRawType
:从通过 toTypeString
获取的类型字符串中提取出原始类型部分。
export const isPlainObject = (val: unknown): val is object =>
toTypeString(val) === '[object Object]'
isPlainObject
:通过类型字符串判断是否为普通对象。
export const isIntegerKey = (key: unknown): boolean =>
isString(key) &&
key!== 'NaN' &&
key[0]!== '-' &&
'' + parseInt(key, 10) === key
isIntegerKey
:判断输入的键是否为整数格式的字符串。
export const isReservedProp: (key: string) => boolean = /*#__PURE__*/ makeMap(
// the leading comma is intentional so empty string "" is also included
',key,ref,ref_for,ref_key,' +
'onVnodeBeforeMount,onVnodeMounted,' +
'onVnodeBeforeUpdate,onVnodeUpdated,' +
'onVnodeBeforeUnmount,onVnodeUnmounted',
)
isReservedProp
:使用 makeMap
函数创建的用于判断字符串是否为预留属性的函数。
export const isBuiltInDirective: (key: string) => boolean =
/*#__PURE__*/ makeMap(
'bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text,memo',
)
isBuiltInDirective
:通过 makeMap
生成用于判断是否为内置指令的函数。
const cacheStringFunction = <T extends (str: string) => string>(fn: T): T => {
const cache: Record<string, string> = Object.create(null)
return ((str: string) => {
const hit = cache[str]
return hit || (cache[str] = fn(str))
}) as T
}
cacheStringFunction
:用于对字符串处理函数进行缓存优化。
const camelizeRE = /-(\w)/g
/**
* @private
*/
export const camelize: (str: string) => string = cacheStringFunction(
(str: string): string => {
return str.replace(camelizeRE, (_, c) => (c? c.toUpperCase() : ''))
},
)
camelize
:将连字符分隔的字符串转换为驼峰格式,并进行缓存优化。
const hyphenateRE = /\B([A-Z])/g
/**
* @private
*/
export const hyphenate: (str: string) => string = cacheStringFunction(
(str: string) => str.replace(hyphenateRE, '-$1').toLowerCase(),
)
hyphenate
:将驼峰格式字符串转换为连字符分隔的格式,并进行缓存优化。
/**
* @private
*/
export const capitalize: <T extends string>(str: T) => Capitalize<T> =
cacheStringFunction(<T extends string>(str: T) => {
return (str.charAt(0).toUpperCase() + str.slice(1)) as Capitalize<T>
})
capitalize
:将字符串的首字母大写,并进行缓存优化。
/**
* @private
*/
export const toHandlerKey: <T extends string>(
str: T,
) => T extends ''? '' : `on${Capitalize<T>}` = cacheStringFunction(
<T extends string>(str: T) => {
const s = str? `on${capitalize(str)}` : ''
return s as T extends ''? '' : `on${Capitalize<T>}`
},
)
toHandlerKey
:根据输入字符串生成特定的处理键,并进行缓存优化。
// compare whether a value has changed, accounting for NaN.
export const hasChanged = (value: any, oldValue: any): boolean =>
!Object.is(value, oldValue)
hasChanged
:比较两个值是否发生变化,考虑了 NaN
的情况。
export const invokeArrayFns = (fns: Function[],...arg: any[]): void => {
for (let i = 0; i < fns.length; i++) {
fns[i](...arg)
}
}
invokeArrayFns
:依次执行函数数组中的每个函数,并传递给定的参数。
export const def = (
obj: object,
key: string | symbol,
value: any,
writable = false,
): void => {
Object.defineProperty(obj, key, {
configurable: true,
enumerable: false,
writable,
value,
})
}
def
:用于定义对象的属性。
/**
* "123-foo" will be parsed to 123
* This is used for the.number modifier in v-model
*/
export const looseToNumber = (val: any): any => {
const n = parseFloat(val)
return isNaN(n)? val : n
}
looseToNumber
:尝试将输入值解析为浮点数,如果无法解析则返回原始值。
/**
* Only concerns number-like strings
* "123-foo" will be returned as-is
*/
export const toNumber = (val: any): any => {
const n = isString(val)? Number(val) : NaN
return isNaN(n)? val : n
}
toNumber
:将字符串转换为数字,如果无法转换则返回原始值。
// for typeof global checks without @types/node
declare var global: {}
let _globalThis: any
export const getGlobalThis = (): any => {
return (
_globalThis ||
(_globalThis =
typeof globalThis!== 'undefined'
? globalThis
: typeof self!== 'undefined'
? self
: typeof window!== 'undefined'
? window
: typeof global!== 'undefined'
? global
: {})
)
}
getGlobalThis
:获取全局对象。
const identRE = /^[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*$/
export function genPropsAccessExp(name: string): string {
return identRE.test(name)
? `__props.${name}`
: `__props[${JSON.stringify(name)}]`
}
genPropsAccessExp
:根据输入的名称生成属性访问表达式。
escapeHtml.ts
文件
escapeRE
常量
const escapeRE = /["'&<>]/
escapeRE
:定义了一个用于匹配需要进行 HTML 转义的特定字符("
、'
、&
、<
和 >
)的正则表达式。
escapeHtml
函数
export function escapeHtml(string: unknown): string {
const str = '' + string
const match = escapeRE.exec(str)
if (!match) {
return str
}
let html = ''
let escaped: string
let index: number
let lastIndex = 0
for (index = match.index; index < str.length; index++) {
switch (str.charCodeAt(index)) {
case 34: // "
escaped = '"'
break
case 38: // &
escaped = '&'
break
case 39: // '
escaped = '''
break
case 60: // <
escaped = '<'
break
case 62: // >
escaped = '>'
break
default:
continue
}
if (lastIndex!== index) {
html += str.slice(lastIndex, index)
}
lastIndex = index + 1
html += escaped
}
return lastIndex!== index? html + str.slice(lastIndex, index) : html
}
escapeHtml
函数的算法流程如下:
-
首先,将输入的未知类型
string
转换为字符串str
,并通过escapeRE.exec(str)
查找是否存在需要转义的字符。如果没有找到匹配的字符,直接返回原始字符串str
。 -
初始化一些变量:
html
用于存储转义后的结果字符串,escaped
用于存储对应字符的转义字符,index
用于遍历字符串的索引,lastIndex
用于记录上一次处理到的位置。 -
进入一个循环,从找到的第一个需要转义的字符的索引位置开始,一直到字符串的末尾。
-
在循环中,通过
str.charCodeAt(index)
获取当前字符的编码,并根据编码进行不同的处理:- 对于特定的编码(
34
表示"
、38
表示&
、39
表示'
、60
表示<
、62
表示>
),设置相应的转义字符到escaped
变量。 - 如果当前位置与上一次处理的位置不同,将这之间的原始字符片段添加到
html
中。
- 对于特定的编码(
-
更新
lastIndex
为当前位置加 1,并将转义字符添加到html
中。 -
循环结束后,如果
lastIndex
不等于字符串的末尾索引,将剩余的原始字符片段添加到html
中。 -
最终返回构建好的转义后的字符串
html
。
总的来说,这个函数通过逐步处理字符串中的每个字符,对需要转义的字符进行替换,从而生成符合 HTML 规范的转义字符串。
commentStripRE
常量
// https://www.w3.org/TR/html52/syntax.html#comments
const commentStripRE = /^-?>|<!--|-->|--!>|<!-$/g
commentStripRE
:定义了一个用于匹配 HTML 注释相关模式的正则表达式。
escapeHtmlComment
函数
export function escapeHtmlComment(src: string): string {
return src.replace(commentStripRE, '')
}
escapeHtmlComment
:该函数使用 commentStripRE
正则表达式对输入的字符串进行替换操作,以去除其中的 HTML 注释部分。
domTagConfig.ts
文件
HTML_TAGS
常量
const HTML_TAGS =
'html,body,base,head,link,meta,style,title,address,article,aside,footer,' +
'header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,' +
'figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,' +
'data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,' +
'time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,' +
'canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,' +
'th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,' +
'option,output,progress,select,textarea,details,dialog,menu,' +
'summary,template,blockquote,iframe,tfoot'
HTML_TAGS
:包含了一系列常见的 HTML 标签名称的字符串。
SVG_TAGS
常量
const SVG_TAGS =
'svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,' +
'defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,' +
'feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,' +
'feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,' +
'feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,' +
'fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,' +
'foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,' +
'mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,' +
'polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,' +
'text,textPath,title,tspan,unknown,use,view'
SVG_TAGS
:包含了一系列 SVG 标签名称的字符串。
MATH_TAGS
常量
const MATH_TAGS =
'annotation,annotation-xml,maction,maligngroup,malignmark,math,menclose,' +
'merror,mfenced,mfrac,mfraction,mglyph,mi,mlabeledtr,mlongdiv,' +
'mmultiscripts,mn,mo,mover,mpadded,mphantom,mprescripts,mroot,mrow,ms,' +
'mscarries,mscarry,msgroup,msline,mspace,msqrt,msrow,mstack,mstyle,msub,' +
'msubsup,msup,mtable,mtd,mtext,mtr,munder,munderover,none,semantics'
MATH_TAGS
:包含了一系列 MathML 标签名称的字符串。
VOID_TAGS
常量
const VOID_TAGS =
'area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr'
VOID_TAGS
:包含了一系列空标签名称的字符串。
isHTMLTag
函数
/**
* Compiler only.
* Do NOT use in runtime code paths unless behind `__DEV__` flag.
*/
export const isHTMLTag: (key: string) => boolean =
/*#__PURE__*/ makeMap(HTML_TAGS)
isHTMLTag
:通过 makeMap
函数基于 HTML_TAGS
创建的用于判断输入字符串是否为 HTML 标签的函数。
isSVGTag
函数
/**
* Compiler only.
* Do NOT use in runtime code paths unless behind `__DEV__` flag.
*/
export const isSVGTag: (key: string) => boolean =
/*#__PURE__*/ makeMap(SVG_TAGS)
isSVGTag
:通过 makeMap
函数基于 SVG_TAGS
创建的用于判断输入字符串是否为 SVG 标签的函数。
isMathMLTag
函数
/**
* Compiler only.
* Do NOT use in runtime code paths unless behind `__DEV__` flag.
*/
export const isMathMLTag: (key: string) => boolean =
/*#__PURE__*/ makeMap(MATH_TAGS)
isMathMLTag
:通过 makeMap
函数基于 MATH_TAGS
创建的用于判断输入字符串是否为 MathML 标签的函数。
isVoidTag
函数
/**
* Compiler only.
* Do NOT use in runtime code paths unless behind `__DEV__` flag.
*/
export const isVoidTag: (key: string) => boolean =
/*#__PURE__*/ makeMap(VOID_TAGS)
isVoidTag
:通过 makeMap
函数基于 VOID_TAGS
创建的用于判断输入字符串是否为空标签的函数。
这些函数和常量主要用于在编译器中进行不同类型标签的判断和处理。
domAttrConfig.ts
文件
isSpecialBooleanAttr
函数
const specialBooleanAttrs = `itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly`
export const isSpecialBooleanAttr: (key: string) => boolean =
/*#__PURE__*/ makeMap(specialBooleanAttrs)
isSpecialBooleanAttr
:通过 makeMap
函数基于 specialBooleanAttrs
字符串创建,用于判断输入的字符串是否为特殊的布尔型属性。
isBooleanAttr
函数
/**
* The full list is needed during SSR to produce the correct initial markup.
*/
export const isBooleanAttr: (key: string) => boolean = /*#__PURE__*/ makeMap(
specialBooleanAttrs +
`,async,autofocus,autoplay,controls,default,defer,disabled,hidden,` +
`inert,loop,open,required,reversed,scoped,seamless,` +
`checked,muted,multiple,selected`,
)
isBooleanAttr
:通过 makeMap
函数创建,用于判断输入的字符串是否为布尔型属性。
includeBooleanAttr
函数
/**
* Boolean attributes should be included if the value is truthy or ''.
* e.g. `<select multiple>` compiles to `{ multiple: '' }`
*/
export function includeBooleanAttr(value: unknown): boolean {
return!!value || value === ''
}
includeBooleanAttr
:用于判断给定的值是否应该被包含作为布尔型属性的值,只要值为真值或空字符串则返回 true
。
isSSRSafeAttrName
函数
const unsafeAttrCharRE = /[>/="'\u0009\u000a\u000c\u0020]/
const attrValidationCache: Record<string, boolean> = {}
export function isSSRSafeAttrName(name: string): boolean {
if (attrValidationCache.hasOwnProperty(name)) {
return attrValidationCache[name]
}
const isUnsafe = unsafeAttrCharRE.test(name)
if (isUnsafe) {
console.error(`unsafe attribute name: ${name}`)
}
return (attrValidationCache[name] =!isUnsafe)
}
isSSRSafeAttrName
:检查属性名称是否安全,通过正则表达式 unsafeAttrCharRE
来判断,如果不安全会打印错误信息,并将结果缓存起来。
propsToAttrMap
对象
export const propsToAttrMap: Record<string, string | undefined> = {
acceptCharset: 'accept-charset',
className: 'class',
htmlFor: 'for',
httpEquiv: 'http-equiv',
}
propsToAttrMap
:一个映射对象,将一些属性名称转换为对应的 HTML 属性名称。
isKnownHtmlAttr
函数
/**
* Known attributes, this is used for stringification of runtime static nodes
* so that we don't stringify bindings that cannot be set from HTML.
* Don't also forget to allow `data-*` and `aria-*`!
* Generated from https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes
*/
export const isKnownHtmlAttr: (key: string) => boolean = /*#__PURE__*/ makeMap(
`accept,accept-charset,accesskey,action,align,allow,alt,async,` +
`autocapitalize,autocomplete,autofocus,autoplay,background,bgcolor,` +
`border,buffered,capture,challenge,charset,checked,cite,class,code,` +
`codebase,color,cols,colspan,content,contenteditable,contextmenu,controls,` +
`coords,crossorigin,csp,data,datetime,decoding,default,defer,dir,dirname,` +
`disabled,download,draggable,dropzone,enctype,enterkeyhint,for,form,` +
`formaction,formenctype,formmethod,formnovalidate,formtarget,headers,` +
`height,hidden,high,href,hreflang,http-equiv,icon,id,importance,inert,integrity,` +
`ismap,itemprop,keytype,kind,label,lang,language,loading,list,loop,low,` +
`manifest,max,maxlength,minlength,media,min,multiple,muted,name,novalidate,` +
`open,optimum,pattern,ping,placeholder,poster,preload,radiogroup,readonly,` +
`referrerpolicy,rel,required,reversed,rows,rowspan,sandbox,scope,scoped,` +
`selected,shape,size,sizes,slot,span,spellcheck,src,srcdoc,srclang,srcset,` +
`start,step,style,summary,tabindex,target,title,translate,type,usemap,` +
`value,width,wrap`,
)
isKnownHtmlAttr
:通过 makeMap
函数创建,用于判断输入的字符串是否为已知的 HTML 属性。
isKnownSvgAttr
函数
/**
* Generated from https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute
*/
export const isKnownSvgAttr: (key: string) => boolean = /*#__PURE__*/ makeMap(
`xmlns,accent-height,accumulate,additive,alignment-baseline,alphabetic,amplitude,` +
`arabic-form,ascent,attributeName,attributeType,azimuth,baseFrequency,` +
`baseline-shift,baseProfile,bbox,begin,bias,by,calcMode,cap-height,class,` +
`clip,clipPathUnits,clip-path,clip-rule,color,color-interpolation,` +
`color-interpolation-filters,color-profile,color-rendering,` +
`contentScriptType,contentStyleType,crossorigin,cursor,cx,cy,d,decelerate,` +
`descent,diffuseConstant,direction,display,divisor,dominant-baseline,dur,dx,` +
`dy,edgeMode,elevation,enable-background,end,exponent,fill,fill-opacity,` +
`fill-rule,filter,filterRes,filterUnits,flood-color,flood-opacity,` +
`font-family,font-size,font-size-adjust,font-stretch,font-style,` +
`font-variant,font-weight,format,from,fr,fx,fy,g1,g2,glyph-name,` +
`glyph-orientation-horizontal,glyph-orientation-vertical,glyphRef,` +
`gradientTransform,gradientUnits,hanging,height,href,hreflang,horiz-adv-x,` +
`horiz-origin-x,id,ideographic,image-rendering,in,in2,intercept,k,k1,k2,k3,` +
`k4,kernelMatrix,kernelUnitLength,kerning,keyPoints,keySplines,keyTimes,` +
`lang,lengthAdjust,letter-spacing,lighting-color,limitingConeAngle,local,` +
`marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,` +
`mask,maskContentUnits,maskUnits,mathematical,max,media,method,min,mode,` +
`name,numOctaves,offset,opacity,operator,order,orient,orientation,origin,` +
`overflow,overline-position,overline-thickness,panose-1,paint-order,path,` +
`pathLength,patternContentUnits,patternTransform,patternUnits,ping,` +
`pointer-events,points,pointsAtX,pointsAtY,pointsAtZ,preserveAlpha,` +
`preserveAspectRatio,primitiveUnits,r,radius,referrerPolicy,refX,refY,rel,` +
`rendering-intent,repeatCount,repeatDur,requiredExtensions,requiredFeatures,` +
`restart,result,rotate,rx,ry,scale,seed,shape-rendering,slope,spacing,` +
`specularConstant,specularExponent,speed,spreadMethod,startOffset,` +
`stdDeviation,stemh,stemv,stitchTiles,stop-color,stop-opacity,` +
`strikethrough-position,strikethrough-thickness,string,stroke,` +
`stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,` +
`stroke-miterlimit,stroke-opacity,stroke-width,style,surfaceScale,` +
`systemLanguage,tabindex,tableValues,target,targetX,targetY,text-anchor,` +
`text-decoration,text-rendering,textLength,to,transform,transform-origin,` +
`type,u1,u2,underline-position,underline-thickness,unicode,unicode-bidi,` +
`unicode-range,units-per-em,v-alphabetic,v-hanging,v-ideographic,` +
`v-mathematical,values,vector-effect,version,vert-adv-y,vert-origin-x,` +
`vert-origin-y,viewBox,viewTarget,visibility,width,widths,word-spacing,` +
`writing-mode,x,x-height,x1,x2,xChannelSelector,xlink:actuate,xlink:arcrole,` +
`xlink:href,xlink:role,xlink:show,xlink:title,xlink:type,xmlns:xlink,xml:base,xml:lang,` +
`xml:space,y,y1,y2,yChannelSelector,z,zoomAndPan`,
)
isKnownSvgAttr
:通过 makeMap
函数创建,用于判断输入的字符串是否为已知的 SVG 属性。
isRenderableAttrValue
函数
/**
* Shared between server-renderer and runtime-core hydration logic
*/
export function isRenderableAttrValue(value: unknown): boolean {
if (value == null) {
return false
}
const type = typeof value
return type ==='string' || type === 'number' || type === 'boolean'
}
isRenderableAttrValue
:用于判断属性值是否可渲染,只要值的类型为字符串、数字或布尔值则返回 true
。
codeFrame.ts
文件
generateCodeFrame
函数
const range: number = 2
export function generateCodeFrame(
source: string,
start = 0,
end: number = source.length,
): string {
// 确保起始和结束位置在源字符串的有效范围内
start = Math.max(0, Math.min(start, source.length))
end = Math.max(0, Math.min(end, source.length))
if (start > end) return ''
// 将源字符串按换行符(包括换行符序列)拆分成行
let lines = source.split(/(\r?\n)/)
// 将换行符序列和行内容分别存储在不同的数组中
const newlineSequences = lines.filter((_, idx) => idx % 2 === 1)
lines = lines.filter((_, idx) => idx % 2 === 0)
let count = 0
const res: string[] = []
for (let i = 0; i < lines.length; i++) {
count +=
lines[i].length +
((newlineSequences[i] && newlineSequences[i].length) || 0)
if (count >= start) {
for (let j = i - range; j <= i + range || end > count; j++) {
if (j < 0 || j >= lines.length) continue
const line = j + 1
res.push(
`${line}${' '.repeat(Math.max(3 - String(line).length, 0))}| ${lines[j]}`,
)
const lineLength = lines[j].length
const newLineSeqLength =
(newlineSequences[j] && newlineSequences[j].length) || 0
if (j === i) {
// 推送上划线
const pad = start - (count - (lineLength + newLineSeqLength))
const length = Math.max(
1,
end > count? lineLength - pad : end - start,
)
res.push(` | ` + ' '.repeat(pad) + '^'.repeat(length))
} else if (j > i) {
if (end > count) {
const length = Math.max(Math.min(end - count, lineLength), 1)
res.push(` | ` + '^'.repeat(length))
}
count += lineLength + newLineSeqLength
}
}
break
}
}
return res.join('\n')
}
generateCodeFrame
函数的作用是根据给定的源字符串、起始位置和结束位置,生成包含指定范围前后几行代码的代码框架。通过对源字符串按行拆分和处理,为每行添加行号,并在指定位置添加下划线标识,以更清晰地展示相关代码片段的位置和上下文。
normalizeProp.ts
文件
normalizeStyle
函数
export function normalizeStyle(
value: unknown,
): NormalizedStyle | string | undefined {
if (isArray(value)) {
const res: NormalizedStyle = {}
for (let i = 0; i < value.length; i++) {
const item = value[i]
const normalized = isString(item)
? parseStringStyle(item)
: (normalizeStyle(item) as NormalizedStyle)
if (normalized) {
for (const key in normalized) {
res[key] = normalized[key]
}
}
}
return res
} else if (isString(value) || isObject(value)) {
return value
}
}
normalizeStyle
函数用于规范化样式输入。如果输入是数组,会遍历数组中的每个元素,对字符串元素调用 parseStringStyle
函数处理,对其他对象元素递归调用 normalizeStyle
函数处理,并将结果合并到一个新的对象中返回。如果输入是字符串或对象,则直接返回输入值。
parseStringStyle
函数
const listDelimiterRE = /;(?![^(]*\))/g
const propertyDelimiterRE = /:([^]+)/
const styleCommentRE = /\/\*[^]*?\*\//g
export function parseStringStyle(cssText: string): NormalizedStyle {
const ret: NormalizedStyle = {}
cssText
.replace(styleCommentRE, '')
.split(listDelimiterRE)
.forEach(item => {
if (item) {
const tmp = item.split(propertyDelimiterRE)
tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim())
}
})
return ret
}
parseStringStyle
函数用于解析字符串形式的 CSS 样式文本。它先去除注释,然后按分号分割,再处理每个属性部分,提取属性名和属性值,并存储到一个对象中返回。
stringifyStyle
函数
export function stringifyStyle(
styles: NormalizedStyle | string | undefined,
): string {
let ret = ''
if (!styles || isString(styles)) {
return ret
}
for (const key in styles) {
const value = styles[key]
if (isString(value) || typeof value === 'number') {
const normalizedKey = key.startsWith(`--`)? key : hyphenate(key)
// only render valid values
ret += `${normalizedKey}:${value};`
}
}
return ret
}
stringifyStyle
函数将规范化的样式对象转换为字符串形式的 CSS 样式。
normalizeClass
函数
export function normalizeClass(value: unknown): string {
let res = ''
if (isString(value)) {
res = value
} else if (isArray(value)) {
for (let i = 0; i < value.length; i++) {
const normalized = normalizeClass(value[i])
if (normalized) {
res += normalized +' '
}
}
} else if (isObject(value)) {
for (const name in value) {
if (value[name]) {
res += name +' '
}
}
}
return res.trim()
}
normalizeClass
函数用于规范化类名输入。如果输入是字符串,直接使用;如果是数组,递归处理每个元素并拼接;如果是对象,将值为真的属性名拼接。
normalizeProps
函数
export function normalizeProps(
props: Record<string, any> | null,
): Record<string, any> | null {
if (!props) return null
let { class: klass, style } = props
if (klass &&!isString(klass)) {
props.class = normalizeClass(klass)
}
if (style) {
props.style = normalizeStyle(style)
}
return props
}
normalizeProps
函数用于规范化属性对象中的 class
和 style
属性。
patchFlags.ts
文件
PatchFlags
枚举
/**
* Patch flags are optimization hints generated by the compiler.
* when a block with dynamicChildren is encountered during diff, the algorithm
* enters "optimized mode". In this mode, we know that the vdom is produced by
* a render function generated by the compiler, so the algorithm only needs to
* handle updates explicitly marked by these patch flags.
*
* Patch flags can be combined using the | bitwise operator and can be checked
* using the & operator, e.g.
*
* ```js
* const flag = TEXT | CLASS
* if (flag & TEXT) {... }
* ```
*
* Check the `patchElement` function in '../../runtime-core/src/renderer.ts' to see how the
* flags are handled during diff.
*/
export enum PatchFlags {
/**
* Indicates an element with dynamic textContent (children fast path)
*/
TEXT = 1,
/**
* Indicates an element with dynamic class binding.
*/
CLASS = 1 << 1,
/**
* Indicates an element with dynamic style
* The compiler pre-compiles static string styles into static objects
* + detects and hoists inline static objects
* e.g. `style="color: red"` and `:style="{ color: 'red' }"` both get hoisted
* as:
* ```js
* const style = { color: 'red' }
* render() { return e('div', { style }) }
* ```
*/
STYLE = 1 << 2,
/**
* Indicates an element that has non-class/style dynamic props.
* Can also be on a component that has any dynamic props (includes
* class/style). when this flag is present, the vnode also has a dynamicProps
* array that contains the keys of the props that may change so the runtime
* can diff them faster (without having to worry about removed props)
*/
PROPS = 1 << 3,
/**
* Indicates an element with props with dynamic keys. When keys change, a full
* diff is always needed to remove the old key. This flag is mutually
* exclusive with CLASS, STYLE and PROPS.
*/
FULL_PROPS = 1 << 4,
/**
* Indicates an element that requires props hydration
* (but not necessarily patching)
* e.g. event listeners & v-bind with prop modifier
*/
NEED_HYDRATION = 1 << 5,
/**
* Indicates a fragment whose children order doesn't change.
*/
STABLE_FRAGMENT = 1 << 6,
/**
* Indicates a fragment with keyed or partially keyed children
*/
KEYED_FRAGMENT = 1 << 7,
/**
* Indicates a fragment with unkeyed children.
*/
UNKEYED_FRAGMENT = 1 << 8,
/**
* Indicates an element that only needs non-props patching, e.g. ref or
* directives (onVnodeXXX hooks). since every patched vnode checks for refs
* and onVnodeXXX hooks, it simply marks the vnode so that a parent block
* will track it.
*/
NEED_PATCH = 1 << 9,
/**
* Indicates a component with dynamic slots (e.g. slot that references a v-for
* iterated value, or dynamic slot names).
* Components with this flag are always force updated.
*/
DYNAMIC_SLOTS = 1 << 10,
/**
* Indicates a fragment that was created only because the user has placed
* comments at the root level of a template. This is a dev-only flag since
* comments are stripped in production.
*/
DEV_ROOT_FRAGMENT = 1 << 11,
/**
* SPECIAL FLAGS -------------------------------------------------------------
* Special flags are negative integers. They are never matched against using
* bitwise operators (bitwise matching should only happen in branches where
* patchFlag > 0), and are mutually exclusive. When checking for a special
* flag, simply check patchFlag === FLAG.
*/
/**
* Indicates a cached static vnode. This is also a hint for hydration to skip
* the entire sub tree since static content never needs to be updated.
*/
CACHED = -1,
/**
* A special flag that indicates that the diffing algorithm should bail out
* of optimized mode. For example, on block fragments created by renderSlot()
* when encountering non-compiler generated slots (i.e. manually written
* render functions, which should always be fully diffed)
* OR manually cloneVNodes
*/
BAIL = -2,
}
PatchFlags
枚举定义了一系列用于表示元素或组件在渲染过程中的不同动态更新特征的标志。这些标志可以通过位运算进行组合和检查,以优化渲染过程中的差异比较和更新操作。
PatchFlagNames
对象
/**
* dev only flag -> name mapping
*/
export const PatchFlagNames: Record<PatchFlags, string> = {
[PatchFlags.TEXT]: `TEXT`,
[PatchFlags.CLASS]: `CLASS`,
[PatchFlags.STYLE]: `STYLE`,
[PatchFlags.PROPS]: `PROPS`,
[PatchFlags.FULL_PROPS]: `FULL_PROPS`,
[PatchFlags.NEED_HYDRATION]: `NEED_HYDRATION`,
[PatchFlags.STABLE_FRAGMENT]: `STABLE_FRAGMENT`,
[PatchFlags.KEYED_FRAGMENT]: `KEYED_FRAGMENT`,
[PatchFlags.UNKEYED_FRAGMENT]: `UNKEYED_FRAGMENT`,
[PatchFlags.NEED_PATCH]: `NEED_PATCH`,
[PatchFlags.DYNAMIC_SLOTS]: `DYNAMIC_SLOTS`,
[PatchFlags.DEV_ROOT_FRAGMENT]: `DEV_ROOT_FRAGMENT`,
[PatchFlags.CACHED]: `HOISTED`,
[PatchFlags.BAIL]: `BAIL`,
}
PatchFlagNames
对象是 PatchFlags
枚举中标志值与名称的映射,主要用于开发过程中的调试和理解。
shapeFlags.ts
文件
ShapeFlags
枚举
export enum ShapeFlags {
ELEMENT = 1,
FUNCTIONAL_COMPONENT = 1 << 1,
STATEFUL_COMPONENT = 1 << 2,
TEXT_CHILDREN = 1 << 3,
ARRAY_CHILDREN = 1 << 4,
SLOTS_CHILDREN = 1 << 5,
TELEPORT = 1 << 6,
SUSPENSE = 1 << 7,
COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,
COMPONENT_KEPT_ALIVE = 1 << 9,
COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT,
}
ShapeFlags
枚举定义了一系列用于表示组件或元素的不同形状特征的标志。
ELEMENT
:表示普通元素。FUNCTIONAL_COMPONENT
:表示函数式组件。STATEFUL_COMPONENT
:表示有状态组件。TEXT_CHILDREN
:表示具有文本子节点。ARRAY_CHILDREN
:表示具有数组子节点。SLOTS_CHILDREN
:表示具有插槽子节点。TELEPORT
:表示 teleport 组件。SUSPENSE
:表示 suspense 组件。COMPONENT_SHOULD_KEEP_ALIVE
:表示组件应该被保持活跃。COMPONENT_KEPT_ALIVE
:表示组件已被保持活跃。COMPONENT
:是STATEFUL_COMPONENT
和FUNCTIONAL_COMPONENT
的组合。
这些标志有助于在 Vue 的渲染和处理过程中更精确地识别和处理不同类型的组件和元素。
slotFlags.ts
文件
SlotFlags
枚举
export enum SlotFlags {
/**
* Stable slots that only reference slot props or context state. The slot
* can fully capture its own dependencies so when passed down the parent won't
* need to force the child to update.
*/
STABLE = 1,
/**
* Slots that reference scope variables (v-for or an outer slot prop), or
* has conditional structure (v-if, v-for). The parent will need to force
* the child to update because the slot does not fully capture its dependencies.
*/
DYNAMIC = 2,
/**
* `<slot/>` being forwarded into a child component. Whether the parent needs
* to update the child is dependent on what kind of slots the parent itself
* received. This has to be refined at runtime, when the child's vnode
* is being created (in `normalizeChildren`)
*/
FORWARDED = 3,
}
SlotFlags
枚举定义了用于描述插槽(slot
)特征的标志。
STABLE
:表示稳定的插槽,其仅引用插槽属性或上下文状态,父组件传递时无需强制子组件更新。DYNAMIC
:表示动态的插槽,引用了作用域变量或具有条件结构,父组件需要强制子组件更新。FORWARDED
:表示转发的插槽,父组件是否需要更新子组件取决于父组件自身接收的插槽类型,这在运行时创建子组件的虚拟节点时确定。
slotFlagsText
对象
/**
* Dev only
*/
export const slotFlagsText: Record<SlotFlags, string> = {
[SlotFlags.STABLE]: 'STABLE',
[SlotFlags.DYNAMIC]: 'DYNAMIC',
[SlotFlags.FORWARDED]: 'FORWARDED',
}
slotFlagsText
对象是 SlotFlags
枚举中标志值与字符串表示的映射,主要用于开发过程中的调试和理解。
toDisplayString.ts
文件
isRef
函数
const isRef = (val: any): val is { value: unknown } => {
return!!(val && val[ReactiveFlags.IS_REF] === true)
}
isRef
函数用于判断输入的值是否为具有特定标识 ReactiveFlags.IS_REF
且值为 true
的对象。
toDisplayString
函数
/**
* For converting {{ interpolation }} values to displayed strings.
* @private
*/
export const toDisplayString = (val: unknown): string => {
return isString(val)
? val
: val == null
? ''
: isArray(val) ||
(isObject(val) &&
(val.toString === objectToString ||!isFunction(val.toString)))
? isRef(val)
? toDisplayString(val.value)
: JSON.stringify(val, replacer, 2)
: String(val)
}
toDisplayString
函数用于将各种类型的输入值转换为显示字符串。
- 如果是字符串类型,直接返回。
- 如果值为
null
,返回空字符串。 - 如果是数组或对象(且
toString
方法不符合特定条件),如果是Ref
类型则递归处理其value
,否则使用JSON.stringify
并传入replacer
函数进行处理。 - 其他情况使用
String
函数进行转换。
replacer
函数
const replacer = (_key: string, val: unknown): any => {
if (isRef(val)) {
return replacer(_key, val.value)
} else if (isMap(val)) {
return {
[`Map(${val.size})`]: [...val.entries()].reduce(
(entries, [key, val], i) => {
entries[stringifySymbol(key, i) + ' =>'] = val
return entries
},
{} as Record<string, any>,
),
}
} else if (isSet(val)) {
return {
[`Set(${val.size})`]: [...val.values()].map(v => stringifySymbol(v)),
}
} else if (isSymbol(val)) {
return stringifySymbol(val)
} else if (isObject(val) &&!isArray(val) &&!isPlainObject(val)) {
// native elements
return String(val)
}
return val
}
replacer
函数用于在 JSON.stringify
过程中处理特定类型的值。
- 如果是
Ref
类型,递归处理其value
。 - 如果是
Map
类型,将其转换为特定格式的对象。 - 如果是
Set
类型,将其值映射为特定格式。 - 如果是
Symbol
类型,进行特定的字符串化处理。 - 如果是特殊的非数组、非普通对象的对象类型,使用
String
转换。
stringifySymbol
函数
const stringifySymbol = (v: unknown, i: number | string = ''): any =>
// Symbol.description in es2019+ so we need to cast here to pass
// the lib: es2016 check
isSymbol(v)? `Symbol(${(v as any).description?? i})` : v
stringifySymbol
函数用于将 Symbol
类型的值转换为特定的字符串表示。
typeUtils.ts
文件
Prettify
类型
export type Prettify<T> = { [K in keyof T]: T[K] } & {}
Prettify
类型用于对输入类型 T
的每个键进行重新映射,保留其对应的值,并确保结果是一个新的对象类型。
UnionToIntersection
类型
export type UnionToIntersection<U> = (
U extends any? (k: U) => void : never
) extends (k: infer I) => void
? I
: never
UnionToIntersection
类型用于将联合类型转换为交叉类型。
LooseRequired
类型
// make keys required but keep undefined values
export type LooseRequired<T> = { [P in keyof (T & Required<T>)]: T[P] }
LooseRequired
类型将输入类型 T
的键变为必填,但仍允许值为 undefined
。
IfAny
类型
// If the type T accepts type "any", output type Y, otherwise output type N.
// https://stackoverflow.com/questions/49927523/disallow-call-with-any/49928360#49928360
export type IfAny<T, Y, N> = 0 extends 1 & T? Y : N
IfAny
类型根据输入类型 T
是否接受 any
类型来决定输出类型是 Y
还是 N
。
Awaited
类型
// To prevent users with TypeScript versions lower than 4.5 from encountering unsupported Awaited<T> type, a copy has been made here.
export type Awaited<T> = T extends null | undefined
? T // special case for `null | undefined` when not in `--strictNullChecks` mode
: T extends object & { then(onfulfilled: infer F,...args: infer _): any } // `await` only unwraps object types with a callable `then`. Non-object types are not unwrapped
? F extends (value: infer V,...args: infer _) => any // if the argument to `then` is callable, extracts the first argument
? Awaited<V> // recursively unwrap the value
: never // the argument to `then` was not callable
: T // non-object or non-thenable
Awaited
类型用于处理 async/await
中的类型,对具有可调用 then
方法的对象类型进行递归地解包值。
OverloadParameters
类型
/**
* Utility for extracting the parameters from a function overload (for typed emits)
* https://github.com/microsoft/TypeScript/issues/32164#issuecomment-1146737709
*/
export type OverloadParameters<T extends (...args: any[]) => any> = Parameters<
OverloadUnion<T>
>
OverloadParameters
类型用于从函数重载中提取参数,通过 OverloadUnion
来处理。
OverloadUnionRecursive
类型
type OverloadProps<TOverload> = Pick<TOverload, keyof TOverload>
type OverloadUnionRecursive<
TOverload,
TPartialOverload = unknown,
> = TOverload extends (...args: infer TArgs) => infer TReturn
? TPartialOverload extends TOverload
? never
:
| OverloadUnionRecursive<
TPartialOverload & TOverload,
TPartialOverload &
((...args: TArgs) => TReturn) &
OverloadProps<TOverload>
>
| ((...args: TArgs) => TReturn)
: never
OverloadUnionRecursive
类型是处理函数重载的递归类型定义。
OverloadUnion
类型
type OverloadUnion<TOverload extends (...args: any[]) => any> = Exclude<
OverloadUnionRecursive<(() => never) & TOverload>,
TOverload extends () => never? never : () => never
>
OverloadUnion
类型用于合并函数重载的相关类型定义。