Loading [MathJax]/jax/output/CommonHTML/jax.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >[面试] 聊聊你对 Vue.js 框架的理解

[面试] 聊聊你对 Vue.js 框架的理解

作者头像
前端老王
发布于 2020-12-14 02:18:59
发布于 2020-12-14 02:18:59
5K00
代码可运行
举报
文章被收录于专栏:前端时空前端时空
运行总次数:0
代码可运行
作者:yacan8

https://github.com/yacan8/blog/issues/26

本文为一次前端技术分享的演讲稿,所以尽力不贴 Vue.js 的源码,因为贴代码在实际分享中,比较枯燥,效果不佳,而更多的是以图片和文字的形式进行表达。

分享目标:

  • 了解 Vue.js 的组件化机制
  • 了解 Vue.js 的响应式系统原理
  • 了解 Vue.js 中的 Virtual DOM 及 Diff 原理

分享keynote:Vue.js框架原理剖析.key

Vue.js概述

Vue 是一套用于构建用户界面的渐进式MVVM框架。那怎么理解渐进式呢?渐进式含义:强制主张最少。

渐进式概念

Vue.js包含了声明式渲染、组件化系统、客户端路由、大规模状态管理、构建工具、数据持久化、跨平台支持等,但在实际开发中,并没有强制要求开发者之后某一特定功能,而是根据需求逐渐扩展。

Vue.js的核心库只关心视图渲染,且由于渐进式的特性,Vue.js便于与第三方库或既有项目整合。

组件机制

定义:组件就是对一个功能和样式进行独立的封装,让HTML元素得到扩展,从而使得代码得到复用,使得开发灵活,更加高效。

与HTML元素一样,Vue.js的组件拥有外部传入的属性(prop)和事件,除此之外,组件还拥有自己的状态(data)和通过数据和状态计算出来的计算属性(computed),各个维度组合起来决定组件最终呈现的样子与交互的逻辑。

数据传递

每一个组件之间的作用域是孤立的,这个意味着组件之间的数据不应该出现引用关系,即使出现了引用关系,也不允许组件操作组件内部以外的其他数据。Vue中,允许向组件内部传递prop数据,组件内部需要显性地声明该prop字段,如下声明一个child组件:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!-- child.vue -->
<template>
    <div>{{msg}}</div>
</template>
<script>
export default {
    props: {
        msg: {
            type: String,
            default: 'hello world' // 当default为引用类型时,需要使用 function 形式返回
        }
    }
}
</script>

父组件向该组件传递数据:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!-- parent.vue -->
<template>
    <child :msg="parentMsg"></child>
</template>
<script>
import child from './child';
export default {
    components: {
        child
    },
    data () {
        return {
            parentMsg: 'some words'
        }
    }
}
</script>

事件传递

Vue内部实现了一个事件总线系统,即EventBus。在Vue中可以使用 EventBus 来作为沟通桥梁的概念,每一个Vue的组件实例都继承了 EventBus,都可以接受事件emit。

如上面一个例子,child.vue 组件想修改 parent.vue 组件的 parentMsg 数据,怎么办呢?为了保证数据流的可追溯性,直接修改组件内 prop 的 msg 字段是不提倡的,且例子中为非引用类型 String,直接修改也修改不了,这个时候需要将修改 parentMsg 的事件传递给 child.vue,让 child.vue 来触发修改 parentMsg 的事件。如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!-- child.vue -->
<template>
    <div>{{msg}}</div>
</template>
<script>
export default {
    props: {
        msg: {
            type: String,
            default: 'hello world'
        }
    },
    methods: {
        changeMsg(newMsg) {
            this.$emit('updateMsg', newMsg);
        }
    }
}
</script>

父组件:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!-- parent.vue -->
<template>
    <child :msg="parentMsg" @updateMsg="changeParentMsg"></child>
</template>
<script>
import child from './child';
export default {
    components: {
        child
    },
    data () {
        return {
            parentMsg: 'some words'
        }
    },
    methods: {
        changeParentMsg: function (newMsg) {
            this.parentMsg = newMsg
        }
    }
}
</script>

