2021Vue面试题,大厂面试必备!

2022年05月10日 阅读数:4
这篇文章主要向大家介绍2021Vue面试题,大厂面试必备!,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

1.请说一下Vue2Vue3响应式数据的理解

  • 数组和对象类型当值变化时如何劫持到。对象内部经过defineReactive方法,使用Object.defineProperty将属性进行劫持(只会劫持已经存在的属性),数组则是经过重写数组方法来实现。 多层对象是经过递归来实现劫持。Vue3则采用proxy

src/core/observer/index.js:135前端

export function defineReactive ( // 定义响应式数据
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()
  // 若是不能够配置直接return
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }
  // 对数据进行观测
  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()
        if (childOb) { // 让对象自己进行依赖收集
          childOb.dep.depend()  // {a:1}  => {} 外层对象
          if (Array.isArray(value)) { // 若是是数组  {arr:[[],[]]} vm.arr取值只会让arr属性和外层数组进行收集   
            dependArray(value) 
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}

2.Vue中如何检测数组变化?

  • 数组考虑性能缘由没有用defineProperty对数组的每一项进行拦截,而是选择重写数组(push,shift,pop,splice,unshift,sort,reverse)方法。
  • 数组中若是是对象数据类型也会进行递归劫持
  • 数组的索引和长度变化是没法监控到的

src/core/observer/index.js:47vue

src/core/observer/array.js:11node

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
methodsToPatch.forEach(function (method) {
  // cache original 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 change
    ob.dep.notify()
    return result
  })
})

3.Vue中如何进行依赖收集?

  • 每一个属性都拥有本身的dep属性,存放他所依赖的watcher,当属性变化后会通知本身对应的watcher去更新
  • 默认在初始化时会调用render函数,此时会触发属性依赖收集 dep.depend
  • 当属性发生修改时会触发watcher更新 dep.notify()

4.如何理解Vue中模板编译原理

问题核心:如何将template转换成render函数 ?react

  • 1.将template模板转换成ast语法树 - parserHTMLweb

  • 2.对静态语法作静态标记 - markUp diff来作优化的 静态节点跳过diff操做面试

  • 3.从新生成代码 - `codeGen` 
    

src/compiler/index.js:11算法

export const createCompiler = createCompilerCreator(function baseCompile (
  template: string,
  options: CompilerOptions
): CompiledResult {
  const ast = parse(template.trim(), options) // 1.解析ast语法树
  if (options.optimize !== false) {          
    optimize(ast, options)                    // 2.对ast树进行标记,标记静态节点
  }
  const code = generate(ast, options)         // 3.生成代码
  return {
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
  }
})

5.Vue生命周期钩子是如何实现的

  • Vue的生命周期钩子就是回调函数而已,当建立组件实例的过程当中会调用对应的钩子方法。
  • 内部会对钩子函数进行处理,将钩子函数维护成数组的形式

src/core/instance/init.js:38 初始化合并api

src/core/util/options.js:388 合并选项数组

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
}

6.Vue的生命周期方法有哪些?通常在哪一步发送请求及缘由

  • beforeCreate 在实例初始化以后,数据观测(data observer) 和 event/watcher 事件配置以前被调用。
  • created 实例已经建立完成以后被调用。在这一步,实例已完成如下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。这里没有$el
  • beforeMount 在挂载开始以前被调用:相关的 render 函数首次被调用。
  • mounted el 被新建立的 vm.$el 替换,并挂载到实例上去以后调用该钩子。
  • beforeUpdate 数据更新时调用,发生在虚拟 DOM 从新渲染和打补丁以前。
  • updated 因为数据更改致使的虚拟 DOM 从新渲染和打补丁,在这以后会调用该钩子。
  • beforeDestroy 实例销毁以前调用。在这一步,实例仍然彻底可用。
  • destroyed Vue 实例销毁后调用。调用后,Vue 实例指示的全部东西都会解绑定,全部的事件监听器会被移除,全部的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。
  • keep-alive (activated 和 deactivated)

在哪发送请求均可以,主要看具体你要作什么事缓存

