面试官都不知道的Vue题目答案

前言

当回答面试官问及的Vue问题,我们除了照本宣科的回答外,其实还可以根据少量的源码来秀一把,来体现出你对Vue的深度了解。

本文会陆续更新,此次涉及以下问题:

  1. “new Vue()做了什么?”
  2. “什么阶段才能访问DOM?”
  3. “谈谈你对Vue生命周期的理解。”
  4. 扩展:新生命周期钩子serverPrefetch是什么?
  5. “vue-router路由模式有几种?”
  6. “谈谈你对keep-alive的了解?”
  7. “了解Vue2.6+新全局API:Vue.observable()吗?”

1. new Vue() 做了什么?

new关键字代表实例化一个对象, 而Vue实际上是一个类, 源码位置是/src/core/instance/index.js

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
} 

接着我们跳转追踪至this._init(),即Vue.prototype._init,位于src\core\instance\init.js
_init()方法的内部有一系列 init* 的方法

Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // ...忽略,从第45行看起
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')
    // ...忽略
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
} 

1.1 这里我们概述一遍:

  1. initProxy,作用域代理,拦截组件内访问其它组件的数据。

  2. initLifecycle, 建立父子组件关系,在当前实例上添加一些属性和生命周期标识。如:$children$refs_isMounted等。

  3. initEvents,用来存放除@hook:生命周期钩子名称="绑定的函数"事件的对象。如:$on$emit等。

  4. initRender,用于初始化$slots$attrs$listeners

  5. initInjections,初始化inject,一般用于更深层次的组件通信,相当于加强版的props。用于组件库开发较多。

只要在上一层级的声明的provide,那么下一层级无论多深都能够通过inject来访问到provide的数据。这么做也是有明显的缺点:在任意层级都能访问,导致数据追踪比较困难,不知道是哪一个层级声明了这个或者不知道哪一层级或若干个层级使用。

  • initState,是很多选项初始化的汇总,包括:props、methods、data、computed 和 watch 等。

  • initProvide,初始化provide

  • vm.$mount,挂载实例。

2. 什么阶段才能访问DOM?

这个回答可以从beforeCreate以及 created 的调用时机谈起,我们根据上面的概述,来简化下代码:


callHook(vm, 'beforeCreate')
// 初始化 inject
// 初始化 props、methods、data、computed 和 watch
// 初始化 provide
callHook(vm, 'created')
// 挂载实例 vm.$mount(vm.$options.el) 

所以当面试官问你:

  • beforeCreate以及 created 调用时,哪些数据能用与否?

  • 什么阶段才能访问DOM?

  • 为什么created之后才挂载实例?

知道怎么回答了吧。

3. 谈谈你对Vue的生命周期的理解

常规回答这里就不说了,来稍微深入点的:

  1. created/mounted/updated/destroyed,以及对应的before钩子。分别是创建=>挂载=>更新=>销毁。

  2. Vue源码中定义了一个mergeHook函数来遍历一个常量数组LIFECYCLE_HOOKS,该数组实际上是由与生命周期钩子同名的字符串组成的数组。


// v2.6.10 最新版
var LIFECYCLE_HOOKS = [
    'beforeCreate',
    'created',
    'beforeMount',
    'mounted',
    'beforeUpdate',
    'updated',
    'beforeDestroy',
    'destroyed',
    'activated',
    'deactivated',
    'errorCaptured',
    // v2.6+ 
    'serverPrefetch'
]; 

于是,你可以答多activated & deactivated(keep-alive 组件激活/停用)、errorCaptured(v2.5 以上版本有的一个钩子,用于处理错误)这三个。

3.1 新生命周期钩子:serverPrefetch是什么?

可以看到,serverPrefetch前身是ssrPrefetch。顾名思义,这是用来处理ssr的。允许我们在渲染过程中“等待”异步数据。可在任何组件中使用,而不仅仅是路由组件。

image

这里我们贴出一段官方例子:


<!-- Item.vue -->
<template>
  <div v-if="item">{{ item.title }}</div>
  <div v-else>...</div>
</template>