父组件 parent.vue 向子组件 child.vue 传递了 updateMsg 事件,在子组件实例化的时候,子组件将 updateMsg 事件使用emit来触发事件。

除了父子组件之间的事件传递,还可以使用一个 Vue 实例为多层级的父子组件建立数据通信的桥梁,如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const eventBus = new Vue();

// 父组件中使用$on监听事件
eventBus.$on('eventName', val => {
    //  ...do something
})

// 子组件使用$emit触发事件
eventBus.$emit('eventName', 'this is a message.');

除了emit以外,事件总线系统还提供了另外两个方法,off,所有事件如下:

  • $on:监听、注册事件。
  • $emit:触发事件。
  • $once:注册事件,仅允许该事件触发一次,触发结束后立即移除事件。
  • $off:移除事件。

内容分发

Vue实现了一套遵循 Web Components 规范草案 的内容分发系统,即将<slot>元素作为承载分发内容的出口。

插槽slot,也是组件的一块HTML模板,这一块模板显示不显示、以及怎样显示由父组件来决定。实际上,一个slot最核心的两个问题在这里就点出来了,是显示不显示和怎样显示。

插槽又分默认插槽、具名插槽。

默认插槽

又名单个插槽、匿名插槽,与具名插槽相对,这类插槽没有具体名字,一个组件只能有一个该类插槽。

如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<template>
<!-- 父组件 parent.vue -->
<div class="parent">
    <h1>父容器</h1>
    <child>
        <div class="tmpl">
            <span>菜单1</span>
        </div>
    </child>
</div>
</template>

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<template>
<!-- 子组件 child.vue -->
<div class="child">
    <h1>子组件</h1>
    <slot></slot>
</div>
</template>

如上,渲染时子组件的slot标签会被父组件传入的div.tmpl替换。

具名插槽

匿名插槽没有name属性,所以叫匿名插槽。那么,插槽加了name属性,就变成了具名插槽。具名插槽可以在一个组件中出现N次,出现在不同的位置,只需要使用不同的name属性区分即可。

如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<template>
<!-- 父组件 parent.vue -->
<div class="parent">
    <h1>父容器</h1>
    <child>
        <div class="tmpl" slot="up">
            <span>菜单up-1</span>
        </div>
        <div class="tmpl" slot="down">
            <span>菜单down-1</span>
        </div>
        <div class="tmpl">
            <span>菜单->1</span>
        </div>
    </child>
</div>
</template>

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<template>
    <div class="child">
        <!-- 具名插槽 -->
        <slot name="up"></slot>
        <h3>这里是子组件</h3>
        <!-- 具名插槽 -->
        <slot name="down"></slot>
        <!-- 匿名插槽 -->
        <slot></slot>
    </div>
</template>

如上,slot 标签会根据父容器给 child 标签内传入的内容的 slot 属性值,替换对应的内容。

其实,默认插槽也有 name 属性值,为default,同样指定 slot 的 name 值为 default,一样可以显示父组件中传入的没有指定slot的内容。

作用域插槽

作用域插槽可以是默认插槽,也可以是具名插槽,不一样的地方是,作用域插槽可以为 slot 标签绑定数据,让其父组件可以获取到子组件的数据。

如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<template>
    <!-- parent.vue -->
    <div class="parent">
        <h1>这是父组件</h1>
        <current-user>
            <template slot="default" slot-scope="slotProps">
                {{ slotProps.user.name }}
            </template>
        </current-user>
    </div>
</template>

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<template>
    <!-- child.vue -->
    <div class="child">
        <h1>这是子组件</h1>
        <slot :user="user"></slot>
    </div>
</template>
<script>
export default {
    data() {
        return {
            user: {
                name: '小赵'
            }
        }
    }
}
</script>

如上例子,子组件 child 在渲染默认插槽 slot 的时候,将数据 user 传递给了 slot 标签,在渲染过程中,父组件可以通过slot-scope属性获取到 user 数据并渲染视图。

