乐趣区

关于前端:Vue-高频原理面试篇详细解答

原文首地址 掘金

三连哦 更多好文 github

大家好,我是林一一,这是一篇对于 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 中的模板编译

<img src=”https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bc2f78cda8514103b4d0a29acb1a4c9a~tplv-k3u1fbpfcp-watermark.image” width=”80%” height=”360px”/>

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 事件循环

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 模板编译原理

Vue.nextTick 的原理和用处

完结

谢谢大家浏览到这里,如果感觉写的还能够,欢送三连呀,我是林一一,下次见。

退出移动版