51信用卡(一面)

在ES5中如何实现继承

小提示:这里我说了很多,从借用构造函数到组合继承到寄生组合继承,但面试官其实最想听到的是寄生组合继承。面试官还追问我具体要如何实现寄生组合继承。当然这里其实问的问题还可以很多,比如ES6的类继承和ES5中的继承有什么区别。

绝对定位

小提示:这个建议大家好好回忆一下,例如子元素是相对父元素的padding、border还是content进行定位之类的,当时面试官问的就这么细。

消抖和节流

小提示:面试官只是问了一下具体的使用场景,没有问实现原理。

简单消抖

    function debounce (fn, wait = 1000) {
      let timeOutId
    
      return function () {
        let context = this
    
        if (timeOutId) {
          clearTimeout(timeOutId)
        }
    
        timeOutId = setTimeout(() => {
          fn.apply(context, arguments)
        }, wait)
      }
    }

带立即执行参数的消抖

    function debounceImmediate (fn, wait = 1000, immediate) {
      let timeOutId, context, args
    
      const later = (immediate) => setTimeout(() => {
        if (!immediate) {
          fn.apply(context, args)
          timeOutId = context = args = null
        }
      }, wait)
    
      return function () {
        if (!timeOutId) {
          timeOutId = later(true)
    
          if (immediate) {
            fn.apply(this, arguments)
          }
    
          context = this
          args = arguments
        } else {
          clearTimeout(timeOutId)
          timeOutId = later(false)
        }
      }
    }

节流

    function throttle (fn, wait) {
      let timeoutId = null
      return function () {
        let context = this
        if (!timeoutId) {
          timeoutId = setTimeout(() => {
            fn.apply(context, arguments)
            timeoutId = null
          }, wait)
        }
      }
    }

Vue中的computed实现原理

小提示:这个问题面试官问的很细,绝对是想问你是否阅读过源码。他首先问computed的实现原理,其次问了这样一个问题:现在有两个computed计算值,其中一个computed计算值为什么可以依赖另外一个computed计算值。这里顺便将watch的实现原理也贴上。

watch的实现原理

watch的分类:

  • deep watch(深层次监听)
  • user watch(用户监听)
  • computed watcher(计算属性)
  • sync watcher(同步监听)

watch实现过程:

  • watch的初始化在data初始化之后(此时的data已经通过Object.defineProperty的设置成响应式)
  • watch的key会在Watcher里进行值的读取,也就是立马执行get获取value(从而实现data对应的key执行getter实现对于watch的依赖收集),此时如果有- immediate属性那么立马执行watch对应的回调函数
  • 当data对应的key发生变化时,触发user watch实现watch回调函数的执行

computed运行原理

  • computed的属性是动态挂载到vm实例上的,和普通的响应式数据在data里声明不同
  • 设置computed的getter,如果执行了computed对应的函数,由于函数会读取data属性值,因此又会触发data属性值的getter函数,在这个执行过程中就可以处理computed相对于data的依赖收集关系了
  • 首次计算computed的值时,会执行vm.computed属性对应的getter函数(用户指定的computed函数,如果没有设置getter,那么将当前指定的函数赋值computed属性的getter),进行上述的依赖收集
  • 如果computed的属性值又依赖了其他computed计算属性值,那么会将当前target暂存到栈中,先进行其他computed计算属性值的依赖收集,等其他计算属性依赖收集完成后,在从栈中pop出来,继续进行当前computed的依赖收集
    var vm = new Vue({
      el: '#demo',
      data: {
        firstName: 'Foo',
        lastName: 'Bar'
      },
      computed: {
        fullName: function () {
          return this.firstName + ' ' + this.lastName
        }
      }
    })

由于 this.firstName 和 this.lastName (上面是Vue官方示例)都是响应式变量,因此会触发它们的 getter,根据我们之前的分析,它们会把自身持有的 dep 添加到当前正在计算的 watcher 中,这个时候Dep.target就是这个 computed watcher,具体步骤如下:

  • data 属性初始化 getter setter
  • computed 计算属性初始化,提供的函数将用作属性 vm.fullName 的 getter
  • 当首次获取 fullName 计算属性的值时,Dep 开始依赖收集
  • 在执行 message getter 方法时,如果 Dep 处于依赖收集状态,则判定firstName和lastName为fullName 的依赖,并建立依赖关系
  • 当firstName或lastName 发生变化时,根据依赖关系,触发 fullName 的重新计算
  • 如果计算值没有发生变化,不会触发视图更新

通过以上的分析,我们知道计算属性本质上就是一个 computed watcher,也了解了它的创建过程和被访问触发 getter 以及依赖更新的过程,其实这是最新的计算属性的实现,之所以这么设计是因为 Vue 想确保不仅仅是计算属性依赖的值发生变化,而是当计算属性最终计算的值发生变化才会触发渲染 watcher 重新渲染,本质上是一种优化。

computed计算值为什么还可以依赖另外一个computed计算值

小提示:这个问题当时完全不知道,哎,官方源码的套路太深了......

周期函数有哪些(beforeCreated和created中间都做了什么)

初始化 data、props、computed、watcher、provide。官方源码具体位置src/core/instance/init.js:

    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')

小结

51信用卡的这次面试其实面试官考察的点还是蛮深入的,问了一些Vue底层源码的实现,总体感觉自己回答的还可以,但是面试官说:你应该去阿里.

Last Updated:
Contributors: leeguooooo