slot 实现原理:当子组件vm实例化时,获取到父组件传入的 slot 标签的内容,存放在vm.slot.default,具名插槽为vm.

至此,父子组件的关系如下图:

父子组件关系图

模板渲染

Vue.js 的核心是声明式渲染,与命令式渲染不同,声明式渲染只需要告诉程序,我们想要的什么效果,其他的事情让程序自己去做。而命令式渲染,需要命令程序一步一步根据命令执行渲染。如下例子区分:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var arr = [1, 2, 3, 4, 5];

// 命令式渲染,关心每一步、关心流程。用命令去实现
var newArr = [];
for (var i = 0; i < arr.length; i++) {
    newArr.push(arr[i] * 2);
}

// 声明式渲染,不用关心中间流程,只需要关心结果和实现的条件
var newArr1 = arr.map(function (item) {
    return item * 2;
});

Vue.js 实现了if、for、事件、数据绑定等指令,允许采用简洁的模板语法来声明式地将数据渲染出视图。

模板编译

为什么要进行模板编译?实际上,我们组件中的 template 语法是无法被浏览器解析的,因为它不是正确的 HTML 语法,而模板编译,就是将组件的 template 编译成可执行的 JavaScript 代码,即将 template 转化为真正的渲染函数。

模板编译分三个阶段,parseoptimizegenerate,最终生成render函数。

模板编译

parse阶段:使用正在表达式将template进行字符串解析,得到指令、class、style等数据,生成抽象语法树 AST。

optimize阶段:寻找 AST 中的静态节点进行标记,为后面 VNode 的 patch 过程中对比做优化。被标记为 static 的节点在后面的 diff 算法中会被直接忽略,不做详细的比较。

generate阶段:根据 AST 结构拼接生成 render 函数的字符串。

预编译

对于 Vue 组件来说,模板编译只会在组件实例化的时候编译一次,生成渲染函数之后在也不会进行编译。因此,编译对组件的 runtime 是一种性能损耗。而模板编译的目的仅仅是将template转化为render function,而这个过程,正好可以在项目构建的过程中完成。

比如webpackvue-loader依赖了vue-template-compiler模块,在 webpack 构建过程中,将template预编译成 render 函数,在 runtime 可直接跳过模板编译过程。

回过头看,runtime 需要是仅仅是 render 函数,而我们有了预编译之后,我们只需要保证构建过程中生成 render 函数就可以。与 React 类似,在添加JSX的语法糖编译器babel-plugin-transform-vue-jsx之后,我们可以在 Vue 组件中使用JSX语法直接书写 render 函数。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<script>
export default {
    data() {
        return {
            msg: 'Hello JSX.'
        }
    },
    render() {
        const msg = this.msg;
        return <div>
            {msg}
        </div>;
    }
}
</script>

如上面组件,使用 JSX 之后,可以在 JS 代码中直接使用 html 标签,而且声明了 render 函数以后,我们不再需要声明 template。当然,假如我们同时声明了 template 标签和 render 函数,构建过程中,template 编译的结果将覆盖原有的 render 函数,即 template 的优先级高于直接书写的 render 函数。

相对于 template 而言,JSX 具有更高的灵活性,面对与一些复杂的组件来说,JSX 有着天然的优势,而 template 虽然显得有些呆滞,但是代码结构上更符合视图与逻辑分离的习惯,更简单、更直观、更好维护。

需要注意的是,最后生成的 render 函数是被包裹在with语法中运行的。

小结

Vue 组件通过 prop 进行数据传递,并实现了数据总线系统EventBus,组件集成了EventBus进行事件注册监听、事件触发,使用slot进行内容分发。

除此以外,实现了一套声明式模板系统,在runtime或者预编译是对模板进行编译,生成渲染函数,供组件渲染视图使用。

响应式系统

