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

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

强烈推荐掘金阅读(知乎markdown支持太差了)

三连哦

面试题篇

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模式

MVC

单向的数据,用户的每一步操作都需要重新请求数据库来修改视图层的渲染,形成一个单向的闭环。比如 jQuery+underscore+backbone M:model 数据存放层 V: view:视图层 页面 * C: controller:控制器 js 逻辑层。 controller 控制层将数据层 model层 的数据处理后显示在视图层 view层,同样视图层 view层 接收用户的指令也可以通过控制层 controller,作用到数据层 model。所以 MVC的缺点是视图层不能和数据层直接交互。

MVVM模式

隐藏了 controller 控制层,直接操控 View 视图层和 Model 数据层。 MVVM 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 都不会有响应式,但是可以通过$set方法给数组添加响应式的数据
  • 扩展:
// 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和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是一个宏任务,宏任务多性能也会差。关于事件循环可以看看 JS 事件循环

  • 扩展
// 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()
    }
  }

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

  • computed 内部就是根据 Object.definedProperty() 实现的
  • computed 具备缓存功能,依赖的值不发生变化,就不会重新计算。
  • watch 是监控值的变化,值发生变化时会执行对应的回调函数。
  • computedwatch 都是基于 Watcher类 来执行的。 computed 缓存功能依靠一个变量 dirty,表示值是不是脏的默认是 true,取值后是 false,再次取值时 dirty 还是 false 直接将还是上一次的取值返回。

13. vue.$set(obj, key, value) 方法的实现原理

小tip:用于给 data 中响应式的对象/数组添加相应式的数据,才会触发试图更新。 $set() 之所以给数组和对象添加新属性可以触发相应式的数据,是因为最后调用 defineReactive() 给新增的属性添加 get/set 调用 $set() 给对象新增属性时可以触发对象依赖收集的 Watcher 去更新。 调用 $set() 修改数组索引时,内部会通过调用 重写的 splice() 方法更新数组。 调用set() 不能给根实例 vm_data 添加属性,因为性能消耗大。 * 扩展
// \src\core\observer\index.js

export function set (target: Array<any> | Object, key: any, val: any): any {
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }

  // 给数组添加响应式数据,调用数组的 splice 方法
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    target.splice(key, 1, val)
    return val
  }

// 给对象添加响应式数据,直接添加
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }

// 这里是给根实例 vm/_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
  }
// 给不是响应式的对象添加属性,直接加上即可,但是不会更新视图
  if (!ob) {
    target[key] = val
    return val
  }

// 最后 defineReactive 给添加的属性增加 `get/set` 
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}

14. vue 为什么需要虚拟 DOM?

  • visual DOM 是真实 dom 的描述对象,直接操作 DOM 可能会引发 DOM 的回流和重绘,性能消耗很大。
  • js层的操作效率高,将真实DOM转化成对象来操作,通过 diff 算法来比较对象差异更新 DOM,可以提高性能。
  • 而且虚拟 DOM 不依赖平台的差异,能实现跨平台运行。

参考

Vue 模板编译原理 Vue.nextTick 的原理和用途

深圳SEO优化公司汕头网站优化按天计费多少钱宝安设计网站报价咸阳网站制作价格平凉模板制作价格钦州模板制作哪家好许昌百度爱采购推荐吕梁建设网站价格伊犁建站价格丹竹头模板网站建设公司保山seo网站推广报价通辽百度竞价多少钱丽水百姓网标王多少钱兴安盟模板网站建设价格自贡推广网站合肥外贸网站建设报价汕尾阿里店铺运营推荐荆州关键词排名包年推广报价兰州设计公司网站多少钱合肥百度竞价推荐青岛百度竞价包年推广多少钱龙岗SEO按天计费公司淄博网络广告推广报价大庆关键词排名徐州推广网站报价张掖模板网站建设哪家好罗湖百度seo多少钱咸宁网站推广工具多少钱合肥seo哪家好北海关键词按天计费报价承德网站优化按天收费歼20紧急升空逼退外机英媒称团队夜以继日筹划王妃复出草木蔓发 春山在望成都发生巨响 当地回应60岁老人炒菠菜未焯水致肾病恶化男子涉嫌走私被判11年却一天牢没坐劳斯莱斯右转逼停直行车网传落水者说“没让你救”系谣言广东通报13岁男孩性侵女童不予立案贵州小伙回应在美国卖三蹦子火了淀粉肠小王子日销售额涨超10倍有个姐真把千机伞做出来了近3万元金手镯仅含足金十克呼北高速交通事故已致14人死亡杨洋拄拐现身医院国产伟哥去年销售近13亿男子给前妻转账 现任妻子起诉要回新基金只募集到26元还是员工自购男孩疑遭霸凌 家长讨说法被踢出群充个话费竟沦为间接洗钱工具新的一天从800个哈欠开始单亲妈妈陷入热恋 14岁儿子报警#春分立蛋大挑战#中国投资客涌入日本东京买房两大学生合买彩票中奖一人不认账新加坡主帅:唯一目标击败中国队月嫂回应掌掴婴儿是在赶虫子19岁小伙救下5人后溺亡 多方发声清明节放假3天调休1天张家界的山上“长”满了韩国人?开封王婆为何火了主播靠辱骂母亲走红被批捕封号代拍被何赛飞拿着魔杖追着打阿根廷将发行1万与2万面值的纸币库克现身上海为江西彩礼“减负”的“试婚人”因自嘲式简历走红的教授更新简介殡仪馆花卉高于市场价3倍还重复用网友称在豆瓣酱里吃出老鼠头315晚会后胖东来又人满为患了网友建议重庆地铁不准乘客携带菜筐特朗普谈“凯特王妃P图照”罗斯否认插足凯特王妃婚姻青海通报栏杆断裂小学生跌落住进ICU恒大被罚41.75亿到底怎么缴湖南一县政协主席疑涉刑案被控制茶百道就改标签日期致歉王树国3次鞠躬告别西交大师生张立群任西安交通大学校长杨倩无缘巴黎奥运

深圳SEO优化公司 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化