|
@@ -0,0 +1,441 @@
|
|
|
+## VUE原理
|
|
|
+
|
|
|
+### 响应式原理
|
|
|
+
|
|
|
+**检测data变化的核心API`Object.defindeProperty`**
|
|
|
+
|
|
|
+基本使用
|
|
|
+
|
|
|
+```javascript
|
|
|
+const data = {};
|
|
|
+let name = "张三";
|
|
|
+
|
|
|
+Object.defineProperty(data,'name',{
|
|
|
+ get:function(){
|
|
|
+ console.log('触发get')
|
|
|
+ return name
|
|
|
+ },
|
|
|
+ set:function(newVal){
|
|
|
+ console.log('触发set')
|
|
|
+ name=newVal
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+//测试
|
|
|
+console.log(data.name) // 触发get 张三
|
|
|
+data.name = '李四' // 触发set
|
|
|
+```
|
|
|
+
|
|
|
+### 虚拟DOOM -- diff算法
|
|
|
+
|
|
|
+1. 通过树进行比较
|
|
|
+
|
|
|
+`diff` 算法用来比较两棵 `Virtual DOM` 树的差异,如果需要两棵树的完全比较,那么 `diff` 算法的时间复杂度为`O(n^3)`。但是在前端当中,你很少会跨越层级地移动 `DOM` 元素,所以 `Virtual DOM` 只会对同一个层级的元素进行对比,如下图所示, `div` 只会和同一层级的 `div` 对比,第二层级的只会跟第二层级对比,这样算法复杂度就可以达到 `O(n)`。
|
|
|
+
|
|
|
+![img](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/7/23/16c1e26a5ecf086e~tplv-t2oaga2asx-jj-mark:3024:0:0:0:q75.awebp)
|
|
|
+
|
|
|
+2. 通过列表进行比较
|
|
|
+
|
|
|
+ 子节点的对比算法,例如 `p, ul, div` 的顺序换成了 `div, p, ul`。这个该怎么对比?如果按照同层级进行顺序对比的话,它们都会被替换掉。如 `p` 和 `div` 的 `tagName` 不同,`p` 会被 `div` 所替代。最终,三个节点都会被替换,这样 `DOM` 开销就非常大。而实际上是不需要替换节点,而只需要经过节点移动就可以达到,我们只需知道怎么进行移动。
|
|
|
+
|
|
|
+ 将这个问题抽象出来其实就是字符串的最小编辑距离问题(`Edition Distance`),最常见的解决方法是 `Levenshtein Distance` , `Levenshtein Distance` 是一个度量两个字符序列间差异的字符串度量标准,两个单词之间的 `Levenshtein Distance` 是将一个单词转换为另一个单词所需的单字符编辑(插入、删除或替换)的最小数量。`Levenshtein Distance` 是1965年由苏联数学家 Vladimir Levenshtein 发明的。`Levenshtein Distance` 也被称为编辑距离(`Edit Distance`),通过**动态规划**求解,时间复杂度为 `O(M*N)`。
|
|
|
+
|
|
|
+### vue源码
|
|
|
+
|
|
|
+vue的virtual dom借鉴了开源库snabbdom,其实主要属性
|
|
|
+
|
|
|
+* `tag` 属性即这个`vnode`的标签属性
|
|
|
+* `data` 属性包含了最后渲染成真实`dom`节点后,节点上的`class`,`attribute`,`style`以及绑定的事件
|
|
|
+* `children` 属性是`vnode`的子节点
|
|
|
+* `text` 属性是文本属性
|
|
|
+* `elm` 属性为这个`vnode`对应的真实`dom`节点
|
|
|
+* `key` 属性是`vnode`的标记,在`diff`过程中可以提高`diff`的效率
|
|
|
+
|
|
|
+### 原理解析
|
|
|
+
|
|
|
+虚拟dom原理流程
|
|
|
+
|
|
|
+> **模板 ==> 渲染函数 ==> 虚拟DOM树 ==> 真实DOM**
|
|
|
+
|
|
|
+* vuejs通过编译将模板(template)转成渲染函数(render),执行渲染函数可以得到一个虚拟节点树
|
|
|
+
|
|
|
+* 在对 Model 进行操作的时候,会触发对应 Dep 中的 Watcher 对象。Watcher 对象会调用对应的 update 来修改视图。
|
|
|
+
|
|
|
+虚拟 DOM 的实现原理主要包括以下 3 部分:
|
|
|
+
|
|
|
+* 用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象;
|
|
|
+* diff 算法 — 比较两棵虚拟 DOM 树的差异;
|
|
|
+* pach 算法 — 将两个虚拟 DOM 对象的差异应用到真正的 DOM 树。
|
|
|
+
|
|
|
+### 构建过程
|
|
|
+
|
|
|
+1. 初始化vue
|
|
|
+
|
|
|
+```javascript
|
|
|
+function Vue (options) {
|
|
|
+ if (process.env.NODE_ENV !== 'production' &&
|
|
|
+ !(this instanceof Vue)
|
|
|
+ ) {
|
|
|
+ warn('Vue is a constructor and should be called with the `new` keyword')
|
|
|
+ }
|
|
|
+ this._init(options)
|
|
|
+}
|
|
|
+// 通过查看 Vue 的 function,我们知道 Vue 只能通过 new 关键字初始化,然后调用 this._init 方法,该方法在 src/core/instance/init.js 中定义。
|
|
|
+
|
|
|
+Vue.prototype._init = function (options?: Object) {
|
|
|
+ const vm: Component = this
|
|
|
+
|
|
|
+ // 省略一系列其它初始化的代码
|
|
|
+
|
|
|
+ if (vm.$options.el) {
|
|
|
+ console.log('vm.$options.el:',vm.$options.el);
|
|
|
+ vm.$mount(vm.$options.el)
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+2. `Vue` 实例挂载
|
|
|
+
|
|
|
+```javascript
|
|
|
+const mount = Vue.prototype.$mount
|
|
|
+Vue.prototype.$mount = function (
|
|
|
+ el?: string | Element,
|
|
|
+ hydrating?: boolean
|
|
|
+): Component {
|
|
|
+ el = el && query(el)
|
|
|
+
|
|
|
+ // 省略一系列初始化以及逻辑判断代码
|
|
|
+
|
|
|
+ return mount.call(this, el, hydrating)
|
|
|
+}
|
|
|
+// 我们发现最终还是调用用原先原型上的 $mount 方法挂载 ,原先原型上的 $mount 方法在 src/platforms/web/runtime/index.js 中定义 。
|
|
|
+Vue.prototype.$mount = function (
|
|
|
+ el?: string | Element,
|
|
|
+ hydrating?: boolean
|
|
|
+): Component {
|
|
|
+ el = el && inBrowser ? query(el) : undefined
|
|
|
+ return mountComponent(this, el, hydrating)
|
|
|
+}
|
|
|
+// 我们发现$mount 方法实际上会去调用 mountComponent 方法,这个方法定义在 src/core/instance/lifecycle.js 文件中
|
|
|
+export function mountComponent (
|
|
|
+ vm: Component,
|
|
|
+ el: ?Element,
|
|
|
+ hydrating?: boolean
|
|
|
+): Component {
|
|
|
+ vm.$el = el
|
|
|
+ // 省略一系列其它代码
|
|
|
+ let updateComponent
|
|
|
+ /* istanbul ignore if */
|
|
|
+ if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
|
|
|
+ updateComponent = () => {
|
|
|
+ // 生成虚拟 vnode
|
|
|
+ const vnode = vm._render()
|
|
|
+ // 更新 DOM
|
|
|
+ vm._update(vnode, hydrating)
|
|
|
+
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ updateComponent = () => {
|
|
|
+ vm._update(vm._render(), hydrating)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 实例化一个渲染Watcher,在它的回调函数中会调用 updateComponent 方法
|
|
|
+ new Watcher(vm, updateComponent, noop, {
|
|
|
+ before () {
|
|
|
+ if (vm._isMounted && !vm._isDestroyed) {
|
|
|
+ callHook(vm, 'beforeUpdate')
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }, true /* isRenderWatcher */)
|
|
|
+ hydrating = false
|
|
|
+
|
|
|
+ return vm
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+3. 创建虚拟node
|
|
|
+
|
|
|
+```javascript
|
|
|
+// Vue 的 _render 方法是实例的一个私有方法,它用来把实例渲染成一个虚拟 Node。它的定义在 src/core/instance/render.js 文件中:
|
|
|
+Vue.prototype._render = function (): VNode {
|
|
|
+ const vm: Component = this
|
|
|
+ const { render, _parentVnode } = vm.$options
|
|
|
+ let vnode
|
|
|
+ try {
|
|
|
+ // 省略一系列代码
|
|
|
+ currentRenderingInstance = vm
|
|
|
+ // 调用 createElement 方法来返回 vnode
|
|
|
+ vnode = render.call(vm._renderProxy, vm.$createElement)
|
|
|
+ } catch (e) {
|
|
|
+ handleError(e, vm, `render`){}
|
|
|
+ }
|
|
|
+ // set parent
|
|
|
+ vnode.parent = _parentVnode
|
|
|
+ console.log("vnode...:",vnode);
|
|
|
+ return vnode
|
|
|
+ }
|
|
|
+// Vue.js 利用 _createElement 方法创建 VNode,它定义在 src/core/vdom/create-elemenet.js 中:
|
|
|
+export function _createElement (
|
|
|
+ context: Component,
|
|
|
+ tag?: string | Class<Component> | Function | Object,
|
|
|
+ data?: VNodeData,
|
|
|
+ children?: any,
|
|
|
+ normalizationType?: number
|
|
|
+): VNode | Array<VNode> {
|
|
|
+
|
|
|
+ // 省略一系列非主线代码
|
|
|
+
|
|
|
+ if (normalizationType === ALWAYS_NORMALIZE) {
|
|
|
+ // 场景是 render 函数不是编译生成的
|
|
|
+ children = normalizeChildren(children)
|
|
|
+ } else if (normalizationType === SIMPLE_NORMALIZE) {
|
|
|
+ // 场景是 render 函数是编译生成的
|
|
|
+ children = simpleNormalizeChildren(children)
|
|
|
+ }
|
|
|
+ let vnode, ns
|
|
|
+ if (typeof tag === 'string') {
|
|
|
+ let Ctor
|
|
|
+ ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
|
|
|
+ if (config.isReservedTag(tag)) {
|
|
|
+ // 创建虚拟 vnode
|
|
|
+ vnode = new VNode(
|
|
|
+ config.parsePlatformTagName(tag), data, children,
|
|
|
+ undefined, undefined, context
|
|
|
+ )
|
|
|
+ } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
|
|
|
+ // component
|
|
|
+ vnode = createComponent(Ctor, data, context, children, tag)
|
|
|
+ } else {
|
|
|
+ vnode = new VNode(
|
|
|
+ tag, data, children,
|
|
|
+ undefined, undefined, context
|
|
|
+ )
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ vnode = createComponent(tag, data, context, children)
|
|
|
+ }
|
|
|
+ if (Array.isArray(vnode)) {
|
|
|
+ return vnode
|
|
|
+ } else if (isDef(vnode)) {
|
|
|
+ if (isDef(ns)) applyNS(vnode, ns)
|
|
|
+ if (isDef(data)) registerDeepBindings(data)
|
|
|
+ return vnode
|
|
|
+ } else {
|
|
|
+ return createEmptyVNode()
|
|
|
+ }
|
|
|
+}
|
|
|
+// _createElement 方法有 5 个参数,context 表示 VNode 的上下文环境,它是 Component 类型;tag表示标签,它可以是一个字符串,也可以是一个 Component;data 表示 VNode 的数据,它是一个 VNodeData 类型,可以在 flow/vnode.js 中找到它的定义;children 表示当前 VNode 的子节点,它是任意类型的,需要被规范为标准的 VNode 数组;
|
|
|
+```
|
|
|
+
|
|
|
+### diff过程
|
|
|
+
|
|
|
+```javascript
|
|
|
+// Vue.js 源码实例化了一个 watcher,这个 ~ 被添加到了在模板当中所绑定变量的依赖当中,一旦 model 中的响应式的数据发生了变化,这些响应式的数据所维护的 dep 数组便会调用 dep.notify() 方法完成所有依赖遍历执行的工作,这包括视图的更新,即 updateComponent 方法的调用。watcher 和 updateComponent方法定义在 src/core/instance/lifecycle.js 文件中 。
|
|
|
+export function mountComponent (
|
|
|
+ vm: Component,
|
|
|
+ el: ?Element,
|
|
|
+ hydrating?: boolean
|
|
|
+): Component {
|
|
|
+ vm.$el = el
|
|
|
+ // 省略一系列其它代码
|
|
|
+ let updateComponent
|
|
|
+ /* istanbul ignore if */
|
|
|
+ if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
|
|
|
+ updateComponent = () => {
|
|
|
+ // 生成虚拟 vnode
|
|
|
+ const vnode = vm._render()
|
|
|
+ // 更新 DOM
|
|
|
+ vm._update(vnode, hydrating)
|
|
|
+
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ updateComponent = () => {
|
|
|
+ vm._update(vm._render(), hydrating)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 实例化一个渲染Watcher,在它的回调函数中会调用 updateComponent 方法
|
|
|
+ new Watcher(vm, updateComponent, noop, {
|
|
|
+ before () {
|
|
|
+ if (vm._isMounted && !vm._isDestroyed) {
|
|
|
+ callHook(vm, 'beforeUpdate')
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }, true /* isRenderWatcher */)
|
|
|
+ hydrating = false
|
|
|
+
|
|
|
+ return vm
|
|
|
+}
|
|
|
+// 完成视图的更新工作事实上就是调用了vm._update方法,这个方法接收的第一个参数是刚生成的Vnode,调用的vm._update方法定义在 src/core/instance/lifecycle.js中。
|
|
|
+ Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
|
|
|
+ const vm: Component = this
|
|
|
+ const prevEl = vm.$el
|
|
|
+ const prevVnode = vm._vnode
|
|
|
+ const restoreActiveInstance = setActiveInstance(vm)
|
|
|
+ vm._vnode = vnode
|
|
|
+ if (!prevVnode) {
|
|
|
+ // 第一个参数为真实的node节点,则为初始化
|
|
|
+ vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
|
|
|
+ } else {
|
|
|
+ // 如果需要diff的prevVnode存在,那么对prevVnode和vnode进行diff
|
|
|
+ vm.$el = vm.__patch__(prevVnode, vnode)
|
|
|
+ }
|
|
|
+ restoreActiveInstance()
|
|
|
+ // update __vue__ reference
|
|
|
+ if (prevEl) {
|
|
|
+ prevEl.__vue__ = null
|
|
|
+ }
|
|
|
+ if (vm.$el) {
|
|
|
+ vm.$el.__vue__ = vm
|
|
|
+ }
|
|
|
+ // if parent is an HOC, update its $el as well
|
|
|
+ if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
|
|
|
+ vm.$parent.$el = vm.$el
|
|
|
+ }
|
|
|
+ }
|
|
|
+// 在这个方法当中最为关键的就是 vm.__patch__ 方法,这也是整个 virtual-dom 当中最为核心的方法,主要完成了prevVnode 和 vnode 的 diff 过程并根据需要操作的 vdom 节点打 patch,最后生成新的真实 dom 节点并完成视图的更新工作。接下来,让我们看下 vm.__patch__的逻辑过程, vm.__patch__ 方法定义在 src/core/vdom/patch.js 中。
|
|
|
+function patch (oldVnode, vnode, hydrating, removeOnly) {
|
|
|
+ ......
|
|
|
+ if (isUndef(oldVnode)) {
|
|
|
+ // 当oldVnode不存在时,创建新的节点
|
|
|
+ isInitialPatch = true
|
|
|
+ createElm(vnode, insertedVnodeQueue)
|
|
|
+ } else {
|
|
|
+ // 对oldVnode和vnode进行diff,并对oldVnode打patch
|
|
|
+ const isRealElement = isDef(oldVnode.nodeType)
|
|
|
+ if (!isRealElement && sameVnode(oldVnode, vnode)) {
|
|
|
+ // patch existing root node
|
|
|
+ patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
|
|
|
+ }
|
|
|
+ ......
|
|
|
+ }
|
|
|
+}
|
|
|
+// 在 patch 方法中,我们看到会分为两种情况,一种是当 oldVnode 不存在时,会创建新的节点;另一种则是已经存在 oldVnode ,那么会对 oldVnode 和 vnode 进行 diff 及 patch 的过程。其中 patch 过程中会调用 sameVnode 方法来对对传入的2个 vnode 进行基本属性的比较,只有当基本属性相同的情况下才认为这个2个vnode 只是局部发生了更新,然后才会对这2个 vnode 进行 diff,如果2个 vnode 的基本属性存在不一致的情况,那么就会直接跳过 diff 的过程,进而依据 vnode 新建一个真实的 dom,同时删除老的 dom节点。
|
|
|
+
|
|
|
+function sameVnode (a, b) {
|
|
|
+ return (
|
|
|
+ a.key === b.key &&
|
|
|
+ a.tag === b.tag &&
|
|
|
+ a.isComment === b.isComment &&
|
|
|
+ isDef(a.data) === isDef(b.data) &&
|
|
|
+ sameInputType(a, b)
|
|
|
+ )
|
|
|
+}
|
|
|
+// diff 过程中主要是通过调用 patchVnode 方法进行的:
|
|
|
+ function patchVnode (oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly) {
|
|
|
+ ......
|
|
|
+ const elm = vnode.elm = oldVnode.elm
|
|
|
+ const oldCh = oldVnode.children
|
|
|
+ const ch = vnode.children
|
|
|
+ // 如果vnode没有文本节点
|
|
|
+ if (isUndef(vnode.text)) {
|
|
|
+ // 如果oldVnode的children属性存在且vnode的children属性也存在
|
|
|
+ if (isDef(oldCh) && isDef(ch)) {
|
|
|
+ // updateChildren,对子节点进行diff
|
|
|
+ if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
|
|
|
+ } else if (isDef(ch)) {
|
|
|
+ if (process.env.NODE_ENV !== 'production') {
|
|
|
+ checkDuplicateKeys(ch)
|
|
|
+ }
|
|
|
+ // 如果oldVnode的text存在,那么首先清空text的内容,然后将vnode的children添加进去
|
|
|
+ if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
|
|
|
+ addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
|
|
|
+ } else if (isDef(oldCh)) {
|
|
|
+ // 删除elm下的oldchildren
|
|
|
+ removeVnodes(elm, oldCh, 0, oldCh.length - 1)
|
|
|
+ } else if (isDef(oldVnode.text)) {
|
|
|
+ // oldVnode有子节点,而vnode没有,那么就清空这个节点
|
|
|
+ nodeOps.setTextContent(elm, '')
|
|
|
+ }
|
|
|
+ } else if (oldVnode.text !== vnode.text) {
|
|
|
+ // 如果oldVnode和vnode文本属性不同,那么直接更新真是dom节点的文本元素
|
|
|
+ nodeOps.setTextContent(elm, vnode.text)
|
|
|
+ }
|
|
|
+ ......
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+```
|
|
|
+
|
|
|
+从以上代码得知,
|
|
|
+
|
|
|
+`diff` 过程中又分了好几种情况,`oldCh` 为 `oldVnode`的子节点,`ch` 为 `Vnode`的子节点:
|
|
|
+
|
|
|
+- 首先进行文本节点的判断,若 `oldVnode.text !== vnode.text`,那么就会直接进行文本节点的替换;
|
|
|
+- 在`vnode` 没有文本节点的情况下,进入子节点的 `diff`;
|
|
|
+- 当 `oldCh` 和 `ch` 都存在且不相同的情况下,调用 `updateChildren` 对子节点进行 `diff`;
|
|
|
+- 若 `oldCh`不存在,`ch` 存在,首先清空 `oldVnode` 的文本节点,同时调用 `addVnodes` 方法将 `ch` 添加到`elm`真实 `dom` 节点当中;
|
|
|
+- 若 `oldCh`存在,`ch`不存在,则删除 `elm` 真实节点下的 `oldCh` 子节点;
|
|
|
+- 若 `oldVnode` 有文本节点,而 `vnode` 没有,那么就清空这个文本节点。
|
|
|
+
|
|
|
+### diff过程 --- 无key
|
|
|
+
|
|
|
+1. 首先从第一个节点开始比较,不管是 `oldCh` 还是 `newCh` 的起始或者终止节点都不存在 `sameVnode` ,同时节点属性中是不带 `key`标记的,因此第一轮的 `diff` 完后,`newCh`的 `startVnode` 被添加到 `oldStartVnode`的前面,同时 `newStartIndex`前移一位;
|
|
|
+
|
|
|
+![å¾çæè¿°](assets/16c1e0e2878c44dctplv-t2oaga2asx-jj-mark3024000q75.awebp)
|
|
|
+
|
|
|
+2. 第二轮的 `diff`中,满足 `sameVnode(oldStartVnode, newStartVnode)`,因此对这2个 `vnode` 进行`diff`,最后将 `patch` 打到 `oldStartVnode` 上,同时 `oldStartVnode`和 `newStartIndex` 都向前移动一位
|
|
|
+
|
|
|
+![å¾çæè¿°](assets/16c1e0e28889eafftplv-t2oaga2asx-jj-mark3024000q75.awebp)
|
|
|
+
|
|
|
+3. 第三轮的 `diff` 中,满足 `sameVnode(oldEndVnode, newStartVnode)`,那么首先对 `oldEndVnode`和`newStartVnode` 进行 `diff`,并对 `oldEndVnode`进行 `patch`,并完成 `oldEndVnode` 移位的操作,最后`newStartIndex`前移一位,`oldStartVnode` 后移一位;
|
|
|
+
|
|
|
+![å¾çæè¿°](assets/16c1e0e289a351b2tplv-t2oaga2asx-jj-mark3024000q75.awebp)
|
|
|
+
|
|
|
+4. 第四轮的 `diff`中,过程同步骤3;
|
|
|
+
|
|
|
+![å¾çæè¿°](assets/16c1e0e289f9213etplv-t2oaga2asx-jj-mark3024000q75.awebp)
|
|
|
+
|
|
|
+5. 第五轮的 `diff` 中,同过程1;
|
|
|
+
|
|
|
+![å¾çæè¿°](assets/16c1e0e28aee99a1tplv-t2oaga2asx-jj-mark3024000q75.awebp)
|
|
|
+
|
|
|
+6. 遍历的过程结束后,`newStartIdx > newEndIdx`,说明此时 `oldCh` 存在多余的节点,那么最后就需要将这些多余的节点删除。
|
|
|
+
|
|
|
+![å¾çæè¿°](assets/16c1e0e2ca893b49tplv-t2oaga2asx-jj-mark3024000q75.awebp)
|
|
|
+
|
|
|
+### diff过程 -- 有key
|
|
|
+
|
|
|
+1. 首先从第一个节点开始比较,不管是 `oldCh` 还是 `newCh` 的起始或者终止节点都不存在 `sameVnode`,但节点属性中是带 `key` 标记的, 然后在 `oldKeyToIndx` 中找到对应的节点,这样第一轮 `diff` 过后 `oldCh` 上的`B节点`被删除了,但是 `newCh` 上的`B节点`上 `elm` 属性保持对 `oldCh` 上 `B节点` 的`elm`引用。
|
|
|
+
|
|
|
+![å¾çæè¿°](assets/16c1e0e2db1c4812tplv-t2oaga2asx-jj-mark3024000q75-16934721993069.awebp)
|
|
|
+
|
|
|
+2. 第二轮的 `diff` 中,满足 `sameVnode(oldStartVnode, newStartVnode)`,因此对这2个 `vnode` 进行`diff`,最后将 `patch` 打到 `oldStartVnode`上,同时 `oldStartVnode` 和 `newStartIndex` 都向前移动一位
|
|
|
+
|
|
|
+![å¾çæè¿°](assets/16c1e0e2d7df4fbftplv-t2oaga2asx-jj-mark3024000q75.awebp)
|
|
|
+
|
|
|
+3. 第三轮的 `diff`中,满足 `sameVnode(oldEndVnode, newStartVnode)`,那么首先对 `oldEndVnode` 和`newStartVnode` 进行 `diff`,并对 `oldEndVnode` 进行 `patch`,并完成 `oldEndVnode` 移位的操作,最后`newStartIndex` 前移一位,`oldStartVnode`后移一位;
|
|
|
+
|
|
|
+![å¾çæè¿°](assets/16c1e0e2e2a2835etplv-t2oaga2asx-jj-mark3024000q75.awebp)
|
|
|
+
|
|
|
+4. 第四轮的`diff`中,过程同步骤2;
|
|
|
+
|
|
|
+![å¾çæè¿°](assets/16c1e0e2e507aec0tplv-t2oaga2asx-jj-mark3024000q75.awebp)
|
|
|
+
|
|
|
+5. 第五轮的`diff`中,因为此时 `oldStartIndex` 已经大于 `oldEndIndex`,所以将剩余的 `Vnode` 队列插入队列最后。
|
|
|
+
|
|
|
+![å¾çæè¿°](assets/16c1e0e3178398fctplv-t2oaga2asx-jj-mark3024000q75.awebp)
|
|
|
+
|
|
|
+## vue生命周期
|
|
|
+
|
|
|
+![在这里插入图片描述](assets/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3N1MjIzMTU5NTc0Mg==,size_16,color_FFFFFF,t_70-169347279895916.png)
|
|
|
+
|
|
|
+生命周期: vue实例从创建到销毁的过程。
|
|
|
+
|
|
|
+声明周期钩子: 就是生命周期事件的别名而已
|
|
|
+
|
|
|
+主要的生命周期函数分类:
|
|
|
+
|
|
|
+* 创建期间的生命周期函数:
|
|
|
+ * beforeCreate:实例刚在内存中被创建出来,此时,还没有初始化好data 和 methods 属性
|
|
|
+ * created:实例已经完成了模板的编译,但是还没有挂载到页面中
|
|
|
+ * beforeMount:此时已经完成了模板的翻译,但是还有完全挂载到页面中
|
|
|
+ * mounted:此时,已经将编译好的模板,挂载到了页面指定的容器中显示
|
|
|
+* 运行期间的生命周期函数:
|
|
|
+ * beforeUpdate:状态更新之前执行此函数,此时 data 中的状态值是最新的,但是界面上显示的数据还是旧的,因为此时还没有开始重新渲染DOM节点
|
|
|
+ * updated:实例更新完毕之后调用次函数,此时 data 中的状态值 和 界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了
|
|
|
+* 销毁期间的生命周期函数:
|
|
|
+ * beforeDestroy:实例销毁之前调用,在这一步,实例仍然完全可用
|
|
|
+ 当执行 beforeDestroy 钩子函数的时候,Vue实例就已经从运行阶段进入到了销毁阶段;当执行 beforeDestroy 的时候,实例身上所有的 data 和所有的 methods, 以及 过滤器、指令、、 都处于可用状态,此时,还没有真正执行销毁的过程
|
|
|
+ * destroyed:Vue 实例销毁后调用。调用后,vue 实例 指示的所有东西都会解绑,所有的事件监听器会被移除,所有的子实例也会被销毁
|