Vue.js 是一款 MVVM 的JS框架,当对数据模型data进行修改时,视图会自动得到更新,即框架帮我们完成了更新DOM的操作,而不需要我们手动的操作DOM。可以这么理解,当我们对数据进行赋值的时候,Vue 告诉了所有依赖该数据模型的组件,你依赖的数据有更新,你需要进行重渲染了,这个时候,组件就会重渲染,完成了视图的更新。

数据模型 && 计算属性 && 监听器

在组件中,可以为每个组件定义数据模型data、计算属性computed、监听器watch

数据模型:Vue 实例在创建过程中,对数据模型data的每一个属性加入到响应式系统中,当数据被更改时,视图将得到响应,同步更新。data必须采用函数的方式 return,不使用 return 包裹的数据会在项目的全局可见,会造成变量污染;使用return包裹后数据中变量只在当前组件中生效,不会影响其他组件。

计算属性:computed基于组件响应式依赖进行计算得到结果并缓存起来。只在相关响应式依赖发生改变时它们才会重新求值,也就是说,只有它依赖的响应式数据(data、prop、computed本身)发生变化了才会重新计算。那什么时候应该使用计算属性呢?模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。对于任何复杂逻辑,你都应当使用计算属性。

监听器:监听器watch作用如其名,它可以监听响应式数据的变化,响应式数据包括 data、prop、computed,当响应式数据发生变化时,可以做出相应的处理。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

响应式原理

在 Vue 中,数据模型下的所有属性,会被 Vue 使用Object.defineProperty(Vue3.0 使用 Proxy)进行数据劫持代理。响应式的核心机制是观察者模式,数据是被观察的一方,一旦发生变化,通知所有观察者,这样观察者可以做出响应,比如当观察者为视图时,视图可以做出视图的更新。

Vue.js 的响应式系统以来三个重要的概念,ObserverDepWatcher

发布者-Observer

Observe 扮演的角色是发布者,他的主要作用是在组件vm初始化的时,调用defineReactive函数,使用Object.defineProperty方法对对象的每一个子属性进行数据劫持/监听,即为每个属性添加gettersetter,将对应的属性值变成响应式。

在组件初始化时,调用initState函数,内部执行initStateinitPropsinitComputed方法,分别对datapropcomputed进行初始化,让其变成响应式。

初始化props时,对所有props进行遍历,调用defineReactive函数,将每个 prop 属性值变成响应式,然后将其挂载到_props中,然后通过代理,把vm.xxx代理到vm._props.xxx中。

同理,初始化data时,与prop相同,对所有data进行遍历,调用defineReactive函数,将每个 data 属性值变成响应式,然后将其挂载到_data中,然后通过代理,把vm.xxx代理到vm._data.xxx中。

初始化computed,首先创建一个观察者对象computed-watcher,然后遍历computed的每一个属性,对每一个属性值调用defineComputed方法,使用Object.defineProperty将其变成响应式的同时,将其代理到组件实例上,即可通过vm.xxx访问到xxx计算属性。

调度中心/订阅器-Dep

Dep 扮演的角色是调度中心/订阅器,在调用defineReactive将属性值变成响应式的过程中,也为每个属性值实例化了一个Dep,主要作用是对观察者(Watcher)进行管理,收集观察者和通知观察者目标更新,即当属性值数据发生改变时,会遍历观察者列表(dep.subs),通知所有的 watcher,让订阅者执行自己的update逻辑。

dep的任务是,在属性的getter方法中,调用dep.depend()方法,将观察者(即 Watcher,可能是组件的render function,可能是 computed,也可能是属性监听 watch)保存在内部,完成其依赖收集。在属性的setter方法中,调用dep.notify()方法,通知所有观察者执行更新,完成派发更新。

观察者-Watcher

Watcher 扮演的角色是订阅者/观察者,他的主要作用是为观察属性提供回调函数以及收集依赖,当被观察的值发生变化时,会接收到来自调度中心Dep的通知,从而触发回调函数。

