Code前端首页关于Code前端联系我们

Vue高频原理访谈+详细解答

terry 2年前 (2023-09-08) 阅读数 136 #Vue

大家好,我是林一尼。这是一道关于Vue原理的面试题。如果你能完全理解它,我想这对每个人来说都是非常有用的。

面试问题

1。了解 MPA/SPA 的优点和缺点是什么?

MPA 多页应用程序。

  • 作文:有多页html作文,
  • 跳转方式:页面跳转是从一页到另一页
  • 刷新方式:整页刷新
  • 页面数据跳转:依赖URL/cookie/localStorage
  • 跳转后的资源会重新加载
  • 优点:SEO友好且开发难度较低。

SPA单页应用程序

  • 页面构成:这个被一个shell页面包围,由多个页面片段(组件)组成
  • 跳转方式:跳转到shell页面,显示或隐藏fragment页面(组件)
  • 刷新方式:页面片段部分刷新
  • 页面数据跳转:组件之间传值更方便
  • 跳转后的资源不会重新加载
  • 缺点:对SEO搜索不太友好,需要单独配置。开发比较困难,需要专门的开发框架

iframe 基本上就是MPA,但是可以实现SPA的一些效果,但是用起来有很多问题。

2。陈词滥调,为什么我们需要这些 MVC/MVVM 模式?谈谈你的 MVC 和 MVVM 模式之间的区别,

目标:借鉴后端思路,职责分工、分层

  • Vue 和 React 都不是真正意义上的 MVVM,更不用说 MVC 了。它们的核心只涉及显示层view

MVC模式

MVC.jpgMVC.jpg

对于单向数据,用户的每一步操作都需要重新请求数据库来改变显示层的外观,形成一个封闭的单向循环。例如jQuery+underscore+backbone

  • M:model数据存储层
  • 问:view:查看图层页面
  • C: controller:controller js 逻辑层。

controller控制层对数据层model层传来的数据进行处理并显示在显示层view层上,显示层view层还可以通过控制层model层接收用户指令,显示在显示层view层上。数据层model。所以MVC的缺点是视图层不能和数据层直接交互。

MVVM模式

隐藏controller控制层并直接操作View显示层和Model数据层。

MVVM.jpgMVVM.jpg

  • M:模型数据模型
  • Q:查看视图模板
  • VM:视图模型-视图数据模板(vue处理的层,vue中定义的属性是处理VM层的逻辑)

双向数据绑定:model 数据模型层通过数据绑定直接影响视图层Data Bindings,视图层view还可以通过监听model修改数据模型层。

  • 数据绑定和DOM事件监控是viewModelVue主要做的事情。即只要将来自数据模型层Model的数据挂载到ViewModel层,就可以实现双向数据绑定。
  • 加上 vuex/redux 可用作 的 model 数据层。
var vm = new Vue()
 

vm是view-model的数据模型层,data:是vmview-model所在层表示的数据。

  • 总结一下两者的区别:MVC的显示层和数据层的交互必须经过控制层。 controller 是单向链接。 MVVM隐藏了控制层controller,让显示层和数据层直接协同工作,是双向的连接。

3。说说你对 Vue 中响应式数据的理解

小提示:响应式数据是指数据发生变化,视图可以更新,即响应式数据

    definedReactive 方法在
  • vue 中实现。该方法内部借用 Object.definedProperty() 将属性 get/set 添加到每个属性。
  • definedReactive只能监控外部对象并递归劫持内部对象的数据。
  • 数组
  • 被重写为7push pop shift unshift reverse sort splice来截取数组的数据,因为这些方法会改变原始数组
  • 展开:
// src\core\observer\index.js
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // 准备给属性添加一个 dep 来依赖收集 Watcher 用于更新视图。
  const dep = new Dep()
  // some code

  // observe() 用来观察值的类型,如果是属性也是对象就递归,为每个属性都加上`get/set`
  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
        // 这里取数据时依赖收集
        const value = getter ? getter.call(obj) : val
        if (Dep.target) {
            dep.depend()
            // childOb 是对对像进行收集依赖
            if (childOb) {
                childOb.dep.depend()

                //这里对数组和内部的数组进行递归收集依赖,这里数组的 key 和 value 都有dep。
                if (Array.isArray(value)) {
                    dependArray(value)
                }
            }
        }
        return value
    },
    set: function reactiveSetter (newVal) {
      // 属性发生改变,这里会通知 watcher 更新视图
    }
  })
}
 

