2

前端面试Vue 高频原理篇+详细解答,还有105道vue面试题集合

 3 years ago
source link: https://blog.csdn.net/weixin_52546522/article/details/117404903
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

前端面试Vue 高频原理篇+详细解答,还有105道vue面试题集合

小编在群里看到一句话,说三件套加vue就可以找到实习,三件套估计说的是HTML+JavaScript+CSS,但还有这好事?这可不得赶紧把vue面试题安排上。实不实习不重要,主要是想学习哈哈哈哈(手动狗头)。

也有小伙伴私信小编有没有vue的面试题,看来之前的前端面试套餐:Vue面试题总结+JavaScript前端经典面试题+100道 CSS 面试题满足不了大家呀,小编又整理了一套vue面试题集合,需要的小伙伴直接点击这里喔

1.老生常谈之, MPA/SPA 的理解,优缺点是什么?

MPA 多页面应用。

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

SPA单页面应用

  • 页面组成:由一个外壳页面包裹,多个页面(组件)片段组成
  • 跳转方式:在外壳页面中跳转,将片段页面(组件)显示或隐藏
  • 刷新方式:页面片段的局部刷新
  • 页面的数据跳转:组件间的传值比较容易
  • 跳转后的资源 不会重新加载
  • 缺点:对 SEO 搜索不太友好需要单独做配置,开发难度高一点需要专门的开发框架

iframe 实际上是 MPA,但是可以实现 SPA 的一些效果,但是本身由不少问题。

2.老生常谈之,为什么需要有这些 MVC/MVVM 模式?谈谈你对 MVC,MVVM 模式的区别,

目的:借鉴后端的思想,职责划分和分层

  • Vue, React 不是真正意义上的 MVVM 更不是 MVC,两者核心只处理视图层 view

MVC模式

单向的数据,用户的每一步操作都需要重新请求数据库来修改视图层的渲染,形成一个单向的闭环。比如 jQuery+underscore+backbone

  • M:model 数据存放层
  • V: view:视图层 页面
  • C: controller:控制器 js 逻辑层。

controller 控制层将数据层 model层 的数据处理后显示在视图层 view层,同样视图层 view层 接收用户的指令也可以通过控制层 controller,作用到数据层 model。所以 MVC的缺点是视图层不能和数据层直接交互。

MVVM模式

隐藏了 controller 控制层,直接操控 View 视图层和 Model 数据层。

  • M:model 数据模型
  • V: view 视图模板
  • VM:view-model 视图数据模板(vue处理的层,vue 中的definedProperty 就是处理 VM 层的逻辑)

双向的数据绑定:model 数据模型层通过数据绑定 Data Bindings 直接影响视图层 View,同时视图层 view 通过监听 Dom Listener 也可以改变数据模型层 model

  • 数据绑定和DOM事件监听就是 viewModelVue 主要做的事。也就是说:只要将 数据模型层Model 的数据挂载到 ViewModelVue 就可以实现双向的数据绑定。
  • 加上 vuex/redux 可以作为 vue和reactmodel 数据层。
var vm = new Vue()
复制代码

vm 就是 view-model 数据模型层,data:就是vm view-model 层所代理的数据。

  • 综上两者的区别:MVC 的视图层和数据层交互需要通过控制层 controller 属于单向链接。MVVM 隐藏了控制层 controller,让视图层和数据层可以直接交互 属于双向连接。

3. 说一下对 Vue 中响应式数据的理解

小tip:响应式数据指的是数据发生了变化,视图可以更新就是响应式的数据

  • vue 中实现了一个 definedReactive 方法,方法内部借用 Object.definedProperty() 给每一个属性都添加了 get/set 的属性。
  • definedReactive 只能监控到最外层的对象,对于内层的对象需要递归劫持数据。
  • 数组则是重写的7个 push 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
  • 手动调用 notify,通知 render watcher,执行 update
  • 数组中如果有对象类型(对象和数组)的话会进行数据拦截。
  • 所以通过修改数组下标和数组长度是不会进行数据拦截的,也就不会有响应式变化。例如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 是什么关系)

tip:Dep 是一个用来负责收集 Watcher 的类,Watcher 是一个封装了渲染视图逻辑的类,用于派发更新的。需要注意的是 Watcher 是不能直接更新视图的还需要结合Vnode经过patch()中的diff算法才可以生成真正的DOM

  • 每一个属性都有自己的 dep 属性,来存放依赖的 Watcher,属性发生变化后会通知 Watcher 去更新。
  • 在用户获取(getter) 数据时 Vue 给每一个属性都添加了 dep 属性来(collect as Dependency)收集 Watcher。在用户 setting 设置属性值时 dep.notify() 通知 收集的Watcher 重新渲染。详情见上面的 defineReactive()
  • Dep依赖收集类 其和 Watcher类 是多对多双向存储的关系
  • 每一个属性都可以有多个 Watcher 类,因为属性可能在不同的组件中被使用。
  • 同时一个 Watcher 类 也可以对应多个属性。