Watcher又分为三类,normal-watchercomputed-watcherrender-watcher

  • normal-watcher:在组件钩子函数watch中定义,即监听的属性改变了,都会触发定义好的回调函数。
  • computed-watcher:在组件钩子函数computed中定义的,每一个computed属性,最后都会生成一个对应的Watcher对象,但是这类Watcher有个特点:当计算属性依赖于其他数据时,属性并不会立即重新计算,只有之后其他地方需要读取属性的时候,它才会真正计算,即具备lazy(懒计算)特性。
  • render-watcher:每一个组件都会有一个render-watcher, 当data/computed中的属性改变的时候,会调用该Watcher来更新组件的视图。

这三种Watcher也有固定的执行顺序,分别是:computed-render -> normal-watcher -> render-watcher。这样就能尽可能的保证,在更新组件视图的时候,computed 属性已经是最新值了,如果 render-watcher 排在 computed-render 前面,就会导致页面更新的时候 computed 值为旧数据。

小结

响应式系统

Observer 负责将数据进行拦截,Watcher 负责订阅,观察数据变化, Dep 负责接收订阅并通知 Observer 和接收发布并通知所有 Watcher。

Virtual DOM

在 Vue 中,template被编译成浏览器可执行的render function,然后配合响应式系统,将render function挂载在render-watcher中,当有数据更改的时候,调度中心Dep通知该render-watcher执行render function,完成视图的渲染与更新。

DOM更新

整个流程看似通顺,但是当执行render function时,如果每次都全量删除并重建 DOM,这对执行性能来说,无疑是一种巨大的损耗,因为我们知道,浏览器的DOM很“昂贵”的,当我们频繁的更新 DOM,会产生一定的性能问题。

为了解决这个问题,Vue 使用 JS 对象将浏览器的 DOM 进行的抽象,这个抽象被称为 Virtual DOM。Virtual DOM 的每个节点被定义为VNode,当每次执行render function时,Vue 对更新前后的VNode进行Diff对比,找出尽可能少的我们需要更新的真实 DOM 节点,然后只更新需要更新的节点,从而解决频繁更新 DOM 产生的性能问题。

VNode

VNode,全称virtual node,即虚拟节点,对真实 DOM 节点的虚拟描述,在 Vue 的每一个组件实例中,会挂载一个$createElement函数,所有的VNode都是由这个函数创建的。

比如创建一个 div:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 声明 render function
render: function (createElement) {
    // 也可以使用 this.$createElement 创建 VNode
    return createElement('div', 'hellow world');
}
// 以上 render 方法返回html片段 <div>hellow world</div>

render 函数执行后,会根据VNode Tree将 VNode 映射生成真实 DOM,从而完成视图的渲染。

Diff

Diff 将新老 VNode 节点进行比对,然后将根据两者的比较结果进行最小单位地修改视图,而不是将整个视图根据新的 VNode 重绘,进而达到提升性能的目的。

patch

Vue.js 内部的 diff 被称为patch。其 diff 算法的是通过同层的树节点进行比较,而非对树进行逐层搜索遍历的方式,所以时间复杂度只有O(n),是一种相当高效的算法。

DIFF

首先定义新老节点是否相同判定函数sameVnode:满足键值key和标签名tag必须一致等条件,返回true,否则false

在进行patch之前,新老 VNode 是否满足条件sameVnode(oldVnode, newVnode),满足条件之后,进入流程patchVnode,否则被判定为不相同节点,此时会移除老节点,创建新节点。

patchVnode

patchVnode 的主要作用是判定如何对子节点进行更新,

  1. 如果新旧VNode都是静态的,同时它们的key相同(代表同一节点),并且新的 VNode 是 clone 或者是标记了 once(标记v-once属性,只渲染一次),那么只需要替换 DOM 以及 VNode 即可。
  2. 新老节点均有子节点,则对子节点进行 diff 操作,进行updateChildren,这个 updateChildren 也是 diff 的核心。
  3. 如果老节点没有子节点而新节点存在子节点,先清空老节点 DOM 的文本内容,然后为当前 DOM 节点加入子节点。
  4. 当新节点没有子节点而老节点有子节点的时候,则移除该 DOM 节点的所有子节点。
  5. 当新老节点都无子节点的时候,只是文本的替换。
