Skip to content

Commit

Permalink
fix:一些简单的笔误
Browse files Browse the repository at this point in the history
  • Loading branch information
liyangworld committed Jul 25, 2019
1 parent 4dde75f commit 7469dd3
Show file tree
Hide file tree
Showing 2 changed files with 10 additions and 10 deletions.
2 changes: 1 addition & 1 deletion docs/zh/renderer-advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ const { render } = createRenderer({
// 如果找到了,则将其删除
parent.children.splice(i, 1)
} else {
// 没找到,说明渲染器除了问题,例如没有在 nodeOps.appendChild 函数中维护正确的父子关系等
// 没找到,说明渲染器出了问题,例如没有在 nodeOps.appendChild 函数中维护正确的父子关系等
// 这时需要打印错误信息,以提示开发者
console.error('target: ', child)
console.error('parent: ', parent)
Expand Down
18 changes: 9 additions & 9 deletions docs/zh/renderer-diff.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ function patchChildren(
mount(nextChildren[i], container)
}
} else if (prevLen > nextLen) {
// 如果 prevLen > nextLen,将多出来的元素添加
// 如果 prevLen > nextLen,将多出来的元素移除
for (let i = commonLength; i < prevLen; i++) {
container.removeChild(prevChildren[i].el)
}
Expand Down Expand Up @@ -207,7 +207,7 @@ h('li', null, 1)
h('li', null, 3)
```

`patch` 函数知道它们是相同的标签,所以只会更新 `VNodeData` 和子节点,由于这两个标签都没有 `VNodeData`,所以只需要更新它们的子节点,旧的 `li` 元素的子节点是文本节点 `'1'`,而新的 `li` 标签的子节点时文本节点 `'3'`,所以最终会调用一次 `patchText` 函数将 `li` 标签的文本子节点由 `'1'` 更新为 `'3'`。接着,使用旧 `children` 的第二个节点和新 `children` 的第二个节点进行比对,结果同样是调用一次 `patchText` 函数用以更新 `li` 标签的文本子节点。类似的,对于新旧 `children` 的第三个节点同样也会调用一次 `patchText` 函数更新其文本子节点。而这,就是问题所在,实际上我们通过观察新旧 `children` 可以很容易的发现:新旧 `children` 中的节点只有顺序是不同的,所以最佳的操作应该是**通过移动元素的位置来达到更新的目的**
`patch` 函数知道它们是相同的标签,所以只会更新 `VNodeData` 和子节点,由于这两个标签都没有 `VNodeData`,所以只需要更新它们的子节点,旧的 `li` 元素的子节点是文本节点 `'1'`,而新的 `li` 标签的子节点是文本节点 `'3'`,所以最终会调用一次 `patchText` 函数将 `li` 标签的文本子节点由 `'1'` 更新为 `'3'`。接着,使用旧 `children` 的第二个节点和新 `children` 的第二个节点进行比对,结果同样是调用一次 `patchText` 函数用以更新 `li` 标签的文本子节点。类似的,对于新旧 `children` 的第三个节点同样也会调用一次 `patchText` 函数更新其文本子节点。而这,就是问题所在,实际上我们通过观察新旧 `children` 可以很容易的发现:新旧 `children` 中的节点只有顺序是不同的,所以最佳的操作应该是**通过移动元素的位置来达到更新的目的**

既然移动元素是最佳期望,那么我们就需要思考一下,能否通过移动元素来完成更新?能够移动元素的关键在于:我们需要在新旧 `children` 的节点中保存映射关系,以便我们能够在旧 `children` 的节点中找到可复用的节点。这时候我们就需要给 `children` 中的节点添加唯一标识,也就是我们常说的 `key`,在没有 `key` 的情况下,我们是没办法知道新 `children` 中的节点是否可以在旧 `children` 中找到可复用的节点的,如下图所示:

Expand Down Expand Up @@ -288,7 +288,7 @@ for (let i = 0; i < nextChildren.length; i++) {

### 找到需要移动的节点

现在我们已经找到了可复用的节点,并进行了合适的更新操作,下一步需要做的,就是判断一个节点是否需要移动以及如何移动。如何判断节点是否需要移动呢?为了弄明白这个为,我们可以先考虑不需要移动的情况,当新旧 `children` 中的节点顺序不变时,就不需要额外的移动操作,如下:
现在我们已经找到了可复用的节点,并进行了合适的更新操作,下一步需要做的,就是判断一个节点是否需要移动以及如何移动。如何判断节点是否需要移动呢?为了弄明白这个问题,我们可以先考虑不需要移动的情况,当新旧 `children` 中的节点顺序不变时,就不需要额外的移动操作,如下:

<img src="@imgs/diff-react-1.png" width="400" />

Expand Down Expand Up @@ -558,7 +558,7 @@ function mountElement(vnode, container, isSVG, refNode) {

<img src="@imgs/diff-react-6.png" width="400" />

可以看出,新的 `children` 中已经不存在 `li-c` 节点了,所以我们应该想办法将 `li-c` 节点对应的真实 DOM 从容器元素内移除。但我们之前编写的算法还不能完成这个任务,因为外层循环遍历的是新的 `children`,所以外层循环会执行两次,第一次用于处理 `li-a` 节点,第二次用于处理 `li-b` 节点,此时整个算法已经运行结束了。所以,我们需要在外层循环结束之后,再优先遍历一次旧的 `children`,并尝试拿着旧 `children` 中的节点去新 `children` 中寻找相同的节点,如果找不到则说明该节点已经不存在与新 `children` 中了,这时我们应该将该节点对应的真实 DOM 移除,如下高亮代码所示:
可以看出,新的 `children` 中已经不存在 `li-c` 节点了,所以我们应该想办法将 `li-c` 节点对应的真实 DOM 从容器元素内移除。但我们之前编写的算法还不能完成这个任务,因为外层循环遍历的是新的 `children`,所以外层循环会执行两次,第一次用于处理 `li-a` 节点,第二次用于处理 `li-b` 节点,此时整个算法已经运行结束了。所以,我们需要在外层循环结束之后,再优先遍历一次旧的 `children`,并尝试拿着旧 `children` 中的节点去新 `children` 中寻找相同的节点,如果找不到则说明该节点已经不存在于新 `children` 中了,这时我们应该将该节点对应的真实 DOM 移除,如下高亮代码所示:

```js {14-26}
let lastIndex = 0
Expand Down Expand Up @@ -1356,7 +1356,7 @@ outer: {

<img src="@imgs/diff-vue3/diff8.png" width="500" />

观察上图中新旧 `children` 中节点的顺序,我发现,这个案例在应用预处理步骤之后,只有 `li-a` 节点和 `li-e` 节点能够被提前 `patch`。换句话说在这种情况下没有办法简单的通过预处理就能够结束 `Diff` 逻辑。这时我们就需要进行下一步操作,实际上无论是 `React``Diff` 算法,还是 `Vue2(snabbdom)``Diff` 算法,其重点无非就是:**判断是否有节点需要移动,以及应该如何移动****寻找出那些需要被添加或移除**的节点,而本节我们所讲解的算法也不例外,所以接下来的任务就是:判断那些节点需要移动,以及如何移动。
观察上图中新旧 `children` 中节点的顺序,我们发现,这个案例在应用预处理步骤之后,只有 `li-a` 节点和 `li-e` 节点能够被提前 `patch`。换句话说在这种情况下没有办法简单的通过预处理就能够结束 `Diff` 逻辑。这时我们就需要进行下一步操作,实际上无论是 `React``Diff` 算法,还是 `Vue2(snabbdom)``Diff` 算法,其重点无非就是:**判断是否有节点需要移动,以及应该如何移动****寻找出那些需要被添加或移除**的节点,而本节我们所讲解的算法也不例外,所以接下来的任务就是:判断那些节点需要移动,以及如何移动。

为了让事情更直观我们把该案例在应用预处理之后的状态用下图描述出来:

Expand Down Expand Up @@ -1437,7 +1437,7 @@ for (let i = prevStart; i <= prevEnd; i++) {
}
```

如上代码所示,外层循环逐个从旧 `children` 中取出未处理的节点,并尝试在新 `children` 中寻找拥有相同 `key` 值的可复用节点,一旦找到了可复用节点,则调用 `patch` 函数更新之。接着更新 `source` 数组中对应位置的值,这里需要注意的是,由于 `k - nextStart` 的值才是正确的位置索引,而非 `k` 本身,并且外层循环中变量 `i` 的值就代表了该节点在旧 `children` 中的位置,所以直接将 `i` 赋值给 `source[k - nextStart]` 即可达到目的,最终的效果就如上图中所展示的那样。可以看到 `source` 数组的第四个元素值仍然为初始值 `-1`,这是因为**`children` 中的 `li-g` 节点不存在与旧 `children`**。除此之外,还有一件很重要的事儿需要做,即判断是否需要移动节点,判断的方式类似于 `React` 所采用的方式,如下高亮代码所示:
如上代码所示,外层循环逐个从旧 `children` 中取出未处理的节点,并尝试在新 `children` 中寻找拥有相同 `key` 值的可复用节点,一旦找到了可复用节点,则调用 `patch` 函数更新之。接着更新 `source` 数组中对应位置的值,这里需要注意的是,由于 `k - nextStart` 的值才是正确的位置索引,而非 `k` 本身,并且外层循环中变量 `i` 的值就代表了该节点在旧 `children` 中的位置,所以直接将 `i` 赋值给 `source[k - nextStart]` 即可达到目的,最终的效果就如上图中所展示的那样。可以看到 `source` 数组的第四个元素值仍然为初始值 `-1`,这是因为**`children` 中的 `li-g` 节点不存在于旧 `children`**。除此之外,还有一件很重要的事儿需要做,即判断是否需要移动节点,判断的方式类似于 `React` 所采用的方式,如下高亮代码所示:

```js {3-4,15-19}
const prevStart = j
Expand Down Expand Up @@ -1751,7 +1751,7 @@ if (moved) {

接着我们将子问题进行扩大,现在我们取出原序列中的最后两个数字作为一个序列,即 `[ 2, 10 ]`。对于这个序列而言,我们可以把它看作是**由序列 `[ 2 ]` 和序列 `[ 10 ]` 这两个序列所组成的**。并且我们观察这两个序列中的数字,发现满足条件 `2 < 10`,这满足了递增的要求,所以我们是否可以认为**序列 `[ 2, 10 ]` 的最长递增子序列等于序列 `[ 2 ]` 和序列 `[ 10 ]` 这两个序列的递增子序列“之和”**?答案是肯定的,而且庆幸的是,我们在上一步中已经求得了序列 `[ 10 ]` 的最长递增子序列的长度是 `1`,同时序列 `[ 2 ]` 也是一个只有一个元素的序列,所以它的最长递增子序列也是它本身,长度也是 `1`,最后我们将两者做和,可知序列 `[ 2, 10 ]` 的最长递增子序列的长度应该是 `1 + 1 = 2`。实际上我们一眼就能够看得出来序列 `[ 2, 10 ]` 的最长递增子序列也是 `[ 2, 10 ]`,其长度当然为 `2` 啦。

为了不过与抽象,我们可以画出如下图所示的格子:
为了不过于抽象,我们可以画出如下图所示的格子:

<img src="@imgs/lis/lis1.png" width="300" />

Expand Down Expand Up @@ -1805,7 +1805,7 @@ if (moved) {

<img src="@imgs/lis/lis7.png" width="300" />

如上图所示,现在所有格子的值都已经更新完毕,接下来我们要做的就是根据这些值,找到整个序列的最长递增子序列。那么应该如何寻找呢?很简单,实际这些上格子中的最大值就代表了整个序列的递增子序列的最大长度,上图中数字 `0` 对应格子的值为 `3`,是最大值,因此原序列的最长递增子序列一定是以数字 `0` 开头的:
如上图所示,现在所有格子的值都已经更新完毕,接下来我们要做的就是根据这些值,找到整个序列的最长递增子序列。那么应该如何寻找呢?很简单,实际上这些格子中的最大值就代表了整个序列的递增子序列的最大长度,上图中数字 `0` 对应格子的值为 `3`,是最大值,因此原序列的最长递增子序列一定是以数字 `0` 开头的:

<img src="@imgs/lis/lis8.png" width="300" />

Expand Down Expand Up @@ -1855,7 +1855,7 @@ if (moved) {

## 不足之处

实际上,我们确实话费了很大的篇幅来尽可能全面的讲解 `Virtual DOM` 核心的 `Diff` 算法,然而这里面仍然存在诸多不足的之处,例如我们在移除一个 DOM 节点时,直接调用了 Web 平台的 `removeChild` 方法,这是因为在以上讲解中,我们始终假设新旧 `children` 中的 `VNode` 都是真实 DOM 的描述,而不包含组件的描述或其他类型 `VNode` 的描述,但实际上 `children` 中 `VNode` 的类型可以是任意的,因此我们不能简单的通过 Web 平台的 `removeChild` 方法进行 DOM 移除操作。这时我们需要封装一个专用函数:`removeVNode`,该函数专门负责移除一个 `VNode`,它会判断该 `VNode` 的类型,并采用合适的方式将其所渲染的真实 DOM 移除。大家思考一下,如果将要被移除的 `VNode` 是一个组件的描述,那是否还应该在移除之前或之后分别调用 `beforeUnmount` 以及 `unmounted` 等生命周期钩子函数呢?答案当然是肯定的。不过,本节讲解的内容虽然存在不足,但至少思路是完全正确的,在此基础上,你可以发挥自己的想象或者结合真正 `Vue3` 的源码去进一步的提升。
实际上,我们确实花费了很大的篇幅来尽可能全面的讲解 `Virtual DOM` 核心的 `Diff` 算法,然而这里面仍然存在诸多不足之处,例如我们在移除一个 DOM 节点时,直接调用了 Web 平台的 `removeChild` 方法,这是因为在以上讲解中,我们始终假设新旧 `children` 中的 `VNode` 都是真实 DOM 的描述,而不包含组件的描述或其他类型 `VNode` 的描述,但实际上 `children` 中 `VNode` 的类型可以是任意的,因此我们不能简单的通过 Web 平台的 `removeChild` 方法进行 DOM 移除操作。这时我们需要封装一个专用函数:`removeVNode`,该函数专门负责移除一个 `VNode`,它会判断该 `VNode` 的类型,并采用合适的方式将其所渲染的真实 DOM 移除。大家思考一下,如果将要被移除的 `VNode` 是一个组件的描述,那是否还应该在移除之前或之后分别调用 `beforeUnmount` 以及 `unmounted` 等生命周期钩子函数呢?答案当然是肯定的。不过,本节讲解的内容虽然存在不足,但至少思路是完全正确的,在此基础上,你可以发挥自己的想象或者结合真正 `Vue3` 的源码去进一步的提升。

## References

Expand Down

0 comments on commit 7469dd3

Please sign in to comment.