6. Vue 中的模板编译

Vue中模板编译:其实就是将 template 转化成 render 函数。说白了就是将真实的 DOM(模板) 编译成虚拟 dom(Vnode)

  • 第一步是将 template 模板字符串转换成 ast 语法树 (parser 解析器),这里使用了大量的正则来匹配标签的名称,属性,文本等。
  • 第二步是对 AST 进行静态节点 static 标记,主要用来做虚拟 DOM 的渲染优化(optimize优化器),这里会遍历出所有的子节点也做静态标记
  • 第三步是 使用 ast语法树 重新生成 render 函数 代码字符串 code。(codeGen 代码生成器)

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

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

  • vue 中的生命周期钩子只是一个回调函数,在创建组件实例化的过程中会调用对应的钩子执行。
  • 使用Vue.mixin({})混入的钩子或生命周期中定义了多个函数,vue 内部会调用mergeHook() 对钩子进行合并放入到队列中依次执行
// 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.老生常谈之 vue 生命周期有哪些,一般在哪里发送请求?

  • beforeCreate: 刚开始初始化 vue 实例,在数据观测observer之前调用,还没有创建 data/methods 等属性
  • created: vue 实例初始化结束,所有的属性已经创建。
  • beforeMount: 在 vue 挂载数据到页面上之前,触发这个钩子,render 函数此时被触发。
  • 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 组件中的data 为什么必须是一个函数?

  • 这和 js 本身机制相关,data 函数中返回的对象引用地址不同,就能保证不同组件之间的数据不相互污染。
  • Vue.mixin() 中如果混入data属性,那么 data 也必须是一个函数。因为Vue.mixin()也可以多处使用。
  • 实例中data可以是一个对象也可以是一个函数,因为我们一个页面一般只初始化一个Vue实例(单例)

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

  • 场景:在 dom 更新循环结束后调用,用于获取更新后的 dom 数据
  • 实现原理:vm.$nextTick(cb) 是一个异步的方法为了兼容性做了很多降级处理依次有 promise.then,MutationObserver,setImmediate,setTimeout。在数据修改后不会马上更新视图,而是经过 set 方法 notify 通知 Watcher 更新,将需要更新的 Watcher 放入到一个异步队列中,nexTick 的回调函数就放在 Watcher 的后面,等待主线程中同步代码执行借宿然后依次清空队列中,所以 vm.nextTick(callback) 是在 dom 更新结束后执行的。

上面将对列中Watcher 依次清空就是 vue 异步批量更新的原理。提一个小思考:为什么不直接使用setTimeout代替?因为setTimeout是一个宏任务,宏任务多性能也会差。

12.老生常谈之 watch 和 computed 区别

  • computed 内部就是根据 Object.definedProperty() 实现的
  • computed 具备缓存功能,依赖的值不发生变化,就不会重新计算。
  • watch 是监控值的变化,值发生变化时会执行对应的回调函数。
  • computedwatch 都是基于 Watcher类 来执行的。

computed 缓存功能依靠一个变量 dirty,表示值是不是脏的默认是 true,取值后是 false,再次取值时 dirty 还是 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面试题集合

面试光看这12道题怎么够呢?小编把vue面试题整理成了一个集合共有105道题目,不多但经典哦。

  • vue.js的两个核心是什么?
  • vue 的双向绑定的原理是什么?
  • vue生命周期钩子函数有哪些?
  • 请问 v-if 和 v-show 有什么区别?
  • vue常用的修饰符
  • nextTick
  • 什么是vue生命周期
  • 数据响应(数据劫持)
  • virtual dom 原理实现
  • Proxy 相比于 defineProperty 的优势
  • vue中 key 值的作用
  • Vue 组件中 data 为什么必须是函数?
  • v-for 与 v-if 的优先级
  • 说出至少 4 种 vue 当中的指令和它的用法
  • vue中子组件调用父组件的方法
  • vue中父组件调用子组件的方法
  • vue页面级组件之间传值
  • 说说vue的动态组件。
  • keep-alive内置组件的作用
  • 递归组件的用法
  • 怎么定义vue-router的动态路由?怎么获取传过来的值?
  • vue-router有哪几种路由守卫?
  • $route和 $router的区别是什么?
  • vue-router响应路由参数的变化
  • vue-router 传参
  • 不用Vuex会带来什么问题?
  • vuex有哪几种属性?
  • vuex的State特性是?
  • vuex的Getter特性是?

完整版的vue集合PDF资料直接点击这里就可以领取了哦,前端面试套餐也是点击这领取,也能联系小编,说不定你想要的我这正有呢。

无论是几件套加vue还是什么,前端找工作该学的知识点、面试该问到的面试题一个不会落下,加油小伙伴们!喜欢这篇文章的留下你们的 “点赞+评论”,给小编充充能量。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK