面试又造火箭了!我该怎么办?实用套路来了!

avatar
公众号「村长学前端」 @B站「前端杨村长」

小伙伴们是否在面试中因为造火箭的问题而苦恼?

我不想抱怨什么,因为我就是那种让面试者造火箭的面试官:)

本文将从底层逻辑分析引起这种现状的原因并提出诚恳建议。

视频教程

村长特地录制了配套视频,手把手带领大家撸出自己的mini vue

从手写Vue到面试策略分析

欢迎各位小伙伴三连+关注,您的鼓励是我坚持下去的最大动力❤️

为什么面试会造火箭

因为市场供需关系变了,以前是小甜甜,现在是牛夫人。尤其初中级前端不像6、7年前那会儿随便找工作。竞争对手多了,用人单位就更挑剔,给同样的钱,当然选更有能力、有干劲的员工。同时面试官们发现大家简历千篇一律:熟练使用vue框架和全家桶,熟练使用element-uiiView等组件库,熟练使用axios获取服务端数据等等。此时如果不提高面试难度,很难区分面试者能力高下。

遇到造火箭问题怎么办

比如面试官问:

  • 为什么需要数据响应式?vue中是怎么实现的?
  • 为什么需要虚拟domdiff过程是怎样的?

小伙伴们选择去找答案,背下来,你这样解决不了问题,因为背的答案是死的,经不住推敲,稍微追问几下就露馅了。

我认为大家应该借此契机好好学习一下源码,不仅能找到问题答案,加深对API理解,还能学到很多算法、设计模式和工程化知识,对提高编程水平很有帮助。

源码学起来很困难怎么办

很多小伙伴也想通过阅读源码学习,但源码通常很庞大繁杂,很容易劝退。建议大家从一个mini版的实现入手。先打下一个很好的基础,掌握之后再去看源码就会简单不少。

造个火箭试试

我就以Vue为例,写一个mini版,然后我们再考虑那些造火箭问题。

Vue的设计理念

开始之前我们先看一下Vue的设计理念,这样后面写起来会更容易理解:

易理解、友好、性能好、易维护、可测试

代码中感受一下

没有使用Vue,01-no-vue.html:

<div id="app"></div>

<script>
  // 需求:
  // 1.有个title标题,想要显示在h3标签中
  // 2.2秒后title会变化
  const title = '我就是个标题'
  const h3 = document.createElement('h3')
  h3.textContent = title
  app.appendChild(h3)
  setTimeout(() => {
    h3.textContent = '我还是那个标题,但我变了'
  }, 2000);
</script>

特点是:

  • 用户要直接接触dom
  • dom操作也是业务一部分
  • 用户心智负担更重,开发效率底下

使用Vue,02-with-vue.html:

<div id="app">
  <h3>{{title}}</h3>
</div>

<script src="http://unpkg.com/vue"></script>
<script>
  // 需求:
  // 1.有个title标题,想要显示在h3标签中
  // 2.2秒后title会变化
  new Vue({
    data() {
      return {
        title: '我就是个标题'
      }
    },
    mounted() {
      setTimeout(() => {
        this.title = '我还是那个标题,但我变了'
      }, 2000);
    },
  }).$mount('#app')
</script>

重要变化是我们的app以数据驱动,能避免DOM操作,那么我们的目标就很明确了:

  • 要能知道数据发生变化
  • 变化之后能执行视图更新

造个轮子试试

基本结构:Vue构造函数和$mount方法

<div id="app"></div>

<script>
  function Vue(options) {}
  Vue.prototype.$mount = function() {}
</script>

利用defineProperty实现数据响应式,监控data中数据变化

function Vue(options) {
  // 响应式
  this.$options = options
  this.$data = options.data()
  observe(this.$data)
}
Vue.prototype.$mount = function () {}
// 遍历obj所有key做响应式处理
function observe(obj) {
  Object.keys(obj).forEach(key => {
    defineReactive(obj, key, obj[key])
  })
}
// 所谓响应式就是拦截对象属性访问
function defineReactive(obj, key, val) {
  Object.defineProperty(obj, key, {
    get() { return val },
    set(newVal) { val = newVal }
    }
  })
}

挂载:准备一个更新函数,负责视图初始化和后续更新

