patchProp属性补丁解析
# 概览
在《createApp创建实例解析》中提到pathProp
用于更新DOM元素的属性,本文会详细解析下patchProp
方法中是如何更新不同类型的属性。
const patchProp = (el, key, prevValue, nextValue, namespace, parentComponent) => {
// el:目标DOM元素 key:属性名 preValue:旧值 nextValue:新值 namespace:命名空间 parentComponent:父组件实例
// isSVG表示是否是SVG元素
const isSVG = namespace === 'svg';
if (key === 'class') {
// 处理class属性
patchClass(el, nextValue, isSVG);
} else if (key === "style") {
// 处理style样式属性
patchStyle(key, prevValue, nextValue);
} else if (shared.isOn(key)) {
// 处理事件监听器,isOn判断属性名即事件名称是否以on开头
if (!shared.isModeListener(key)) {
// isModeListener过滤掉onUpdate特殊事件
patchEvent(el, key, prevValue, nextValue, parentComponent);
}
} else if (key[0] === '.' ? (key = key.slice(1), true) :
key[0] === "^" ? (key = key.slice(1), false) :
shouldSetAsProp(el, key, nextValue, isSVG)) {
// 处理DOM属性
patchDOMProp(el, key, nextValue);
if (!el.tagName.includes("-") && (key === "value" || key === "checked" || key === "selected")) {
// 处理特殊属性 value/checked/selected
patchAttr(el, key, nextValue, isSVG, parentComponent, key !== "value")
}
} else if (el._isVueCE && (/[A-Z]/.test(key) || !shared.isstring(nextValue))) {
// 处理Web Components属性
patchDOMProp(el, shared, camelize(key), nextValue, parentComponent, key)
} else {
// 处理其他属性
if (key === 'true-value') {
el._trueValue = nextValue;
} else if (key === 'false-value') {
el._falseValue = nextValue;
}
patchAttr(el, key, nextValue, isSVG)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 更新方法
# patchClass
patchClass
主要处理class
属性,先判断是否存在动画class
类名,若存在,则将其一并加入到value
中;然后判断value
是否为null
。若value
为null
,则移除元素的class
属性,否则判断元素是否是SVG
元素,若是SVG
元素,则设置class
属性的值;否则设置className
的值。
patchClass
源码如下:
function patchClass(el, value, isSVG) {
const transitionClasses = el[vtcKey];
if (transitionClasses) {
value = (value ? [value, ...transitionClasses] : [...transitionClasses]).join(" ");
}
if (value == null) {
el.removeAttribute("class");
} else if (isSVG) {
el.setAttribute("class", value);
} else {
el.className = value;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
patchClass
的源码中有注释提到理论上设置className
的值比设置属性setAttribute
速度要快。
# patchStyle
patchStyle
用于更新设置元素的style
属性。
// display属性正则
const displayRE = /(^|;)\s*display\s*:/;
function patchStyle(el, prev, next) {
// 获取元素的style值
const style = el.style;
// isCssString表示style的新值是否字符串
const isCssString = shared.isString(next);
// 表示是否需要控制display属性
let hasControlledDisplay = false;
if (next && !isCssString) {
// 若新值存在并且新值不是一个字符串
if (prev) {
// 若style属性存在旧值
if (!shared.isString(prev)) {
// 若旧值不是一个字符串,则遍历旧值
for (const key in prev) {
// 若在新值中旧值对应key的值为null,则调用setStyle方法,将其置空
if (next[key] == null) {
setStyle(style, key, "");
}
}
} else {
// 若旧值是一个字符串,则调用split方法将其转为数组进行遍历
for (const prevStyle of prev.split(";")) {
// 获取旧值中的key
const key = prevStyle.slice(0, prevStyle.indexOf(":")).trim();
// 同上,判断旧值中的key在新值中是否为null,为null则调用setStyle
if (next[key] == null) {
setStyle(style, key, "");
}
}
}
}
// 遍历新值
for (const key in next) {
// 若新值中存在display属性,则将hasControlledDisplay变量设为true
if (key === "display") {
hasControlledDisplay = true;
}
// 然后调用setStyle方法设置样式的新属性
setStyle(style, key, next[key]);
}
} else {
if (isCssString) {
// 若style的新值时字符串
if (prev !== next) {
// 若新值不等于旧值
const cssVarText = style[CSS_VAR_TEXT];
if (cssVarText) {
// 判断样式中是否存在css文本变量,则拼接新值
next += ";" + cssVarText;
}
// 设置样式的cssText属性
style.cssText = next;
// 通过正则匹配,修改hasControlledDisplay
hasControlledDisplay = displayRE.test(next);
}
} else if (prev) {
// 若旧值存在,则移除元素的style属性
el.removeAttribute("style");
}
}
// 判断el元素是否存在v-show指令的属性
if (vShowOriginalDisplay in el) {
// 设置元素上的属性值
el[vShowOriginalDisplay] = hasControlledDisplay ? style.display : "";
if (el[vShowHidden]) {
// 若元素上设置了v-show指令,且其值为false,则el[vShowHidden]为true
style.display = "none";
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# patchEvent
patchEvent
用于更新或设置元素上绑定的事件。封装了addEventListener
/removeEventListener
用于绑定或解绑元素上的事件监听。
patchEvent
源码实现如下:
// veiKey用于存储el元素上存储事件调用器invokers的键
const veiKey = Symbol("_vei");
function patchEvent(el, rawName, prevValue, nextValue, instance = null) {
// rawName 是原始事件名 如onClick或onClickOnce
const invokers = el[veiKey] || (el[veiKey] = {});
// 获取或初始化事件调用器对象invokers
const existingInvoker = invokers[rawName];
if (nextValue && existingInvoker) {
// 若当前事件名已存在调用器且信值存在,则更新调用器的值
existingInvoker.value = nextValue;
} else {
// 通过parseName解析出事件名和修饰符
const [name, options] = parseName(rawName);
if (nextValue) {
// 若新值存在,则调用createInvoker创建一个新的调用器
const invoker = invokers[rawName] = createInvoker(
nextValue,
instance
);
// 事件监听绑定到元素上
addEventListener(el, name, invoker, options);
} else if (existingInvoker) {
// 若新值不存在且存在旧的调用器,则需要移除事件监听
removeEventListener(el, name, existingInvoker, options);
// 清除调用器引用
invokers[rawName] = void 0;
}
}
}
// 绑定监听
function addEventListener(el, event, handler, options) {
el.addEventListener(event, handler, options);
}
// 移除监听
function removeEventListener(el, event, handler, options) {
el.removeEventListener(event, handler, options);
}
let cachedNow = 0; // 时间戳
const p = Promise.resolve(); // 微任务
// getNow用于获取当前时间
const getNow = () => cachedNow || (p.then(() => cachedNow = 0), cachedNow = Date.now());
// 创建一个调用器函数
function createInvoker(initialValue, instance) {
// initialValue:初始值 instance:实例
// 定义调用器函数invoker
const invoker = (e) => {
// 判断事件是否存在时间戳,若不存在,则将其设置为当前时间
if (!e._vts) {
e._vts = Date.now();
} else if (e._vts <= invoker.attached) {
// 若事件的时间戳小于或等于调用器绑定的时间,则跳过执行
return;
}
// 调用callWithAsyncErrorHandling执行事件,并处理异步错误
runtimeCore.callWithAsyncErrorHandling(
// 处理事件传播
patchStopImmediatePropagation(e, invoker.value),
instance,
5,
[e]
);
};
// 存储当前的事件处理函数
invoker.value = initialValue;
// 记录调用器被绑定的时间
invoker.attached = getNow();
return invoker;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# patchDOMProp
patchDOMProp
用于在el
元素上设置属性,该属性是DOM对象上的JavasScript属性,而不是HTML标签上的特性,这两者是有区别的。
function patchDOMProp(el, key, value, parentComponent, attrName) {
if (key === "innerHTML" || key === "textContent") {
// 处理innerHTML和textContent属性
if (value != null) {
// unsafeToTrustedHTML 是标记信任的HTML字符串
el[key] = key === "innerHTML" ? unsafeToTrustedHTML(value) : value;
}
return;
}
const tag = el.tagName;
// 处理value属性并且不是<progress>标签,也不是自定义元素
if (key === "value" && tag !== "PROGRESS" && // custom elements may use _value internally
!tag.includes("-")) {
// 获取旧值
const oldValue = tag === "OPTION" ? el.getAttribute("value") || "" : el.value;
// 获取新值,兼容处理复选框
const newValue = value == null ? (
el.type === "checkbox" ? "on" : ""
) : String(value);
// 判断,若新值不等于旧值,或者元素上不存在_value属性
if (oldValue !== newValue || !("_value" in el)) {
// 设置value属性为新值
el.value = newValue;
}
// 若value是null,则移除value属性
if (value == null) {
el.removeAttribute(key);
}
el._value = value;
return;
}
// 处理其他属性
let needRemove = false;
if (value === "" || value == null) {
const type = typeof el[key];
if (type === "boolean") {
value = shared.includeBooleanAttr(value);
} else if (value == null && type === "string") {
value = "";
needRemove = true;
} else if (type === "number") {
value = 0;
needRemove = true;
}
}
try {
el[key] = value;
} catch (e) {
}
needRemove && el.removeAttribute(attrName || key);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# patchAttr
patchAttr
用于处理普通HTML属性和SVG的xlink
属性
const xlinkNS = "http://www.w3.org/1999/xlink";
function patchAttr(el, key, value, isSVG, instance, isBoolean = shared.isSpecialBooleanAttr(key)) {
if (isSVG && key.startsWith("xlink:")) {
// 若是SVG,且key是xlink:开头
if (value == null) {
// 若value是null,则移除属性
el.removeAttributeNS(xlinkNS, key.slice(6, key.length));
} else {
// 设置xlink属性
el.setAttributeNS(xlinkNS, key, value);
}
} else {
// 普通属性处理
if (value == null || isBoolean && !shared.includeBooleanAttr(value)) {
// 若值value为null或undefined,或者是布尔属性且值不应该存在,则移除属性
el.removeAttribute(key);
} else {
// 否则设置属性
el.setAttribute(
key,
isBoolean ? "" : shared.isSymbol(value) ? String(value) : value
);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# shared.isOn
isOn
用于检测key
事件名是否是以on
为前缀,第三个字符为大写的。
const isOn = (key) => key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && (key.charCodeAt(2) > 122 || key.charCodeAt(2) < 97);
1
# shared.isModeListener
isModeListener
用于判断事件名是否以onUpdate:
开头。
const isModelListener = (key) => key.startsWith("onUpdate:");
1
编辑 (opens new window)
上次更新: 2025/09/22, 01:09:15