<script>
export default {
  computed: {
    item () {
      return this.$store.state.items[this.$route.params.id]
    }
  },
  serverPrefetch () {
    return this.fetchItem()
  },
  mounted () {
    if (!this.item) {
      this.fetchItem()
    }
  },
  methods: {
    fetchItem () {
      // return the Promise from the action
      return this.$store.dispatch('fetchItem', this.$route.params.id)
    }
  }
}
</script> 
  • 绝大多数的面试官都不会去关注v2.6+ 以后的代码记录和变更。这里如果你说出这个v2.6.10的变化,啧啧…面试官肯定更加欣赏你。

3.2 生命周期钩子的合并策略

callHook(vm, 'created')讲,先判断组件的选项中有无对应名字的生命周期钩子,再判断是否有 parentVal(vm)。若存在parentVal(vm)且都有对应的生命周期钩子,则会将两者concat为一个数组(parentVal.concat(childVal))。所以,生命周期钩子其实是可以写成数组。如:


created: [
function () {
  console.log('first')
},
function () {
  console.log('second')
},
function () {
  console.log('third')
}] 

钩子函数将按顺序执行。

4. Vue-router 路由模式有几种?

三种 "hash" | "history" | "abstract",一般人只知道两种"hash" | "history"

这里贴出源码:


switch (mode) {
  case 'history':
    this.history = new HTML5History(this, options.base)
    break
  case 'hash':
    this.history = new HashHistory(this, options.base, this.fallback)
    break
  case 'abstract':
    this.history = new AbstractHistory(this, options.base)
    break
  default:
    if (process.env.NODE_ENV !== 'production') {
      assert(false, `invalid mode: ${mode}`)
    }
} 

# mode

类型: string

默认值: "hash" (浏览器环境) | "abstract" (Node.js 环境)

可选值: "hash" | "history" | "abstract"
配置路由模式:

  • hash: 使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器。

  • history: 依赖 HTML5 History API 和服务器配置。查看 HTML5 History 模式。

  • abstract: 支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式.

5. 谈谈你对keep-alive的了解?

先贴一个常规回答:

keep-alive是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。
在vue 2.1.0 版本之后,keep-alive新加入了两个属性: include(包含的组件缓存) 与 exclude(排除的组件不缓存,优先级大于include) 。

然后你可以开始骚了:

  1. Vue 源码中实现的一个全局抽象组件,通过自定义 render 函数并且利用了插槽来实现数据缓存和更新。它的定义在src/core/components/keep-alive.js 中:

export default {
  name: 'keep-alive',
  abstract: true,
  ...
} 
  1. 所有的抽象组件是通过定义abstract选项来声明的。抽象组件不渲染真实DOM,且不会出现在父子关系的路径上(initLifecycle会忽略抽象组件),相关代码片段:

if (parent && !options.abstract) {
  // abstract 即 `ptions.abstract`
  // while 循环查找第一个非抽象的父组件
  while (parent.$options.abstract && parent.$parent) {
    parent = parent.$parent
  }
  parent.$children.push(vm)
} 

6. 了解Vue2.6+新全局APIVue.observable()吗?

Vue2.6+新的全局API是Vue.observable(),它的使用方式:


import vue from vue;
const state = Vue.observable ({
   counter: 0,
});
export default {
   render () {
     return (
       <div>
         {state.counter}
           <button v-on:click={() => {state.counter ++; }}>
           Increment counter
         </ button>
       </ div>
     );
   },
}; 

而它定义在/src/core/global-api/index.js第48行:


import { observe } from 'core/observer/index'
// ...
// 2.6 explicit observable API
Vue.observable = <T>(obj: T): T => {
observe(obj)
return obj
} 

再看看它importobserve,最近一次提交在12/1/2018,唔。。。

image

核心就是暴露出****observe(obj)观测后的数据,代码啥都没改。懂了吧?

作者:前端劝退师
链接:https://juejin.im/post/5c959f74f265da610c068fa8

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,692评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,482评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,995评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,223评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,245评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,208评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,091评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,929评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,346评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,570评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,739评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,437评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,037评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,677评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,833评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,760评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,647评论 2 354

推荐阅读更多精彩内容