• vue高频面试题


    1Vue-router 导航守卫有哪些
    全局前置/钩子:beforeEach、beforeResolve、afterEach
    路由独享的守卫:beforeEnter
    组件内的守卫:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
    为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty?
    Object.defineProperty 本身有一定的监控到数组下标变化的能力,但是在 Vue 中,从性能/体验的性价比考虑,尤大大就弃用了这个特性(Vue 为什么不能检测数组变动 )。为了解决这个问题,经过 vue 内部处理后可以使用以下几种方法来监听数组

    push();
    pop();
    shift();
    unshift();
    splice();
    sort();
    reverse();
    复制代码

    由于只针对了以上 7 种方法进行了 hack 处理,所以其他数组的属性也是检测不到的,还是具有一定的局限性。

    Object.defineProperty 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue 2.x 里,是通过 递归 + 遍历 data 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象是才是更好的选择。

    Proxy 可以劫持整个对象,并返回一个新的对象。Proxy 不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。

    前端vue面试题详细解答

    2v-for 为什么要加 key
    如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速

    更准确:因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。所以会更加准确。

    更快速:利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快

    如何从真实DOM到虚拟DOM
    涉及到Vue中的模板编译原理,主要过程:

    将模板转换成 ast 树, ast 用对象来描述真实的JS语法(将真实DOM转换成虚拟DOM)
    优化树
    将 ast 树生成代码
    为什么Vue采用异步渲染呢?
    Vue 是组件级更新,如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染,所以为了性能, Vue 会在本轮数据更新后,在异步更新视图。核心思想 nextTick 。

    dep.notify() 通知 watcher进行更新, subs[i].update 依次调用 watcher 的 update , queueWatcher 将watcher 去重放入队列, nextTick( flushSchedulerQueue )在下一tick中刷新watcher队列(异步)。

    为什么vue组件中data必须是一个函数?
    对象为引用类型,当复用组件时,由于数据对象都指向同一个data对象,当在一个组件中修改data时,其他重用的组件中的data会同时被修改;而使用返回对象的函数,由于每次返回的都是一个新对象(Object的实例),引用地址不同,则不会出现这个问题。

    3MVC 和 MVVM 区别
    MVC

    MVC 全名是 Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范

    Model(模型):是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据
    View(视图):是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的
    Controller(控制器):是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据
    MVC 的思想:一句话描述就是 Controller 负责将 Model 的数据用 View 显示出来,换句话说就是在 Controller 里面把 Model 的数据赋值给 View。

    MVVM

    MVVM 新增了 VM 类

    ViewModel 层:做了两件事达到了数据的双向绑定 一是将【模型】转化成【视图】,即将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定。二是将【视图】转化成【模型】,即将所看到的页面转化成后端的数据。实现的方式是:DOM 事件监听。
    MVVM 与 MVC 最大的区别就是:它实现了 View 和 Model 的自动同步,也就是当 Model 的属性改变时,我们不用再自己手动操作 Dom 元素,来改变 View 的显示,而是改变属性后该属性对应 View 层显示会自动改变(对应Vue数据驱动的思想)

    整体看来,MVVM 比 MVC 精简很多,不仅简化了业务与界面的依赖,还解决了数据频繁更新的问题,不用再用选择器操作 DOM 元素。因为在 MVVM 中,View 不知道 Model 的存在,Model 和 ViewModel 也观察不到 View,这种低耦合模式提高代码的可重用性

    注意:Vue 并没有完全遵循 MVVM 的思想 这一点官网自己也有说明

    那么问题来了 为什么官方要说 Vue 没有完全遵循 MVVM 思想呢?

    严格的 MVVM 要求 View 不能和 Model 直接通信,而 Vue 提供了$refs 这个属性,让 Model 可以直接操作 View,违反了这一规定,所以说 Vue 没有完全遵循 MVVM。
    Vue 为什么要用 vm.$set() 解决对象新增属性不能响应的问题 ?你能说说如下代码的实现原理么?
    1)Vue为什么要用vm.$set() 解决对象新增属性不能响应的问题

    Vue使用了Object.defineProperty实现双向数据绑定
    在初始化实例时对属性执行 getter/setter 转化
    属性必须在data对象上存在才能让Vue将它转换为响应式的(这也就造成了Vue无法检测到对象属性的添加或删除)
    所以Vue提供了Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value)

    2)接下来我们看看框架本身是如何实现的呢?

    Vue 源码位置:vue/src/core/instance/index.js

    1. export function set (target: Array<any> | Object, key: any, val: any): any {
    2.   // target 为数组  
    3.   if (Array.isArray(target) && isValidArrayIndex(key)) {
    4.     // 修改数组的长度, 避免索引>数组长度导致splcie()执行有误
    5.     target.length = Math.max(target.length, key)
    6.     // 利用数组的splice变异方法触发响应式  
    7.     target.splice(key, 1, val)
    8.     return val
    9.   }
    10.   // key 已经存在,直接修改属性值  
    11.   if (key in target && !(key in Object.prototype)) {
    12.     target[key] = val
    13.     return val
    14.   }
    15.   const ob = (target: any).__ob__
    16.   // target 本身就不是响应式数据, 直接赋值
    17.   if (!ob) {
    18.     target[key] = val
    19.     return val
    20.   }
    21.   // 对属性进行响应式处理
    22.   defineReactive(ob.value, key, val)
    23.   ob.dep.notify()
    24.   return val
    25. }


    我们阅读以上源码可知,vm.$set 的实现原理是:

    如果目标是数组,直接使用数组的 splice 方法触发相应式;
    如果目标是对象,会先判读属性是否存在、对象是否是响应式,
    最终如果要对属性进行响应式处理,则是通过调用 defineReactive 方法进行响应式处理
    defineReactive 方法就是 Vue 在初始化对象时,给对象属性采用 Object.defineProperty 动态添加 getter 和 setter 的功能所调用的方法

    4Vue3.0 和 2.0 的响应式原理区别
    Vue3.x 改用 Proxy 替代 Object.defineProperty。因为 Proxy 可以直接监听对象和数组的变化,并且有多达 13 种拦截方法。

    相关代码如下

    1. import { mutableHandlers } from "./baseHandlers"; // 代理相关逻辑
    2. import { isObject } from "./util"; // 工具方法
    3. export function reactive(target) {
    4.   // 根据不同参数创建不同响应式对象
    5.   return createReactiveObject(target, mutableHandlers);
    6. }
    7. function createReactiveObject(target, baseHandler) {
    8.   if (!isObject(target)) {
    9.     return target;
    10.   }
    11.   const observed = new Proxy(target, baseHandler);
    12.   return observed;
    13. }
    14. const get = createGetter();
    15. const set = createSetter();
    16. function createGetter() {
    17.   return function get(target, key, receiver) {
    18.     // 对获取的值进行放射
    19.     const res = Reflect.get(target, key, receiver);
    20.     console.log("属性获取", key);
    21.     if (isObject(res)) {
    22.       // 如果获取的值是对象类型,则返回当前对象的代理对象
    23.       return reactive(res);
    24.     }
    25.     return res;
    26.   };
    27. }
    28. function createSetter() {
    29.   return function set(target, key, value, receiver) {
    30.     const oldValue = target[key];
    31.     const hadKey = hasOwn(target, key);
    32.     const result = Reflect.set(target, key, value, receiver);
    33.     if (!hadKey) {
    34.       console.log("属性新增", key, value);
    35.     } else if (hasChanged(value, oldValue)) {
    36.       console.log("属性值被修改", key, value);
    37.     }
    38.     return result;
    39.   };
    40. }
    41. export const mutableHandlers = {
    42.   get, // 当获取属性时调用此方法
    43.   set, // 当修改属性时调用此方法
    44. };


    Vue模版编译原理知道吗,能简单说一下吗?
    简单说,Vue的编译过程就是将template转化为render函数的过程。会经历以下阶段:

    生成AST树
    优化
    codegen
    首先解析模版,生成AST语法树(一种用JavaScript对象的形式来描述整个模板)。 使用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相关处理。

    Vue的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变化,对应的DOM也不会变化。那么优化过程就是深度遍历AST树,按照相关条件对树节点进行标记。这些被标记的节点(静态节点)我们就可以跳过对它们的比对,对运行时的模板起到很大的优化作用。

    编译的最后一步是将优化后的AST树转换为可执行的代码。

    5MVVM的优缺点?
    优点:

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

    Bug很难被调试: 因为使⽤双向绑定的模式,当你看到界⾯异常了,有可能是你View的代码有Bug,也可能是Model的代码有问题。数据绑定使得⼀个位置的Bug被快速传递到别的位置,要定位原始出问题的地⽅就变得不那么容易了。另外,数据绑定的声明是指令式地写在View的模版当中的,这些内容是没办法去打断点debug的
    ⼀个⼤的模块中model也会很⼤,虽然使⽤⽅便了也很容易保证了数据的⼀致性,当时⻓期持有,不释放内存就造成了花费更多的内存
    对于⼤型的图形应⽤程序,视图状态较多,ViewModel的构建和维护的成本都会⽐较⾼。
    Vue data 中某一个属性的值发生改变后,视图会立即同步执行重新渲染吗?
    不会立即同步执行重新渲染。Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化, Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。

    如果同一个watcher被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环tick中,Vue 刷新队列并执行实际(已去重的)工作。

    6diff算法
    答案
    时间复杂度: 个树的完全 diff 算法是一个时间复杂度为 O(n*3) ,vue进行优化转化成 O(n) 。

    理解:

    最小量更新, key 很重要。这个可以是这个节点的唯一标识,告诉 diff 算法,在更改前后它们是同一个DOM节点

    扩展 v-for 为什么要有 key ,没有 key 会暴力复用,举例子的话随便说一个比如移动节点或者增加节点(修改DOM),加 key 只会移动减少操作DOM。
    只有是同一个虚拟节点才会进行精细化比较,否则就是暴力删除旧的,插入新的。

    只进行同层比较,不会进行跨层比较。

    diff算法的优化策略:四种命中查找,四个指针

    旧前与新前(先比开头,后插入和删除节点的这种情况)

    旧后与新后(比结尾,前插入或删除的情况)

    旧前与新后(头与尾比,此种发生了,涉及移动节点,那么新前指向的节点,移动到旧后之后)

    旧后与新前(尾与头比,此种发生了,涉及移动节点,那么新前指向的节点,移动到旧前之前)

    — 问完上面这些如果都能很清楚的话,基本O了 —

    以下的这些简单的概念,你肯定也是没有问题的啦😉

    7Vue的优点
    轻量级框架:只关注视图层,是一个构建数据的视图集合,大小只有几十 kb ;
    简单易学:国人开发,中文文档,不存在语言障碍 ,易于理解和学习;
    双向数据绑定:保留了 angular 的特点,在数据操作方面更为简单;
    组件化:保留了 react 的优点,实现了 html 的封装和重用,在构建单页面应用方面有着独特的优势;
    视图,数据,结构分离:使数据的更改更为简单,不需要进行逻辑代码的修改,只需要操作数据就能完成相关操作;
    虚拟DOM:dom 操作是非常耗费性能的,不再使用原生的 dom 操作节点,极大解放 dom 操作,但具体操作的还是 dom 不过是换了另一种方式;
    运行速度更快:相比较于 react 而言,同样是操作虚拟 dom,就性能而言, vue 存在很大的优势。
    vue-router 路由钩子函数是什么 执行顺序是什么
    路由钩子的执行流程, 钩子函数种类有:全局守卫、路由守卫、组件守卫

    8完整的导航解析流程:

    导航被触发。
    在失活的组件里调用 beforeRouteLeave 守卫。
    调用全局的 beforeEach 守卫。
    在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
    在路由配置里调用 beforeEnter。
    解析异步路由组件。
    在被激活的组件里调用 beforeRouteEnter。
    调用全局的 beforeResolve 守卫 (2.5+)。
    导航被确认。
    调用全局的 afterEach 钩子。
    触发 DOM 更新。
    调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
    9Vue.js的template编译
    简而言之,就是先转化成AST树,再得到的render函数返回VNode(Vue的虚拟DOM节点),详细步骤如下:

    首先,通过compile编译器把template编译成AST语法树(abstract syntax tree 即 源代码的抽象语法结构的树状表现形式),compile是createCompiler的返回值,createCompiler是用以创建编译器的。另外compile还负责合并option。

    然后,AST会经过generate(将AST语法树转化成render funtion字符串的过程)得到render函数,render的返回值是VNode,VNode是Vue的虚拟DOM节点,里面有(标签名、子节点、文本等等)

    10$nextTick 是什么?
    Vue 实现响应式并不是在数据发生后立即更新 DOM,使用 vm.$nextTick 是在下次 DOM 更新循环结束之后立即执行延迟回调。在修改数据之后使用,则可以在回调中获取更新后的 DOM。

    11说说Vue的生命周期吧
    什么时候被调用?

    beforeCreate :实例初始化之后,数据观测之前调用
    created:实例创建万之后调用。实例完成:数据观测、属性和方法的运算、 watch/event 事件回调。无 $el .
    beforeMount:在挂载之前调用,相关 render 函数首次被调用
    mounted:了被新创建的vm.$el替换,并挂载到实例上去之后调用改钩子。
    beforeUpdate:数据更新前调用,发生在虚拟DOM重新渲染和打补丁,在这之后会调用改钩子。
    updated:由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用改钩子。
    beforeDestroy:实例销毁前调用,实例仍然可用。
    destroyed:实例销毁之后调用,调用后,Vue实例指示的所有东西都会解绑,所有事件监听器和所有子实例都会被移除
    每个生命周期内部可以做什么?

    created:实例已经创建完成,因为他是最早触发的,所以可以进行一些数据、资源的请求。
    mounted:实例已经挂载完成,可以进行一些DOM操作。
    beforeUpdate:可以在这个钩子中进一步的更改状态,不会触发重渲染。
    updated:可以执行依赖于DOM的操作,但是要避免更改状态,可能会导致更新无线循环。
    destroyed:可以执行一些优化操作,清空计时器,解除绑定事件。
    ajax放在哪个生命周期?:一般放在 mounted 中,保证逻辑统一性,因为生命周期是同步执行的, ajax 是异步执行的。单数服务端渲染 ssr 同一放在 created 中,因为服务端渲染不支持 mounted 方法。 什么时候使用beforeDestroy?:当前页面使用 $on ,需要解绑事件。清楚定时器。解除事件绑定, scroll mousemove 。

    12Vue 怎么用 vm.$set() 解决对象新增属性不能响应的问题 ?
    受现代 JavaScript 的限制 ,Vue 无法检测到对象属性的添加或删除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的。但是 Vue 提供了 Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value) 来实现为对象添加响应式属性,那框架本身是如何实现的呢?

    我们查看对应的 Vue 源码:vue/src/core/instance/index.js

    1. export function set (target: Array<any> | Object, key: any, val: any): any {
    2.   // target 为数组  
    3.   if (Array.isArray(target) && isValidArrayIndex(key)) {
    4.     // 修改数组的长度, 避免索引>数组长度导致splcie()执行有误
    5.     target.length = Math.max(target.length, key)
    6.     // 利用数组的splice变异方法触发响应式  
    7.     target.splice(key, 1, val)
    8.     return val
    9.   }
    10.   // key 已经存在,直接修改属性值  
    11.   if (key in target && !(key in Object.prototype)) {
    12.     target[key] = val
    13.     return val
    14.   }
    15.   const ob = (target: any).__ob__
    16.   // target 本身就不是响应式数据, 直接赋值
    17.   if (!ob) {
    18.     target[key] = val
    19.     return val
    20.   }
    21.   // 对属性进行响应式处理
    22.   defineReactive(ob.value, key, val)
    23.   ob.dep.notify()
    24.   return val
    25. }


    我们阅读以上源码可知,vm.$set 的实现原理是:

    如果目标是数组,直接使用数组的 splice 方法触发相应式;
    如果目标是对象,会先判读属性是否存在、对象是否是响应式,最终如果要对属性进行响应式处理,则是通过调用 defineReactive 方法进行响应式处理( defineReactive 方法就是 Vue 在初始化对象时,给对象属性采用 Object.defineProperty 动态添加 getter 和 setter 的功能所调用的方法)

    原文链接:https://blog.csdn.net/helloworld1024fd/article/details/126085900

  • 相关阅读:
    PS系统教学24
    jdk21(最新版) download 配置(linux window mac)
    MAC系统“无法验证开发者”问题
    设计模式与应用:迭代器模式
    微信小程序录音和头像上传
    C++之模板——初阶
    C/C++计算表达式值 2020年12月电子学会青少年软件编程(C/C++)等级考试一级真题答案解析
    自动控制原理-2 控制系统的数学模型
    问题求解:总计600人,每次刀一个奇数位的人,最后剩下谁的概率最高 暴力求解法
    7.使用多线程时,要注意哪些场景?经验之谈
  • 原文地址:https://blog.csdn.net/qq_41328247/article/details/132755392