上面的Dep(类)是用来做什么的?答:WatcherWatcher是用来收集视图的?答案:watcher是一个类,用于更新视图的

4。 Vue 如何检测数组的变化?

  • vue 并没有对数组中的每一项使用definedProperty()来截取数据,而是重写了数组方法push pop shift unshift reverse sort splice
  • 手动调用通知,通知renderwatcher并执行更新
  • 如果
  • 数组中存在对象类型(对象和数组),则进行数据拦截。
  • 所以通过调整数组下标和数组长度,就不会出现数据拦截,也不会发生响应变化。例如,arr[0] = 1, arr.length = 2 不响应
  • 展开:
// src\core\observer\array.js
const methodsToPatch = ['push','pop','shift','unshift','splice','sort','reverse']
methodsToPatch.forEach(function (method) {
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    // 新增的类型再次观察
    if (inserted) ob.observeArray(inserted)
    // 手动调用 notify 派发更新
    ob.dep.notify()
    return result
  })
})
 

5。 Vue 如何依赖直接借记? (dep和Watcher是什么关系)

dep.jpgdep.jpg

提示:Dep是负责收集Watcher的类,Watcher是包含发送更新的显示逻辑的类。请注意Watcher 是不能直接更新视图的还需要结合Vnode经过patch()中的diff算法才可以生成真正的DOM

  • 每个属性都有自己的 dep 属性来存储依赖的 Watcher。属性更改后,Watcher会通知您需要更新。
  • 当用户(getter)获取数据时,Vue 将属性dep添加到每个要收集的属性(收集为依赖项)Watcher。当用户 setting 设置属性值时,dep.notify() 会通知 收集的Watcher 有新视图。详情请参阅上文defineReactive()
  • Dep依赖收集类Watcher类 是多对多的双向存储关系
  • 每个属性可以有多个Watcher 类,因为该属性可以在不同的组件中使用。
  • 同时,一个Watcher 类还可以匹配多个属性。

6。 Vue 中的模板编译

Vue 高频原理面试篇+详细解答Vue 高频原理面试篇+详细解答

Vue 中的模板编译:基本上就是将 template 转换为 render 函数。说白了,就是把真实的DOM(模板)编译成虚拟的dom(Vnode)

  • 第一步是将字符串 转换为 ast 语法树 (解析器-解析器)。这里使用了大量的正则化来匹配标签名称、属性、文本等。
  • 第二步,将AST标记为静态节点static,主要用于虚拟DOM(optimize优化器)渲染优化。这里所有的子节点都被遍历并且也被静态标记为
  • 第三步是使用ast语法树重新生成代码序列代码。 (codeGen 代码生成器)

为什么需要静态标记节点?如果是静态节点(没有绑定数据,前后不需要变化的节点),那么就不需要diff算法进行比较。

7。生命周期钩子实现原理

  • vue中的生命周期钩子只是一个回调函数,在创建组件实例化的过程中调用相应的钩子来执行。
  • 使用混合挂钩或在生命周期中定义多个函数,Vue将在内部调用来合并挂钩并将其排队等待执行
  • 扩展
// src\core\util\options.js
function mergeHook (
  parentVal: ?Array<Function>,
  childVal: ?Function | ?Array<Function>
): ?Array<Function> {
  const res = childVal
    ? parentVal
      ? parentVal.concat(childVal) // 合并
      : Array.isArray(childVal)
        ? childVal
        : [childVal]
    : parentVal
  return res
    ? dedupeHooks(res)
    : res
}
 