<script>
  Vue.prototype.$mount = function (sel) {
    // 创建更新函数
    this.update = function () {
      const child = this.$options.render.call(this)
      const parent = document.querySelector(sel)
        
      if (!this.isMounted) {
        // init
        parent.appendChild(child)

        this.isMounted = true
        if (this.$options.mounted) {
          this.$options.mounted.call(this)
        }
      } else {
        // update
        parent.innerHTML = ''
        parent.appendChild(child)
      }
    }

    this.update()
  }
  function observe(obj) {}
  function defineReactive(obj, key, val) {
    Object.defineProperty(obj, key, {
      get() {},
      set(newVal) {
        if (newVal !== val) {
          // 触发更新
          app.update();
        }
      }
    })
  }
</script>
<script>
  const app = new Vue({
		// 添加render函数负责渲染dom
    render() {
      const h3 = document.createElement('h3')
      h3.textContent = this.$data.title
      return h3
    }
  })
</script>

问题:每次更新都是全量更新视图

基于vnode实现,避免全量更新

<script>
  Vue.prototype.$mount = function (sel) {
    this.update = function () {
      // 执行render获取vnode
      const vnode = this.$options.render.call(this, this.createElement)
      
      if (!this.isMounted) {
        // init patch:传入parent是dom
        const parent = document.querySelector(sel)
        this.patch(parent, vnode)
      } else {
        // update patch:传入两个vnode做diff
        this.patch(this._vnode, vnode)
      }

      this._vnode = vnode
    }

    this.update()
  }

  // 加一个vnode生成函数
  Vue.prototype.createElement = function (tag, props, children) {
    return { tag, props, children }
  }

  // patch用于初始化或更新时转换vnode为dom
  Vue.prototype.patch = function (n1, n2) {
    if (n1.nodeType) {
      // init
      const child = this.createElm(n2)
      n1.appendChild(child)
      n2.$el = child
    } else {
      // update
    }

  }
  
  // 递归创建元素
  Vue.prototype.createElm = function (vnode) {
    const {tag, props, children} = vnode
    const el = document.createElement(tag)
    // 创建children
    if (Array.isArray(children)) {
      // element
      children.forEach(child => el.appendChild(createElm(children)))
    } else {
      // text
      el.textContent = children
    }
    vnode.$el = el
    return el
  }
</script>
<script>
  const app = new Vue({
    // render返回vnode
    render(h) {
      return h('h3', null, this.$data.title)
    }
  })
  app.$mount('#app')
</script>

更新逻辑:主要看双方children类型,针对性做dom操作,此处仅解决了测试用例中的文本情况

Vue.prototype.patch = function (n1, n2) {  if (n1.nodeType) {} else {    // 获取待操作dom    const el = n2.$el = n1.$el    // children更新    if (n1.tag === n2.tag) { // 是否相同节点,节点复用      if (typeof n1.children === 'string') {        if (typeof n2.children === 'string') {          // text update          if (n1.children !== n2.children) {            el.textContent = n2.children          }        } else {          // replace text with elements        }      } else {        if (typeof n2.children === 'string') {          // replace elements with text        } else {          // update children        }      }    } else {      // replace    }  }

再来思考回答策略

可使用四段体:介绍概念,说必要性,源码如何实现,实践中如何使用。例如:

  • 为什么需要数据响应式?怎么执行?

    介绍概念:数据响应式是MVVM这类框架中侦测数据变化的机制,三大框架中各不相同(知道就发挥一下)

    必要性:MVVM最重要的任务就是实现数据驱动,要实现数据驱动就必须要知道数据何时发生变化,从而做出响应,这就需要一套数据响应式机制。

    源码实现:vue 2.x中主要利用defineProperty,vue 3.x中主要利用Proxy(不知道就不提)。以vue 2.x为例通过遍历对象属性,定义get/set,做属性拦截,将来数据变化,就可以感知,并调用更新函数使视图更新。

    结合实践:实践中,我们传入组件的属性props,方法methods,数据data,都会在Vue初始化的时候统一做响应式处理,因此当它们发生变化,视图就会重新渲染,得以更新。也有些特殊情况,比如有新属性添加或删除,需要使用Vue.set/delete这样的API。

当然,小伙伴还要继续学习不少细节,比如:

  • 怎么通知视图更新(依赖收集,异步更新等知识点)
  • 视图是怎么更新的(虚拟dom和patch)
  • vue 2.x中响应式有啥问题(效率、额外api、数组处理等)
  • 为什么需要Vue.set/delete这样的API

你看全是和响应式这个点引申出来的,如果回答得当,基本妥妥的。

范例源码

关注公众号「村长学前端」自取

视频教程

村长特地录制了配套视频,手把手带领大家撸出自己的mini vue

从手写Vue到面试策略分析

欢迎各位小伙伴三连+关注,您的鼓励是我坚持下去的最大动力❤️