认识
一、认识
mountElement
主要用于虚拟 DOM
节点渲染为 真实 DOM
节点, 并挂载到对应容器。基本实现如下所示:
const processElement = (n1, n2, container,anchor) => {
if(n1 == null){
mountElement(n2,container,anchor);
}else{
patchElement(n1,n2,parentComponent);
}
};
const mountElement = (vnode, container) => {
const el = createElement(vnode.type);
if (typeof vnode.children === 'string') {
setElementText(el, vnode.children);
} else if (Array.isArray(vnode.children)) {
vnode.children.forEach(child => {
/**
* @description: patch 挂载点需要更改为当前 vnode 创建的 DOM 元素 el, 保证 child 子节点挂载到正确的位置
*/
patch(null, child, el);
});
}
if (vnode.props) {
for(let key in vnode.props){
patchProp(el,key,null,props[key]);
}
}
insert(el, container);
};
二、mount 细节
挂载子节点时, 首先要区分其类型:
-
如果
vnode.children
是字符串: 则说明元素具有文本子节点 -
如果
vnode.children
是数组: 则说明元素具有多个子节点
三、props 细节
3.1 patchProp
export function patchProp(el, key, prevValue, nextValue) {
if (key === 'class') {
patchClass(el, nextValue);
} else if (key === 'style') {
patchStyle(el, prevValue, nextValue);
} else if (shouldSetAsProps(el, key, nextValue)) {
// 通过 shouldSetAsProps 判断 key 属性是否是可读属性 或者 存在于 DOM Properties 中
const type = typeof el[key];
if (type === 'boolean' && nextValue === '') {
el[key] = true;
} else {
el[key] = nextValue;
}
} else {
// 如果要设置的属性没有对应的 DOM Properties 或者是可读属性, 则使用 setAttribute 来设置属性
el.setAttribute(key, nextValue);
}
}
3.2 patchProps
patchProps
通过两个循环, 来对属性进行挂载或者更新。
-
循环旧属性: 如果旧属性不在新属性中, 去除
if (oldProps !== EMPTY_OBJ) {
for (const key in oldProps) {
if (!(key in newProps)) {
hostPatchProp(el, key, oldProps[key], null);
}
}
} -
循环新属性: 如果新属性值发生改变, 更新
for (const key in newProps) {
const next = newProps[key];
const prev = oldProps[key];
if (next !== prev) {
hostPatchProp(el, key, prev, next);
}
}
3.3 shouldSetAsProp
readonly attribute
只能够通过 el.setAttribute()
来设置。比如:
input
:input.form
3.4 patchClass
class attribute
设置的方式有以下三种:
-
el.setAttribute()
-
el.className = xx
-
el.classList.add()
其中, el.className
的性能最优。因此, 通过 el.className = xxx
处理 class attribute
3.5 patchStyle
3.6 patchEvent
在 vnode.props
中, 凡是以字符串 on
开头的属性都视作事件,如下所示:
const vnode = {
type: 'p',
props: {
onClick: ()=>{
console.log("onClick");
}
},
children: 'p - children'
}
vnode.props.on事件
将事件绑定到 DOM
元素的过程如下:
-
匹配以
on
开头的属性,视为事件 -
根据属性名称得到对应的事件名称:
onClick --> click
-
通过性能高优的方式绑定事件、更好的完成事件更新: 在绑定事件时,绑定一个伪造的事件处理函数
invoker
, 然后把真正的处理函数设置为invoker.value
属性的值。这样更新事件的时候,我们不再需要调用removeEventListener
函数来移除上一次绑定的事件,只需要更新invoker.value
的值即可。 -
通过为伪造的事件处理函数
invoker
添加invoker.attached
属性, 用来存储事件处理函数被绑定的时间。然后,在invoker
执行的时候, 通过事件对象的e.timeStamp
获取事件发生的时间。最后,比较两者, 如果事件处理函数被绑定的时间晚于事件发生的时间,则不执行该事件处理函数。这样一来,屏蔽了所有绑定时间晚于事件触发时间的事件处理函数的执行, 解决了事件冒泡与事件更新之间相互影响的问题。
vnode.props.on事件
将事件绑定到 DOM
元素的实现如下:
function patchEvent(el, key, prevValue, nextValue) {
const invokers = el._vei || (el._vei = {});
let invoker = invokers[key];
const name = key.slice(2).toLowerCase();
if (nextValue) {
/**
* @description: 如果 nextValue 存在 且 没有 invoker ,伪造一个 invoker 并缓存到 el._vei 中
*/
if (!invoker) {
invoker = el._vei[key] = e => {
/**
* @description: 如果事件发生的时间早于事件处理函数绑定的时间,则不执行事件处理函数
*/
if (e.timeStamp < invoker.attached) {
return;
}
/**
* @description: invoker.value 是数组,则遍历 invoker.value 逐个调用事件处理函数
*/
if (Array.isArray(invoker.value)) {
invoker.value.forEach(fn => fn(e));
} else {
invoker.value(e);
}
};
invoker.value = nextValue;
/**
* @description: invoker.attached 存储事件处理函数被绑定的时间
*/
invoker.attached = performance.now();
el.addEventListener(name, invoker);
} else {
/**
* @description: 如果 nextValue 存在 且 之前有 invoker, 只需要更新 invoker.value 的值即可
*/
invoker.value = nextValue;
}
} else if (invoker) {
/**
* @description: 如果 nextValue 不存在, 且 之前有 invoker, 则移除绑定事件
*/
el.removeEventListener(name, invoker);
}
}
3.7 HTML Attributes VS DOM Properties
HTML Attributes
指的是定义在 HTML
标签上的属性。比如说 <div id="class-div"></div>
,其中, id
就是 HTML Attribute
。当浏览器解析 HTML
之后, 会创建一个与之相符的 DOM
元素对象, 这个 DOM
对象会包含很多属性 Properties
。这些属性就是 DOM Properties
。很多 HTML Attributes
在 DOM
对象上有很多同名的 DOM Properties
,也有一些不同的。HTML Attributes
与 DOM Properties
中相同名称的属性可以看成是 直接映射, 不同的名称可能会有关联。但是在 Vue
中 HTML Attributes
与 DOM Properties
有价值的关系在于: HTML Attributes
作用是设置 与之对应的 DOM Properties
的初始值
3.8 el.setAttribute(key,value) VS el[key] = value
el.setAttribute(key,value)
为 HTML
设置属性。其中 value
无论是什么类型, 最后都会被转化为字符串类型, 比如 el.setAttribute(key,false) => el.setAttribute(el,'false')
。所以,我们不能总是使用 setAttribute
来将 vnode.props
中的属性设置到 el
上。
el[key] = value
为 DOM
设置属性。当我们设置 el['disable'] = ''
时, 浏览器解析空字符串时, 会将它置为 true
。而我们设置 el['disable'] = ''
却将 el['disable'] = false
, 违背了用户的本意, 且我们的实现与浏览器的实现不一致。
因此, 我们需要灵活的在 el.setAttribute(key,value)
和 el[key] = value
中切换, 即优先设置元素的 DOM Properties
,但当值为空字符串时, 要手动将值矫正为 true
。只有这样, 才能保证代码符合预期。