8。平凡的生命周期是什么?请求通常发送到哪里?

  • beforeCreate:刚刚开始初始化vue实例,在数据观察之前调用observerdata/methods之类的属性还没有创建
  • created:Vue实例已完成初始化,所有属性已创建。
  • beforeMount:这个钩子在vue链接页面上的数据之前被触发,此时渲染函数被触发。
  • mounted:el被创建的vm.$el替换,vue初始化的数据挂载到页面上,在这里可以访问到真实的DOM。通常在这里请求数据。
  • beforeUpdate:在数据更新时调用,即在虚拟dom再次渲染之前。
  • updated:数据变化导致虚拟dom重新渲染后发生。
  • beforeDestroy:该钩子在实例被销毁之前调用,并且实例仍然存在。 vm.$destroy 激活两种方法。
  • destroyed:Vue实例销毁后调用。将联系所有事件侦听器。

请求详情根据具体业务需求来决定发送到哪里ajax

9。 Vue.mixin({})的使用场景和原理

  • 使用场景:用于提取公共业务逻辑以供复用。
  • 实现原理:调用mergeOptions()方法,利用策略模式合并多个属性。如果混合数据与组件的数据冲突,则使用组件自己的数据。
  • Vue.mixin({}) 缺陷: 1. 可能会导致混合属性名与组件属性名发生名称冲突; 2. 数据源依赖问题
  • 展开
export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  // some code
  if (!child._base) {
    if (child.extends) {
      parent = mergeOptions(parent, child.extends, vm)
    }
    if (child.mixins) {
      for (let i = 0, l = child.mixins.length; i < l; i++) {
        parent = mergeOptions(parent, child.mixins[i], vm)
      }
    }
  }

 // 递归遍历合并组件和混入的属性
  const options = {}
  let key
  for (key in parent) {
    mergeField(key)
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}
 

10。老生常谈:为什么vue组件中的数据需要是一个函数?

  • 这个和js本身的机制有关。 data函数中返回的对象引用地址不同,这样可以保证不同组件之间的数据不会互相污染。
  • 如果
  • Vue.mixin()与属性data混合,则data也必须是一个函数。因为Vue.mixin()还可以用在多个地方。
  • 在实例
  • 中,data可以是一个对象或函数,因为我们通常在页面上只初始化一个Vue实例(单例)

11。老生常谈:vm.$nextTick(cb)在vue中的实现原理和场景

  • 场景:在 dom 更新循环结束后调用,用于获取更新后的 dom 数据
  • 实现原理:vm.$nextTick(cb)是异步方式,为了兼容性做了很多降级处理,其次是promise.then,MutationObserver,setImmediate,setTimeout。数据改变后,视图并不会立即更新,而是通过set方法通知Watcher更新,并将需要更新的Watcher放入异步队列中,回调函数nexTick 放置在 中。 之后,等待主线程中的同步代码执行保持过夜,然后逐一清空队列,以便vm.nextTick(callback)dom更新完成后执行。

将上面一栏的Watcher一一清除即可得到vue 异步批量更新的原理。让我想想:为什么不直接使用setTimeout呢?由于setTimeout是一个宏任务,多个宏任务的性能会很差。至于事件循环,可以看看JS Event Loop

12。看和计算之间的区别是陈词滥调

  • computed是在Object.definedProperty()
  • 的基础上内部实现的
  • computed 如果依赖值没有改变,缓存功能不会重新计算它。
  • watch正在监控值的变化。当值改变时,执行相应的回调函数。
  • computedwatch都是在Watcher类的基础上执行的。

computed 缓存功能取决于一个变量dirty,该变量指示该值是否为脏值。默认值为 true。取值后是false。再次取值时,仍取回false值。

// src\core\instance\state.js computed 取值函数
function createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      if (watcher.dirty) {  // 判断值是不是脏 dirty
        watcher.evaluate()
      }
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}


// src\core\instance\state.js watch 实现
  Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    const vm: Component = this
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    options.user = true
    // 实例化 watcher
    const watcher = new Watcher(vm, expOrFn, cb, options)
    if (options.immediate) {
      const info = `callback for immediate watcher "${watcher.expression}"`
      pushTarget()
      invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
      popTarget()
    }
    return function unwatchFn () {
      watcher.teardown()
    }
  }
 

参考

Vue模板编译原理

Vue.nextTick原理及使用

结束

感谢大家到目前为止的阅读。如果你觉得文笔还可以,欢迎来到三联。我是林一伊。直到下一次。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门