7.Vue.mixin的使用场景和原理

  • Vue.mixin的做用就是抽离公共的业务逻辑,原理相似“对象的继承”,当组件初始化时会调用mergeOptions方法进行合并,采用策略模式针对不一样的属性进行合并。若是混入的数据和自己组件中的数据冲突,会采用“就近原则”以组件的数据为准。
  • mixin中有不少缺陷 “命名冲突问题”、“依赖问题”、“数据来源问题”

src/core/global-api/mixin.js

8.Vue组件data为何必须是个函数?

  • 每次使用组件时都会对组件进行实例化操做,而且调用data函数返回一个对象做为组件的数据源。这样能够保证多个组件间数据互不影响
function Vue() {}
function Sub() { // 会将data存起来
    this.data = this.constructor.options.data;
}
Vue.extend = function(options) {
    Sub.options = options;
    return Sub;
}
let Child = Vue.extend({
    data: { name: 'zf' }
});
// 两个组件就是两个实例, 但愿数据互不干扰
let child1 = new Child();
let child2 = new Child();

console.log(child1.data.name);
child1.data.name = 'jw';
console.log(child2.data.name);

src/core/util/options.js:121 data的合并策略

strats.data = function (
  parentVal: any,
  childVal: any,
  vm?: Component
): ?Function {
  if (!vm) { // 组件在合并时并无产生实例,因此会校验类型
    if (childVal && typeof childVal !== 'function') {
      process.env.NODE_ENV !== 'production' && warn(
        'The "data" option should be a function ' +
        'that returns a per-instance value in component ' +
        'definitions.',
        vm
      )
      return parentVal
    }
    return mergeDataOrFn(parentVal, childVal)
  }
  return mergeDataOrFn(parentVal, childVal, vm)
}
function mergedInstanceDataFn () {
    // instance merge
    const instanceData = typeof childVal === 'function'
    ? childVal.call(vm, vm)
    : childVal
    const defaultData = typeof parentVal === 'function'
    ? parentVal.call(vm, vm)
    : parentVal
    if (instanceData) {
        return mergeData(instanceData, defaultData) // 合并两个对象
    } else {
        return defaultData
    }
}

9.nextTick在哪里使用?原理是?

  • nextTick中的回调是在下次 DOM 更新循环结束以后执行的延迟回调。
  • 可用于获取更新后的 DOM。
  • Vue中数据更新是异步的,使用nextTick方法能够保证用户定义的逻辑在更新以后执行。

src/core/util/nextTick.js:89

10.computed和watch区别

  • computed和watch都是基于Watcher来实现的
  • computed属性是具有缓存的,依赖的值不发生变化,对其取值时计算属性方法不会从新执行
  • watch则是监控值的变化,当值发生变化时调用对应的回调函数

src/core/instance/state.js:58

src/core/instance/state.js:241 计算属性取值函数

src/core/instance/state.js:345 watch的实现

