• vue面试如何准备,这几道面试题助力你拿到理想offer


    谈谈Vue和React组件化的思想

    • 1.我们在各个页面开发的时候,会产生很多重复的功能,比如element中的xxxx。像这种纯粹非页面的UI,便成为我们常用的UI组件,最初的前端组件也就仅仅指的是UI组件
    • 2.随着业务逻辑变得越来多是,我们就想要我们的组件可以处理很多事,这就是我们常说的组件化,这个组件就不是UI组件了,而是包具体业务的业务组件
    • 3.这种开发思想就是分而治之。最大程度的降低开发难度和维护成本的效果。并且可以多人协作,每个人写不同的组件,最后像撘积木一样的把它构成一个页面

    什么是 mixin ?

    • Mixin 使我们能够为 Vue 组件编写可插拔和可重用的功能。
    • 如果希望在多个组件之间重用一组组件选项,例如生命周期 hook、 方法等,则可以将其编写为 mixin,并在组件中简单的引用它。
    • 然后将 mixin 的内容合并到组件中。如果你要在 mixin 中定义生命周期 hook,那么它在执行时将优化于组件自已的 hook。

    Vue 组件通讯有哪几种方式

    1. props 和 e m i t 父组件向子组件传递数据是通过 p r o p 传递的,子组件传递数据给父组件是通过 emit 父组件向子组件传递数据是通过 prop 传递的,子组件传递数据给父组件是通过 emit父组件向子组件传递数据是通过prop传递的,子组件传递数据给父组件是通过emit 触发事件来做到的
    2. p a r e n t , parent, parent,children 获取当前组件的父组件和当前组件的子组件
    3. a t t r s 和 attrs 和 attrslisteners A->B->C。Vue 2.4 开始提供了 a t t r s 和 attrs 和 attrslisteners 来解决这个问题
    4. 父组件中通过 provide 来提供变量,然后在子组件中通过 inject 来注入变量。(官方不推荐在实际业务中使用,但是写组件库时很常用)
    5. $refs 获取组件实例
    6. envetBus 兄弟组件数据传递 这种情况下可以使用事件总线的方式
    7. vuex 状态管理

    template和jsx的有什么分别?

    对于 runtime 来说,只需要保证组件存在 render 函数即可,而有了预编译之后,只需要保证构建过程中生成 render 函数就可以。在 webpack 中,使用vue-loader编译.vue文件,内部依赖的vue-template-compiler模块,在 webpack 构建过程中,将template预编译成 render 函数。与 react 类似,在添加了jsx的语法糖解析器babel-plugin-transform-vue-jsx之后,就可以直接手写render函数。

    所以,template和jsx的都是render的一种表现形式,不同的是:JSX相对于template而言,具有更高的灵活性,在复杂的组件中,更具有优势,而 template 虽然显得有些呆滞。但是 template 在代码结构上更符合视图与逻辑分离的习惯,更简单、更直观、更好维护。

    参考:前端vue面试题详细解答

    created和mounted的区别

    • created:在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。
    • mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。

    路由的hash和history模式的区别

    Vue-Router有两种模式:hash模式history模式。默认的路由模式是hash模式。

    1. hash模式

    简介: hash模式是开发中默认的模式,它的URL带着一个#

    特点:hash值会出现在URL里面,但是不会出现在HTTP请求中,对后端完全没有影响。所以改变hash值,不会重新加载页面。这种模式的浏览器支持度很好,低版本的IE浏览器也支持这种模式。hash路由被称为是前端路由,已经成为SPA(单页面应用)的标配。

    原理: hash模式的主要原理就是onhashchange()事件

    window.onhashchange = function(event){
        console.log(event.oldURL, event.newURL);
        let hash = location.hash.slice(1);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    使用onhashchange()事件的好处就是,在页面的hash值发生变化时,无需向后端发起请求,window就可以监听事件的改变,并按规则加载相应的代码。除此之外,hash值变化对应的URL都会被浏览器记录下来,这样浏览器就能实现页面的前进和后退。虽然是没有请求后端服务器,但是页面的hash值和对应的URL关联起来了。

    2. history模式

    简介: history模式的URL中没有#,它使用的是传统的路由分发模式,即用户在输入一个URL时,服务器会接收这个请求,并解析这个URL,然后做出相应的逻辑处理。 特点: 相比hash模式更加好看。但是,history模式需要后台配置支持。如果后台没有正确配置,访问时会返回404。 API: history api可以分为两大部分,切换历史状态和修改历史状态:

    • 修改历史状态:包括了 HTML5 History Interface 中新增的 pushState()replaceState() 方法,这两个方法应用于浏览器的历史记录栈,提供了对历史记录进行修改的功能。只是当他们进行修改时,虽然修改了url,但浏览器不会立即向后端发送请求。如果要做到改变url但又不刷新页面的效果,就需要前端用上这两个API。
    • 切换历史状态: 包括forward()back()go()三个方法,对应浏览器的前进,后退,跳转操作。

    虽然history模式丢弃了丑陋的#。但是,它也有自己的缺点,就是在刷新页面的时候,如果没有相应的路由或资源,就会刷出404来。

    如果想要切换到history模式,就要进行以下配置(后端也要进行配置):

    const router = new VueRouter({
      mode: 'history',
      routes: [...]
    })
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    3. 两种模式对比

    调用 history.pushState() 相比于直接修改 hash,存在以下优势:

    • pushState() 设置的新 URL 可以是与当前 URL 同源的任意 URL;而 hash 只可修改 # 后面的部分,因此只能设置与当前 URL 同文档的 URL;
    • pushState() 设置的新 URL 可以与当前 URL 一模一样,这样也会把记录添加到栈中;而 hash 设置的新值必须与原来不一样才会触发动作将记录添加到栈中;
    • pushState() 通过 stateObject 参数可以添加任意类型的数据到记录中;而 hash 只可添加短字符串;
    • pushState() 可额外设置 title 属性供后续使用。
    • hash模式下,仅hash符号之前的url会被包含在请求中,后端如果没有做到对路由的全覆盖,也不会返回404错误;history模式下,前端的url必须和实际向后端发起请求的url一致,如果没有对用的路由处理,将返回404错误。

    hash模式和history模式都有各自的优势和缺陷,还是要根据实际情况选择性的使用。

    computed 和 watch 的区别和运用的场景?

    computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;

    watch: 更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;

    运用场景:

    • 当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;
    • 当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

    vue和react的区别

    => 相同点:

    1. 数据驱动页面,提供响应式的试图组件
    2. 都有virtual DOM,组件化的开发,通过props参数进行父子之间组件传递数据,都实现了webComponents规范
    3. 数据流动单向,都支持服务器的渲染SSR
    4. 都有支持native的方法,react有React native, vue有wexx
    
    • 1
    • 2
    • 3
    • 4

    => 不同点:

     1.数据绑定:Vue实现了双向的数据绑定,react数据流动是单向的
     2.数据渲染:大规模的数据渲染,react更快
     3.使用场景:React配合Redux架构适合大规模多人协作复杂项目,Vue适合小快的项目
     4.开发风格:react推荐做法jsx + inline style把html和css都写在js了
          vue是采用webpack + vue-loader单文件组件格式,html, js, css同一个文件
    
    • 1
    • 2
    • 3
    • 4
    • 5

    一般在哪个生命周期请求异步数据

    我们可以在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。

    推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:

    • 能更快获取到服务端数据,减少页面加载时间,用户体验更好;
    • SSR不支持 beforeMount 、mounted 钩子函数,放在 created 中有助于一致性。

    Vue模版编译原理

    vue中的模板template无法被浏览器解析并渲染,因为这不属于浏览器的标准,不是正确的HTML语法,所有需要将template转化成一个JavaScript函数,这样浏览器就可以执行这一个函数并渲染出对应的HTML元素,就可以让视图跑起来了,这一个转化的过程,就成为模板编译。模板编译又分三个阶段,解析parse,优化optimize,生成generate,最终生成可执行函数render。

    • 解析阶段:使用大量的正则表达式对template字符串进行解析,将标签、指令、属性等转化为抽象语法树AST。
    • 优化阶段:遍历AST,找到其中的一些静态节点并进行标记,方便在页面重渲染的时候进行diff比较时,直接跳过这一些静态节点,优化runtime的性能。
    • 生成阶段:将最终的AST转化为render函数字符串。

    如何从真实DOM到虚拟DOM

    涉及到Vue中的模板编译原理,主要过程:

    1. 将模板转换成 ast 树, ast 用对象来描述真实的JS语法(将真实DOM转换成虚拟DOM)
    2. 优化树
    3. ast 树生成代码

    MVVM的优缺点?

    优点:

    • 分离视图(View)和模型(Model),降低代码耦合,提⾼视图或者逻辑的重⽤性: ⽐如视图(View)可以独⽴于Model变化和修改,⼀个ViewModel可以绑定不同的"View"上,当View变化的时候Model不可以不变,当Model变化的时候View也可以不变。你可以把⼀些视图逻辑放在⼀个ViewModel⾥⾯,让很多view重⽤这段视图逻辑
    • 提⾼可测试性: ViewModel的存在可以帮助开发者更好地编写测试代码
    • ⾃动更新dom: 利⽤双向绑定,数据更新后视图⾃动更新,让开发者从繁琐的⼿动dom中解放

    缺点:

    • Bug很难被调试: 因为使⽤双向绑定的模式,当你看到界⾯异常了,有可能是你View的代码有Bug,也可能是Model的代码有问题。数据绑定使得⼀个位置的Bug被快速传递到别的位置,要定位原始出问题的地⽅就变得不那么容易了。另外,数据绑定的声明是指令式地写在View的模版当中的,这些内容是没办法去打断点debug的
    • ⼀个⼤的模块中model也会很⼤,虽然使⽤⽅便了也很容易保证了数据的⼀致性,当时⻓期持有,不释放内存就造成了花费更多的内存
    • 对于⼤型的图形应⽤程序,视图状态较多,ViewModel的构建和维护的成本都会⽐较⾼。

    了解nextTick吗?

    异步方法,异步渲染最后一步,与JS事件循环联系紧密。主要使用了宏任务微任务(setTimeoutpromise那些),定义了一个异步方法,多次调用nextTick会将方法存入队列,通过异步方法清空当前队列。

    Vue3.0 和 2.0 的响应式原理区别

    Vue3.x 改用 Proxy 替代 Object.defineProperty。因为 Proxy 可以直接监听对象和数组的变化,并且有多达 13 种拦截方法。

    相关代码如下

    import { mutableHandlers } from "./baseHandlers"; // 代理相关逻辑
    import { isObject } from "./util"; // 工具方法
    
    export function reactive(target) {
      // 根据不同参数创建不同响应式对象
      return createReactiveObject(target, mutableHandlers);
    }
    function createReactiveObject(target, baseHandler) {
      if (!isObject(target)) {
        return target;
      }
      const observed = new Proxy(target, baseHandler);
      return observed;
    }
    
    const get = createGetter();
    const set = createSetter();
    
    function createGetter() {
      return function get(target, key, receiver) {
        // 对获取的值进行放射
        const res = Reflect.get(target, key, receiver);
        console.log("属性获取", key);
        if (isObject(res)) {
          // 如果获取的值是对象类型,则返回当前对象的代理对象
          return reactive(res);
        }
        return res;
      };
    }
    function createSetter() {
      return function set(target, key, value, receiver) {
        const oldValue = target[key];
        const hadKey = hasOwn(target, key);
        const result = Reflect.set(target, key, value, receiver);
        if (!hadKey) {
          console.log("属性新增", key, value);
        } else if (hasChanged(value, oldValue)) {
          console.log("属性值被修改", key, value);
        }
        return result;
      };
    }
    export const mutableHandlers = {
      get, // 当获取属性时调用此方法
      set, // 当修改属性时调用此方法
    };
    
    • 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

    Vue3.x 响应式数据原理

    Vue3.x改用Proxy替代Object.defineProperty。因为Proxy可以直接监听对象和数组的变化,并且有多达13种拦截方法。并且作为新标准将受到浏览器厂商重点持续的性能优化。

    proxy基本用法

    // proxy默认只会代理第一层对象,只有取值再次是对象的时候再次代理,不是一上来就代理,提高性能。不像vue2.x递归遍历每个对象属性
    let handler = {
        set(target, key, value) {
            return Reflect.set(target, key, value);
        },
        get(target, key) {
            if (typeof target[key] == 'object' && target[key] !== null) {
                return new Proxy(target[key], handler); // 懒代理,只有取值再次是对象的时候再次代理,提高性能
            }
            return Reflect.get(target, key);
        }
    }
    let obj = { school: { name: 'poetry', age: 20 } };
    let proxy = new Proxy(obj, handler);
    
    // 返回对象的代理
    proxy.school
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    vue-router 路由钩子函数是什么 执行顺序是什么

    路由钩子的执行流程, 钩子函数种类有:全局守卫、路由守卫、组件守卫

    完整的导航解析流程:

    1. 导航被触发。
    2. 在失活的组件里调用 beforeRouteLeave 守卫。
    3. 调用全局的 beforeEach 守卫。
    4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
    5. 在路由配置里调用 beforeEnter。
    6. 解析异步路由组件。
    7. 在被激活的组件里调用 beforeRouteEnter。
    8. 调用全局的 beforeResolve 守卫 (2.5+)。
    9. 导航被确认。
    10. 调用全局的 afterEach 钩子。
    11. 触发 DOM 更新。
    12. 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
    Vue的生命周期方法有哪些
    1. Vue 实例有一个完整的生命周期,也就是从开始创建初始化数据编译模版挂载Dom -> 渲染更新 -> 渲染卸载等一系列过程,我们称这是Vue的生命周期
    2. Vue生命周期总共分为8个阶段创建前/后载入前/后更新前/后销毁前/后

    beforeCreate => created => beforeMount => Mounted => beforeUpdate => updated => beforeDestroy => destroyedkeep-alive下:activated deactivated

    生命周期vue2生命周期vue3描述
    beforeCreatebeforeCreate在实例初始化之后,数据观测(data observer) 之前被调用。
    createdcreated实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。这里没有$el
    beforeMountbeforeMount在挂载开始之前被调用:相关的 render 函数首次被调用
    mountedmountedel 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子
    beforeUpdatebeforeUpdate组件数据更新之前调用,发生在虚拟 DOM 打补丁之前
    updatedupdated由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子
    beforeDestroybeforeUnmount实例销毁之前调用。在这一步,实例仍然完全可用
    destroyedunmounted实例销毁后调用。调用后, Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。

    其他几个生命周期

    生命周期vue2生命周期vue3描述
    activatedactivatedkeep-alive专属,组件被激活时调用
    deactivateddeactivatedkeep-alive专属,组件被销毁时调用
    errorCapturederrorCaptured捕获一个来自子孙组件的错误时被调用
    -renderTracked调试钩子,响应式依赖被收集时调用
    -renderTriggered调试钩子,响应式依赖被触发时调用
    -serverPrefetchssr only,组件实例在服务器上被渲染前调用
    1. 要掌握每个生命周期内部可以做什么事
    • beforeCreate 初始化vue实例,进行数据观测。执行时组件实例还未创建,通常用于插件开发中执行一些初始化任务
    • created 组件初始化完毕,可以访问各种数据,获取接口数据等
    • beforeMount 此阶段vm.el虽已完成DOM初始化,但并未挂载在el选项上
    • mounted 实例已经挂载完成,可以进行一些DOM操作
    • beforeUpdate 更新前,可用于获取更新前各种状态。此时view层还未更新,可用于获取更新前各种状态。可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
    • updated 完成view层的更新,更新后,所有状态已是最新。可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态,因为这可能会导致更新无限循环。 该钩子在服务器端渲染期间不被调用。
    • destroyed 可以执行一些优化操作,清空定时器,解除绑定事件
    • vue3 beforeunmount:实例被销毁前调用,可用于一些定时器或订阅的取消
    • vue3 unmounted:销毁一个实例。可清理它与其它实例的连接,解绑它的全部指令及事件监听器

    <div id="app">{{name}}div>
    <script>
        const vm = new Vue({
            data(){
                return {name:'poetries'}
            },
            el: '#app',
            beforeCreate(){
                // 数据观测(data observer) 和 event/watcher 事件配置之前被调用。
                console.log('beforeCreate');
            },
            created(){
                // 属性和方法的运算, watch/event 事件回调。这里没有$el
                console.log('created')
            },
            beforeMount(){
                // 相关的 render 函数首次被调用。
                console.log('beforeMount')
            },
            mounted(){
                // 被新创建的 vm.$el 替换
                console.log('mounted')
            },
            beforeUpdate(){
                //  数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。
                console.log('beforeUpdate')
            },
            updated(){
                //  由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
                console.log('updated')
            },
            beforeDestroy(){
                // 实例销毁之前调用 实例仍然完全可用
                console.log('beforeDestroy')
            },
            destroyed(){ 
                // 所有东西都会解绑定,所有的事件监听器会被移除
                console.log('destroyed')
            }
        });
        setTimeout(() => {
            vm.name = 'poetry';
            setTimeout(() => {
                vm.$destroy()  
            }, 1000);
        }, 1000);
    script>
    
    • 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
    1. 组合式API生命周期钩子

    你可以通过在生命周期钩子前面加上 “on” 来访问组件的生命周期钩子。

    下表包含如何在 setup() 内部调用生命周期钩子:

    选项式 APIHook inside setup
    beforeCreate不需要*
    created不需要*
    beforeMountonBeforeMount
    mountedonMounted
    beforeUpdateonBeforeUpdate
    updatedonUpdated
    beforeUnmountonBeforeUnmount
    unmountedonUnmounted
    errorCapturedonErrorCaptured
    renderTrackedonRenderTracked
    renderTriggeredonRenderTriggered

    因为 setup 是围绕 beforeCreatecreated 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写

    export default {
      setup() {
        // mounted
        onMounted(() => {
          console.log('Component is mounted!')
        })
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    setupcreated谁先执行?

    • beforeCreate:组件被创建出来,组件的methodsdata还没初始化好
    • setup:在beforeCreatecreated之间执行
    • created:组件被创建出来,组件的methodsdata已经初始化好了

    由于在执行setup的时候,created还没有创建好,所以在setup函数内我们是无法使用datamethods的。所以vue为了让我们避免错误的使用,直接将setup函数内的this执行指向undefined

    import { ref } from "vue"
    export default {
      // setup函数是组合api的入口函数,注意在组合api中定义的变量或者方法,要在template响应式需要return{}出去
      setup(){
        let count = ref(1)
        function myFn(){
          count.value +=1
        }
        return {count,myFn}
      },
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    1. 其他问题
    • 什么是vue生命周期? Vue 实例从创建到销毁的过程,就是生命周期。从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、销毁等一系列过程,称之为 Vue 的生命周期。

    • vue生命周期的作用是什么? 它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑。

    • vue生命周期总共有几个阶段? 它可以总共分为8个阶段:创建前/后、载入前/后、更新前/后、销毁前/销毁后。

    • 第一次页面加载会触发哪几个钩子? 会触发下面这几个beforeCreatecreatedbeforeMountmounted

    • 你的接口请求一般放在哪个生命周期中? 接口请求一般放在mounted中,但需要注意的是服务端渲染时不支持mounted,需要放到created

    • DOM 渲染在哪个周期中就已经完成?mounted中,

      • 注意 mounted 不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用 vm.$nextTick 替换掉 mounted
      mounted: function () {
          this.$nextTick(function () {
              // Code that will run only after the
              // entire view has been rendered
          })
        }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    
    
    ### Vue 的父子组件生命周期钩子函数执行顺序
    
    * **渲染顺序** :先父后子,完成顺序:先子后父
    * **更新顺序** :父更新导致子更新,子更新完成后父
    * **销毁顺序** :先父后子,完成顺序:先子后父
    
    **加载渲染过程**
    
    父 `beforeCreate`->父 `created`->父 `beforeMount`->子 `beforeCreate`->子 `created`->子 `beforeMount`->子 `mounted`->父 `mounted`。**子组件先挂载,然后到父组件**
    
    **子组件更新过程**
    
    父 `beforeUpdate`->子 `beforeUpdate`->子 `updated`->父 `updated`
    
    **父组件更新过程**
    
    父 `beforeUpdate`->父 `updated`
    
    **销毁过程**
    
    父 `beforeDestroy`->子 `beforeDestroy`->子 `destroyed`->父 `destroyed`
    
    > 之所以会这样是因为`Vue`创建过程是一个递归过程,先创建父组件,有子组件就会创建子组件,因此创建时先有父组件再有子组件;子组件首次创建时会添加`mounted`钩子到队列,等到`patch`结束再执行它们,可见子组件的`mounted`钩子是先进入到队列中的,因此等到`patch`结束执行这些钩子时也先执行。
    
    ![](https://s.poetries.work/uploads/2022/08/c0e24e4114c32a12.png)
    
    ```javascript
    function patch (oldVnode, vnode, hydrating, removeOnly) { 
        if (isUndef(vnode)) { 
          if (isDef(oldVnode)) invokeDestroyHook(oldVnode) return 
        }
        let isInitialPatch = false 
        const insertedVnodeQueue = [] // 定义收集所有组件的insert hook方法的数组 // somthing ... 
        createElm( 
            vnode, 
            insertedVnodeQueue, oldElm._leaveCb ? null : parentElm, 
            nodeOps.nextSibling(oldElm) 
        )// somthing... 
        // 最终会依次调用收集的insert hook 
        invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
        return vnode.elm
    }
    
    function createElm ( vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index ) { 
        // createChildren 会递归创建儿子组件 
        createChildren(vnode, children, insertedVnodeQueue) // something... 
    } 
    // 将组件的vnode插入到数组中 
    function invokeCreateHooks (vnode, insertedVnodeQueue) { 
        for (let i = 0; i < cbs.create.length; ++i) { 
            cbs.create[i](emptyNode, vnode) 
        }
        i = vnode.data.hook // Reuse variable 
        if (isDef(i)) { 
            if (isDef(i.create)) i.create(emptyNode, vnode) 
            if (isDef(i.insert)) insertedVnodeQueue.push(vnode) 
        } 
    } 
    // insert方法中会依次调用mounted方法 
    insert (vnode: MountedComponentVNode) { 
        const { context, componentInstance } = vnode 
        if (!componentInstance._isMounted) { 
            componentInstance._isMounted = true 
            callHook(componentInstance, 'mounted') 
        } 
    }
    function invokeInsertHook (vnode, queue, initial) { 
        // delay insert hooks for component root nodes, invoke them after the // element is really inserted 
        if (isTrue(initial) && isDef(vnode.parent)) { 
            vnode.parent.data.pendingInsert = queue 
        } else { 
            for (let i = 0; i < queue.length; ++i) { 
                queue[i].data.hook.insert(queue[i]); // 调用insert方法 
            } 
        } 
    }
    
    Vue.prototype.$destroy = function () { 
        callHook(vm, 'beforeDestroy') 
        // invoke destroy hooks on current rendered tree 
        vm.__patch__(vm._vnode, null) // 先销毁儿子 
        // fire destroyed hook 
        callHook(vm, 'destroyed') 
    }
    
    • 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
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    谈谈对keep-alive的了解

    keep-alive 可以实现组件的缓存,当组件切换时不会对当前组件进行卸载。常用的2个属性 include/exclude ,2个生命周期 activated deactivated

    Vue是如何收集依赖的?

    在初始化 Vue 的每个组件时,会对组件的 data 进行初始化,就会将由普通对象变成响应式对象,在这个过程中便会进行依赖收集的相关逻辑,如下所示∶

    function defieneReactive (obj, key, val){
      const dep = new Dep();
      ...
      Object.defineProperty(obj, key, {
        ...
        get: function reactiveGetter () {
          if(Dep.target){
            dep.depend();
            ...
          }
          return val
        }
        ...
      })
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    以上只保留了关键代码,主要就是 const dep = new Dep()实例化一个 Dep 的实例,然后在 get 函数中通过 dep.depend() 进行依赖收集。 (1)Dep Dep是整个依赖收集的核心,其关键代码如下:

    class Dep {
      static target;
      subs;
    
      constructor () {
        ...
        this.subs = [];
      }
      addSub (sub) {
        this.subs.push(sub)
      }
      removeSub (sub) {
        remove(this.sub, sub)
      }
      depend () {
        if(Dep.target){
          Dep.target.addDep(this)
        }
      }
      notify () {
        const subs = this.subds.slice();
        for(let i = 0;i < subs.length; i++){
          subs[i].update()
        }
      }
    }
    
    
    • 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

    Dep 是一个 class ,其中有一个关 键的静态属性 static,它指向了一个全局唯一 Watcher,保证了同一时间全局只有一个 watcher 被计算,另一个属性 subs 则是一个 Watcher 的数组,所以 Dep 实际上就是对 Watcher 的管理,再看看 Watcher 的相关代码∶

    (2)Watcher

    class Watcher {
      getter;
      ...
      constructor (vm, expression){
        ...
        this.getter = expression;
        this.get();
      }
      get () {
        pushTarget(this);
        value = this.getter.call(vm, vm)
        ...
        return value
      }
      addDep (dep){
            ...
        dep.addSub(this)
      }
      ...
    }
    function pushTarget (_target) {
      Dep.target = _target
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    Watcher 是一个 class,它定义了一些方法,其中和依赖收集相关的主要有 get、addDep 等。

    (3)过程

    在实例化 Vue 时,依赖收集的相关过程如下∶
    初 始 化 状 态 initState , 这 中 间 便 会 通 过 defineReactive 将数据变成响应式对象,其中的 getter 部分便是用来依赖收集的。
    初始化最终会走 mount 过程,其中会实例化 Watcher ,进入 Watcher 中,便会执行 this.get() 方法,

    updateComponent = () => {
      vm._update(vm._render())
    }
    new Watcher(vm, updateComponent)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    get 方法中的 pushTarget 实际上就是把 Dep.target 赋值为当前的 watcher。

    this.getter.call(vm,vm),这里的 getter 会执行 vm._render() 方法,在这个过程中便会触发数据对象的 getter。那么每个对象值的 getter 都持有一个 dep,在触发 getter 的时候会调用 dep.depend() 方法,也就会执行 Dep.target.addDep(this)。刚才 Dep.target 已经被赋值为 watcher,于是便会执行 addDep 方法,然后走到 dep.addSub() 方法,便将当前的 watcher 订阅到这个数据持有的 dep 的 subs 中,这个目的是为后续数据变化时候能通知到哪些 subs 做准备。所以在 vm._render() 过程中,会触发所有数据的 getter,这样便已经完成了一个依赖收集的过程。

  • 相关阅读:
    平曲线坐标、反算桩号计算程序
    PlantUML基础使用教程
    28.Python面向对象(一)【类:创建类&实例对象,私有属性,类里面的方法,类属性CRUD,常用内置类属性】
    Docker安装Nginx较佳实践
    Geoserver发布shp、tiff、瓦片等格式的GIS数据
    React_lazy使用-组件加载前loading做优雅降级
    MongoDB、Elasticsearch分组统计性能比较
    Rust中FFI编程知识点整理总结
    集成hibeaver的血泪史 -- Ambiguous method overloading for method java.io.File#<init>
    17-Go并发之锁
  • 原文地址:https://blog.csdn.net/bb_xiaxia1998/article/details/127781690