Vue的面试题你必须要掌握到这个程度(上篇)

1. 谈谈对 MVVM 的理解及原理

MVC 与 MVVM

  • 传统的 MVC 模式是用户请求服务端路由,路由调用控制器处理,控制器获取数据返回给前端,页面重新渲染

  • MVVM:不需要手动操作 DOM,将数据绑定到 viewModel 层,会自动将数据渲染到视图层,通过 DOM 事件监听,当视图层变化会通知 viewModel 层更新数据

2. 响应式数据原理(02:50)

理解:

  1. 核心点是:Object.defineProperty
  2. Vue 在初始化数(initData)据时,使用一个 Observer来监测所有 data 数据,Observer 使用 defineReactive方法(使用的是Object.defineProperty)会对 data 对象中的所有属性重新定义,获取(get)属性时,会进行依赖(当前组件的所有 Watcher)收集(dep.depend),属性变化(set)时通知相关依赖进行更新操作(dep.notify)

原理:
响应式数据原理

3. Vue 如何检测数组的变化(08:40)

理解:

  • 未使用 Object.defineProperty( ) 方法
  • 使用函数劫持的方式重写数组的方法
  • Vue 将 data 中的数组进行了原型链重写,调用push、pop、shift、unshift、splice、reverse、sort这七个方法时,会指向自定义的数组原型方法,可通知依赖更新数据
  • 对于push、unshift、splice三个会插入数据的方法会对插入数据(inserted)进行监听

原理:
检测数组变化

4. Vue 为何采用异步渲染(16:00)

理解:

  • Vue 组件级更新
  • 防止一更新数据就更新视图造成的性能浪费,Vue 会在本轮数据更新完成后更新视图
  • dep.notify( ) 通知 watcher 进行更新
  • 依次调用 subs[i].update() (subs 为一个 watcher 数组)
  • queueWatcher( ) 根据 id 对 watcher 去重
  • nextTick( flushScheduleQueue ):flushScheduleQueue函数会把 queue 中所有的 watcher 取出来,并执行视图更新的操作

原理:
异步渲染

5. nextTick 实现原理(22:10)

理解:

  • 首先要了解 js 运行机制

    js 是单线程的,基于事件循环。步骤如下:
    1主线程执行同步任务(形成执行栈
    2主线程外有一个任务队列,异步任务有运行结果就在任务队列放置一个事件
    3一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行
    4主线程不断重复以上第三步

    • 只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制
    • 异步任务又分为两大类:macro task 和 micro task
    • 每个 macro task 结束后 开始前,都要清空当前微任务队列里所有的 micro task
    • 常见的 macro task 有 setTimeout、MessageChannel、postMessage、setImmediate
    • 常见的 micro task 有 MutationObsever 和 Promise.then

JS 运行机制

  • nextTick 源码理解

1.申明了 microTimerFunc 和 macroTimerFunc 2 个变量,它们分别对应的是 micro task 的函数和 macro task 的函数
2.对于macro task 优先检测是否支持原生 setImmediate,这是一个高版本 IE 和 Edge 才支持的特性,不支持的话再去检测是否支持原生的 MessageChannel,如果也不支持的话就会降级为 setTimeout 0;而对于 micro task 的实现,则检测浏览器是否原生支持 Promise,不支持的话直接指向 macro task 的实现
3.nextTick(cb) :

  • 将 cb 压入回调函数数组 callbacks => 根据 useMacroTask (默认false) 执行 macroTimerFunc 或 microTimerFunc => 下一 tick 执行 flushCallbacks

原理:
nextTick 原理

6. 计算属性(computed)与侦听属性(watch)区别(30:10)

理解:

  • watcher 有四种类型:deep、user、computed、sync

    • deep:深度监听,调用 traverse 函数对对象作深层递归遍历,可收集到所有子对象的依赖

    • user:调用 $watch 方法创建的 watcher,会做一个错误捕获

    • computed:为计算属性量身定制

    • sync:同步执行 watcher 的回调函数

      (默认情况下,响应式数据变化后,触发update( ) 只是把该 watcher 推送到一个队列中,在 nextTick 才会执行回调)

  • computed 属性具有缓存的功能,只有所依赖的属性发生变化时才更新视图

    • 1.initComputed:对 computed 对象做遍历,拿到userDef => 获取对应的 getter函数 => 为每个 getter 创建 watcher (默认lazy: true,不执行)=> 判断 key 非 vm 的属性,则调用defineComputed(vm, key, userDef)
    • 2.defineComputed:利用 Object.defineProperty 给计算属性对应的 key 值添加 getter 和 setter(使用较少), getter 对应的是 createComputedGetter(key) 的返回值
    • 3.createComputedGetter:返回一个函数 computedGetter,它就是计算属性对应的 getter

原理:
computed 原理

7. watch 的 deep 如何实现的?(55:04)

理解:

  • 1.initWatch:对 watch 对象做遍历,拿到每一个 handler => handler == 数组 ?遍历数组,调用 createWatcher 方法 :直接调用 createWatcher

  • 2.createWatcher:对 hanlder 的类型做判断,拿到它最终的回调函数 => 调用 vm.$watch(keyOrFn, handler, options) 函数

  • 本质上侦听属性也是基于 Watcher 实现的,它是一个 user watcher

  • deep 实现

    • 在 watcher 执行 get 求值的过程,若 deep: true,则会执行 traverse 函数

      	get() {
        let value = this.getter.call(vm, vm)
        // ...
        if (this.deep) {
          traverse(value)
        }
      }
      
    • traverse 的逻辑:对一个对象做深层递归遍历,因为遍历过程中就是对一个子对象的访问,会触发它们的 getter 过程,这样就可以收集到依赖,也就是订阅它们变化的 watcher

8. vue 组件的生命周期(51:50)

理解:
何时调用进行哪些操作


9.ajax请求放在哪个生命周期

理解:
ajax请求放在哪个生命周期

10.何时使用 beforeDestroy(03:54)

理解:
何时使用 beforeDestroy

11. Vue 模版编译原理(04:35)

理解:

  • **baseCompile **:把模板 template 编译成 render 函数,三个逻辑:
    • 1.parse:解析模版字符串生成 AST

      • 输入 template + options ,输出 AST 对象
      • 实现:正则 + 字符串截取 + advance 函数
      • 对于开始标签、闭合标签、文本会分别执行不同的回调
      • 三种 AST 元素结点类型type:1-普通元素、2-表达式、3-纯文本
    • 2.optimize:优化 AST

      • 目的:首次渲染后不会改变的数据,patch 过程会直接跳过比对

      • 实现:markStatic(root) 标记静态节点

      • markStaticRoots(root, false) 标记静态根

        • 首先需要是普通元素(type==1)静态节点
        • 且满足至少拥有一个子节点,且不能只有一个文本节点
        • 最后遍历所有子节点均为静态节点,才满足 'static': true
    • 3.codegen:生成代码

      • generate(ast, options) 函数通过 genElement(ast, state) 生成可执行的 render 代码串,并用 with(this){return ${code}}} 包裹起来
      • genElement(ast, state) 判断当前 AST 元素节点的属性执行不同的代码生成函数