function createComputedGetter (key) {  return function computedGetter () {    const watcher = this._computedWatchers && this._computedWatchers[key]    if (watcher) {      if (watcher.dirty) { // 若是值是脏的 进行求值操做        watcher.evaluate()       }      if (Dep.target) { // 让计算属性所依赖的属性 收集渲染watcher        watcher.depend()      }      return watcher.value    }  }}
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)}

11.Vue.set方法是如何实现的

  • 咱们给对象和数组自己都增长了dep属性
  • 当给对象新增不存在的属性则触发对象依赖的watcher去更新
  • 当修改数组索引时咱们调用数组自己的splice方法去更新数组

// src/core/observer:45; 给对象增长dep属性 // src/core/observer:201; set方法的定义

export function set (target: Array<any> | Object, key: any, val: any): any {  // 1.是开发环境 target 没定义或者是基础类型则报错  if (process.env.NODE_ENV !== 'production' &&    (isUndef(target) || isPrimitive(target))  ) {    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)  }  // 2.若是是数组 Vue.set(array,1,100); 调用咱们重写的splice方法 (这样能够更新视图)  if (Array.isArray(target) && isValidArrayIndex(key)) {    target.length = Math.max(target.length, key)    target.splice(key, 1, val)    return val  }  // 3.若是是对象自己的属性,则直接添加便可  if (key in target && !(key in Object.prototype)) {    target[key] = val    return val  }  // 4.若是是Vue实例 或 根数据data时 报错,(更新_data 无心义)  const ob = (target: any).__ob__  if (target._isVue || (ob && ob.vmCount)) {    process.env.NODE_ENV !== 'production' && warn(      'Avoid adding reactive properties to a Vue instance or its root $data ' +      'at runtime - declare it upfront in the data option.'    )    return val  }  // 5.若是不是响应式的也不须要将其定义成响应式属性  if (!ob) {    target[key] = val    return val  }  // 6.将属性定义成响应式的  defineReactive(ob.value, key, val)  // 通知视图更新  ob.dep.notify()  return val}

12.Vue为何须要虚拟DOM

  • Virtual DOM就是用js对象来描述真实DOM,是对真实DOM的抽象
  • 因为直接操做DOM性能低可是js层的操做效率高,能够将DOM操做转化成对象操做,最终经过diff算法比对差别进行更新DOM(减小了对真实DOM的操做)。
  • 虚拟DOM不依赖真实平台环境从而也能够实现跨平台。

scr/core/vdom/create-element.js:28

src/core/vdom/vnode.js 虚拟节点的实现

13.Vuediff算法原理

  • Vue的diff算法是平级比较,不考虑跨级比较的状况。内部采用深度递归的方式 + 双指针的方式进行比较。
  • 1.先比较是不是相同节点 key tag
  • 2.相同节点比较属性,并复用老节点
  • 3.比较儿子节点,考虑老节点和新节点儿子的状况
  • 4.优化比较:头头、尾尾、头尾、尾头
  • 5.比对查找进行复用
  • Vue3中采用最长递增子序列来实现diff优化

src/core/vdom/patch.js:700

src/core/vdom/patch.js:501 比较两个虚拟节点 patchVnode()

src/core/vdom/patch.js:404 比较两个虚拟节点 patchChildren()

14.既然Vue经过数据劫持能够精准探测数据变化,为何还须要虚拟DOM进行diff检测差别

  • 响应式数据变化,Vue确实能够在数据发生变化时,响应式系统能够马上得知。可是若是给每一个属性都添加watcher用于更新的话,会产生大量的watcher从而下降性能。
  • 并且粒度过细也会致使更新不精准的问题,因此vue采用了组件级的watcher配合diff来检测差别。

15.请说明Vue中key的做用和原理,谈谈你对它的理解

  • Vue在patch过程当中经过key能够判断两个虚拟节点是不是相同节点。 (能够复用老节点)
  • 无key会致使更新的时候出问题
  • 尽可能不要采用索引做为key

16.谈一谈对Vue组件化的理解

  • 组件化开发能大幅提升应用开发效率、测试性、复用性等;
  • 经常使用的组件化技术:属性、自定义事件、插槽等
  • 下降更新范围,只从新渲染变化的组件
  • 组件的特色:高内聚、低耦合、单向数据流

17.Vue的组件渲染流程

  • 产生组件虚拟节点 -> 建立组件的真实节点 -> 插入到页面中

18.Vue组件更新流程

属性更新时会触发patchVnode方法 -> 组件虚拟节点会调用prepatch钩子 -> 更新属性 -> 组件更新

19.Vue中异步组件原理

  • 默认渲染异步占位符节点 -> 组件加载完毕后调用forceUpdate强制更新

vue3.0

随着vue3.0的发布,Vue.js再次被推上了前端的风口浪尖。

同时,面试官的提问也将加入一些有关Vue3.0的新元素(相信近期去面试的小伙伴或多或少都会被问到Vue3.0的知识点)。

今天就分析总结了一些Vue3.0的面试题(题目讲的很是详细,对你们有所帮助)

1、vue3.0源码分析

2、vue3.0实战应用

以上便是vue3.0文档内容,须要的小伙伴点击这里便可免费下载获取。

说实话,做为一名前端工程师,不论你需不须要面试都应该好好看下这份资料。我大概作了一遍,真的是堪称典范,我已经所有为你们打包准备好了,拿到手是不亏的~