updateChildren

Diff 的核心,对比新老子节点数据,判定如何对子节点进行操作,在对比过程中,由于老的子节点存在对当前真实 DOM 的引用,新的子节点只是一个 VNode 数组,所以在进行遍历的过程中,若发现需要更新真实 DOM 的地方,则会直接在老的子节点上进行真实 DOM 的操作,等到遍历结束,新老子节点则已同步结束。

updateChildren内部定义了4个变量,分别是oldStartIdxoldEndIdxnewStartIdxnewEndIdx,分别表示正在 Diff 对比的新老子节点的左右边界点索引,在老子节点数组中,索引在oldStartIdxoldEndIdx中间的节点,表示老子节点中为被遍历处理的节点,所以小于oldStartIdx或大于oldEndIdx的表示未被遍历处理的节点。同理,在新的子节点数组中,索引在newStartIdxnewEndIdx中间的节点,表示老子节点中为被遍历处理的节点,所以小于newStartIdx或大于newEndIdx的表示未被遍历处理的节点。

每一次遍历,oldStartIdxoldEndIdxnewStartIdxnewEndIdx之间的距离会向中间靠拢。当 oldStartIdx > oldEndIdx 或者 newStartIdx > newEndIdx 时结束循环。

img

在遍历中,取出4索引对应的 Vnode节点:

  • oldStartIdx:oldStartVnode
  • oldEndIdx:oldEndVnode
  • newStartIdx:newStartVnode
  • newEndIdx:newEndVnode

diff 过程中,如果存在key,并且满足sameVnode,会将该 DOM 节点进行复用,否则则会创建一个新的 DOM 节点。

首先,oldStartVnodeoldEndVnodenewStartVnodenewEndVnode两两比较,一共有 2*2=4 种比较方法。

情况一:当oldStartVnodenewStartVnode满足 sameVnode,则oldStartVnodenewStartVnode进行 patchVnode,并且oldStartIdxnewStartIdx右移动。

情况二:与情况一类似,当oldEndVnodenewEndVnode满足 sameVnode,则oldEndVnodenewEndVnode进行 patchVnode,并且oldEndIdxnewEndIdx左移动。

情况三:当oldStartVnodenewEndVnode满足 sameVnode,则说明oldStartVnode已经跑到了oldEndVnode后面去了,此时oldStartVnodenewEndVnode进行 patchVnode 的同时,还需要将oldStartVnode的真实 DOM 节点移动到oldEndVnode的后面,并且oldStartIdx右移,newEndIdx左移。

情况四:与情况三类似,当oldEndVnodenewStartVnode满足 sameVnode,则说明oldEndVnode已经跑到了oldStartVnode前面去了,此时oldEndVnodenewStartVnode进行 patchVnode 的同时,还需要将oldEndVnode的真实 DOM 节点移动到oldStartVnode的前面,并且oldStartIdx右移,newEndIdx左移。

当这四种情况都不满足,则在oldStartIdxoldEndIdx之间查找与newStartVnode满足sameVnode的节点,若存在,则将匹配的节点真实 DOM 移动到oldStartVnode的前面。

若不存在,说明newStartVnode为新节点,创建新节点放在oldStartVnode前面即可。

当 oldStartIdx > oldEndIdx 或者 newStartIdx > newEndIdx,循环结束,这个时候我们需要处理那些未被遍历到的 VNode。

当 oldStartIdx > oldEndIdx 时,说明老的节点已经遍历完,而新的节点没遍历完,这个时候需要将新的节点创建之后放在oldEndVnode后面。

当 newStartIdx > newEndIdx 时,说明新的节点已经遍历完,而老的节点没遍历完,这个时候要将没遍历的老的节点全都删除。

总结

借用官方的一幅图:

Vue.js 实现了一套声明式渲染引擎,并在runtime或者预编译时将声明式的模板编译成渲染函数,挂载在观察者 Watcher 中,在渲染函数中(touch),响应式系统使用响应式数据的getter方法对观察者进行依赖收集(Collect as Dependency),使用响应式数据的setter方法通知(notify)所有观察者进行更新,此时观察者 Watcher 会触发组件的渲染函数(Trigger re-render),组件执行的 render 函数,生成一个新的 Virtual DOM Tree,此时 Vue 会对新老 Virtual DOM Tree 进行 Diff,查找出需要操作的真实 DOM 并对其进行更新。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-12-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端时空 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Vue 2 常见面试题速查
当一个 Vue 实例创建时,Vue 会遍历 data 中的属性,用 Object.defineProperty 将它们转为 getter / setter,并且在内部追踪相关依赖,在属性被访问和修改时通知变化。每个组件实例都有相应的 watcher 程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。
Cellinlab
2023/05/17
1.2K0
Vue 2 常见面试题速查
前端二面vue面试题总结_2023-03-01
在 Vue3.0 中变量必须 return 出来, template 中才能使用;而在 Vue3.2 中只需要在 script 标签上加上 setup 属性,无需 return, template 便可直接使用,非常的香啊!
用户10377014
2023/03/01
7970
2023前端vue面试题及答案_2023-02-28
在 Vue2 中, 0bject.defineProperty 会改变原始数据,而 Proxy 是创建对象的虚拟表示,并提供 set 、get 和 deleteProperty 等处理器,这些处理器可在访问或修改原始对象上的属性时进行拦截,有以下特点∶
用户10358241
2023/02/28
1.8K0
vue面试常见考察点总结
beforeCreate 在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。在当前阶段 data、methods、computed 以及 watch 上的数据和方法都不能被访问
bb_xiaxia1998
2022/10/13
8550
那些年曾经没回答上来的vue面试题
Vue3.x 改用 Proxy 替代 Object.defineProperty。因为 Proxy 可以直接监听对象和数组的变化,并且有多达 13 种拦截方法。
bb_xiaxia1998
2022/09/26
5250
字节前端一面常见vue面试题(必备)_2023-02-28
Vue 的编译过程就是将 template 转化为 render 函数的过程 分为以下三步
用户10377014
2023/02/28
6150
图文解析vue2.0的diff算法
vue2.0加入了virtual dom,有向react靠拢的意思。vue的diff位于patch.js文件中,我的一个小框架aoy也同样使用此算法,该算法来源于snabbdom,复杂度为O(n)。 了解diff过程可以让我们更高效的使用框架。 本文力求以图文并茂的方式来讲明这个diff的过程。
前端迷
2019/08/15
7070
Vue(v2.6.11)万行源码生啃,就硬刚!
众所周知,以下代码就是 vue 的一种直接上手方式。通过 cdn 可以在线打开 vue.js。一个文件,一万行源码,是万千开发者赖以生存的利器,它究竟做了什么?让人品味。
掘金安东尼
2024/01/27
4010
Vue(v2.6.11)万行源码生啃,就硬刚!
浅析 Snabbdom 中 vnode 和 diff 算法
目前前端使用最多的就是 vue 或 react 了,我们在学习这两个框架的过程中,总有一个绕不开的话题:vnode,也就是虚拟 DOM。什么是虚拟 DOM,引用一段 vue 官方的解释就是:
政采云前端团队
2022/03/29
7380
浅析 Snabbdom 中 vnode 和 diff 算法
Diff算法核心原理
讲Diff算法前,我先给大家讲一讲什么是虚拟DOM吧。这有利于后面大家对Diff算法的理解加深。
花落花相惜
2021/12/15
1K0
Diff算法核心原理
一直是我写文章的宗旨,今天我就用通俗的方式来讲解一下Diff算法吧?Lets Go
ruochen
2021/11/21
6100
前端常见vue面试题合集
通过webpack的tree-shaking功能,可以将无用模块“剪辑”,仅打包需要的
bb_xiaxia1998
2022/11/09
7320
理解DOM Diff算法
虚拟 DOM 出现的背景:在 jQuery 时代,可以自行控制 DOM 操作的时机,手动调整,但是当项目很大时,操作 DOM 的复杂度就会上来,DOM 操作会很耗费性能,操作 DOM 就还需要考虑优化 DOM 操作,提升性能。《高性能 JavaScript》这本书中说,把 DOM 和 JavaScript 各自想象成一个岛屿,它们之间用收费桥梁连接。操作 DOM 后需要经过跨流程通信和渲染线程触发的重新渲染(重绘或者重排),在开发中,应尽量减少操作 DOM。而虚拟 DOM 出现后,更新 DOM 交给框架处理。操作虚拟 DOM 可能并没有操作真实 DOM 快,但是它让开发人员不再把很多精力放在操作 DOM 上,而是专注于处理业务数据。本文以 Vue 原码中的 DOM diff 算法为例,介绍一下这个算法的实现原理。
多云转晴
2020/07/29
1.1K0
理解DOM Diff算法
美团前端常见vue面试题(必备)_2023-02-28
(1)作用在表单元素上 动态绑定了 input 的 value 指向了 messgae 变量,并且在触发 input 事件的时候去动态把 message设置为目标值:
用户10377014
2023/02/28
7270
​vue源码分析前置知识必备
最近利用空闲时间又翻看了一遍Vue的源码,只不过这次不同的是看了Flow版本的源码。说来惭愧,最早看的第一遍时对Flow不了解,因此阅读的是打包之后的vue文件,大家可以想象这过程的痛苦,没有类型的支持,看代码时摸索了很长时间,所以我们这次对Vue源码的剖析是Flow版本的源码,也就是从Github上下载下来的源码中src目录下的代码。不过,在分析之前,我想先说说阅读Vue源码所需要的一些知识点,掌握这些知识点之后,相信再阅读源码会较为轻松。
前端老鸟
2019/07/29
6350
「源码级回答」大厂高频Vue面试题(中)
本篇是「源码级回答」大厂高频Vue面试题系列的第二篇,本篇也是选择了面试中经常会问到的一些经典面试题,从源码角度去分析。
前端森林
2020/05/21
9900
「源码级回答」大厂高频Vue面试题(中)
【揭秘Vue核心】为什么不建议在 v-for 指令中使用 index 作为 key,让你秒懂!
Vue 默认按照“就地更新”的策略来更新通过 v-for 渲染的元素列表。当数据项的顺序改变时,Vue 不会随之移动 DOM 元素的顺序,而是就地更新每个元素,确保它们在原本指定的索引位置上渲染。
奋飛
2023/07/24
2950
【揭秘Vue核心】为什么不建议在 v-for 指令中使用 index 作为 key,让你秒懂!
你不知道的React 和 Vue 的20个区别【源码层面】
面试竞争力越来越大,是时候撸一波Vue和React源码啦; 本文从20个层面来对比Vue和React的源码区别; 文章源码:请戳,原创码字不易,欢迎star!
火狼1
2020/07/08
1.5K0
你不知道的React 和 Vue 的20个区别【源码层面】
面试官:了解过vue中的diff算法吗?说说看
diff 算法的在很多场景下都有应用,在 vue 中,作用于虚拟 dom 渲染成真实 dom 的新旧 VNode 节点比较
@超人
2021/02/26
7560
面试官:了解过vue中的diff算法吗?说说看
VUE中diff比较
要知道渲染真实DOM的开销是很大的,比如有时候我们修改了某个数据,如果直接渲染到真实dom上会引起整个dom树的重绘和重排,有没有可能我们只更新我们修改的那一小块dom而不要更新整个dom呢?diff算法能够帮助我们。
全栈程序员站长
2021/07/01
6890
相关推荐
Vue 2 常见面试题速查
更多 >
领券
社区富文本编辑器全新改版!诚邀体验~
全新交互,全新视觉,新增快捷键、悬浮工具栏、高亮块等功能并同时优化现有功能,全面提升创作效率和体验
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文