原理:
markStatic

12.v-if 与 v-show 区别(19:40)

理解:

  • v-if 根据条件决定是否渲染(编译后是一个返回一个三元表达式的函数)
  • v-show 都会渲染,只是控制显示与隐藏(编译为指令,控制display属性)

13. 为什么 v-for 与 v-if 不可以连用?(24:20)

理解:

  • v-for 优先级更高,若与 v-if 连用则会给每个元素都加上 v-if ,造成性能浪费

原理:

export function genElement (el: ASTElement, state: CodegenState): string {
  if (el.staticRoot && !el.staticProcessed) {
    return genStatic(el, state)
  } else if (el.once && !el.onceProcessed) {
    return genOnce(el, state)
  } else if (el.for && !el.forProcessed) {
    return genFor(el, state)
  } else if (el.if && !el.ifProcessed) {
    return genIf(el, state)
  } else if {
  	// ...
  }
  // ...

14. 虚拟 DOM(VNode描述 DOM 结构)(26:00)

理解:

  • 虚拟节点就是用对象描述真实的 DOM 元素
  • Vue 中使用 createElement函数创建VNode
  • 每个 VNode 有 children,children 每个元素也是一个 VNode,这样就形成了一个 VNode Tree
  • 最后通过 vm._update 把 VNode 渲染成一个真实的 DOM 并渲染出来
    • vm._update 的核心是 patch 方法,首次渲染我们调用了 createElm 方法,这里传入的 parentElm 是 oldVnode.elm 的父元素

原理:
createElement

15. diff 算法事件复杂度 (35:25)

理解:

  • 传统 diff 算法时间复杂度 O(n^3)

    O(n^2) — 将两个DOM树的所有节点两两对比
    +
    O(n) — 进行树的编辑(插入、替换、删除)需要遍历一次

  • Vue 对 diff 算法进行了优化:只进行同层级对比,时间复杂度 O(n)

16.Vue 中 diff 算法原理(优化)

理解:

  • 只比较同层级 Vnode
  • 新旧节点不同则直接替换
  • 若相同,则比较子节点
    • 指向相同对象,直接return
    • 都只有文本节点,则替换文本节点
    • 只有一个有子节点,则删除或新增
    • 都有子节点的情况,是执行updateChildren函数来更新子节点
      • 采用首尾双指针遍历新旧子节点
      • 四个指针分别记为:oS(oldStart)、oE(oldEnd)、S(Start) 、E(End)
      • 出现匹配的情况:
      1. oS = S / oE = E,指针内移一位
      2. oS = E / oE = s,真实 DOM 节点移到尾部或首部
      3. 全部不匹配,遍历 oldChild 依次与 S 匹配,成功则将其移到首位,否则,将 S 对应节点插到 DOM 中 oS 位置,oS 与 S 指针内移一位

+ 图解

原理:
diff 算法


参考资料

  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值