关于vue.js:必会vue面试题附答案

57次阅读

共计 9778 个字符,预计需要花费 25 分钟才能阅读完成。

vue 初始化页面闪动问题

应用 vue 开发时,在 vue 初始化之前,因为 div 是不归 vue 管的,所以咱们写的代码在还没有解析的状况下会容易呈现花屏景象,看到相似于 {{message}} 的字样,尽管个别状况下这个工夫很短暂,然而还是有必要让解决这个问题的。

首先:在 css 里加上以下代码:

[v-cloak] {display: none;}

如果没有彻底解决问题,则在根元素加上style="display: none;" :style="{display:'block'}"

v-show 与 v-if 有什么区别?

v-if 真正 的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是 惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。

v-show 就简略得多——不论初始条件是什么,元素总是会被渲染,并且只是简略地基于 CSS 的“display”属性进行切换。

所以,v-if 实用于在运行时很少扭转条件,不须要频繁切换条件的场景;v-show 则实用于须要十分频繁切换条件的场景。

你有对 Vue 我的项目进行哪些优化?

(1)代码层面的优化

  • v-if 和 v-show 辨别应用场景
  • computed 和 watch 辨别应用场景
  • v-for 遍历必须为 item 增加 key,且防止同时应用 v-if
  • 长列表性能优化
  • 事件的销毁
  • 图片资源懒加载
  • 路由懒加载
  • 第三方插件的按需引入
  • 优化有限列表性能
  • 服务端渲染 SSR or 预渲染

(2)Webpack 层面的优化

  • Webpack 对图片进行压缩
  • 缩小 ES6 转为 ES5 的冗余代码
  • 提取公共代码
  • 模板预编译
  • 提取组件的 CSS
  • 优化 SourceMap
  • 构建后果输入剖析
  • Vue 我的项目的编译优化

(3)根底的 Web 技术的优化

  • 开启 gzip 压缩
  • 浏览器缓存
  • CDN 的应用
  • 应用 Chrome Performance 查找性能瓶颈

为什么 Vue 采纳异步渲染呢?

Vue 是组件级更新,如果不采纳异步更新,那么每次更新数据都会对以后组件进行从新渲染,所以为了性能, Vue 会在本轮数据更新后,在异步更新视图。核心思想 nextTick

dep.notify() 告诉 watcher 进行更新, subs[i].update 顺次调用 watcher 的 update queueWatcher 将 watcher 去重放入队列,nextTick( flushSchedulerQueue)在下一 tick 中刷新 watcher 队列(异步)。

keep-alive 应用场景和原理

keep-alive 是 Vue 内置的一个组件,能够实现组件缓存,当组件切换时不会对以后组件进行卸载。

  • 罕用的两个属性 include/exclude,容许组件有条件的进行缓存。
  • 两个生命周期 activated/deactivated,用来得悉以后组件是否处于沉闷状态。
  • keep-alive 的中还使用了 LRU(最近起码应用) 算法,抉择最近最久未应用的组件予以淘汰。

能说下 vue-router 中罕用的 hash 和 history 路由模式实现原理吗?

(1)hash 模式的实现原理

晚期的前端路由的实现就是基于 location.hash 来实现的。其实现原理很简略,location.hash 的值就是 URL 中 # 前面的内容。比方上面这个网站,它的 location.hash 的值为 ‘#search’:

https://www.word.com#search

hash 路由模式的实现次要是基于上面几个个性:

  • URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 局部不会被发送;

hash 值的扭转,都会在浏览器的拜访历史中减少一个记录。因而咱们能通过浏览器的回退、后退按钮管制 hash 的切换;

  • 能够通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 的 hash 值会产生扭转;或者应用 JavaScript 来对 loaction.hash 进行赋值,扭转 URL 的 hash 值;
  • 咱们能够应用 hashchange 事件来监听 hash 值的变动,从而对页面进行跳转(渲染)。

(2)history 模式的实现原理

HTML5 提供了 History API 来实现 URL 的变动。其中做最次要的 API 有以下两个:history.pushState() 和 history.repalceState()。这两个 API 能够在不进行刷新的状况下,操作浏览器的历史纪录。惟一不同的是,前者是新增一个历史记录,后者是间接替换以后的历史记录,如下所示:

window.history.pushState(null, null, path);
window.history.replaceState(null, null, path);

history 路由模式的实现次要基于存在上面几个个性:

  • pushState 和 repalceState 两个 API 来操作实现 URL 的变动;
  • 咱们能够应用 popstate 事件来监听 url 的变动,从而对页面进行跳转(渲染);
  • history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时咱们须要手动触发页面跳转(渲染)。

参考 前端进阶面试题具体解答

Vue 模版编译原理晓得吗,能简略说一下吗?

简略说,Vue 的编译过程就是将 template 转化为 render 函数的过程。会经验以下阶段:

  • 生成 AST 树
  • 优化
  • codegen

首先解析模版,生成AST 语法树(一种用 JavaScript 对象的模式来形容整个模板)。应用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相干解决。

Vue 的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变动,对应的 DOM 也不会变动。那么优化过程就是深度遍历 AST 树,依照相干条件对树节点进行标记。这些被标记的节点 (动态节点) 咱们就能够 跳过对它们的比对,对运行时的模板起到很大的优化作用。

编译的最初一步是 将优化后的 AST 树转换为可执行的代码

vue 和 react 的区别

=> 相同点:

1. 数据驱动页面,提供响应式的试图组件
2. 都有 virtual DOM, 组件化的开发,通过 props 参数进行父子之间组件传递数据,都实现了 webComponents 标准
3. 数据流动单向,都反对服务器的渲染 SSR
4. 都有反对 native 的办法,react 有 React native,vue 有 wexx

=> 不同点:

 1. 数据绑定:Vue 实现了双向的数据绑定,react 数据流动是单向的
 2. 数据渲染:大规模的数据渲染,react 更快
 3. 应用场景:React 配合 Redux 架构适宜大规模多人合作简单我的项目,Vue 适宜小快的我的项目
 4. 开发格调:react 举荐做法 jsx + inline style 把 html 和 css 都写在 js 了
      vue 是采纳 webpack + vue-loader 单文件组件格局,html, js, css 同一个文件

Vue 模版编译原理晓得吗,能简略说一下吗?

简略说,Vue 的编译过程就是将 template 转化为 render 函数的过程。会经验以下阶段:

  • 生成 AST 树
  • 优化
  • codegen

首先解析模版,生成AST 语法树(一种用 JavaScript 对象的模式来形容整个模板)。应用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相干解决。

Vue 的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变动,对应的 DOM 也不会变动。那么优化过程就是深度遍历 AST 树,依照相干条件对树节点进行标记。这些被标记的节点 (动态节点) 咱们就能够 跳过对它们的比对,对运行时的模板起到很大的优化作用。

编译的最初一步是 将优化后的 AST 树转换为可执行的代码

v-for 为什么要加 key

如果不应用 key,Vue 会应用一种最大限度缩小动静元素并且尽可能的尝试就地批改 / 复用雷同类型元素的算法。key 是为 Vue 中 vnode 的惟一标记,通过这个 key,咱们的 diff 操作能够更精确、更疾速

更精确:因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 比照中能够防止就地复用的状况。所以会更加精确。

更疾速:利用 key 的唯一性生成 map 对象来获取对应节点,比遍历形式更快

created 和 mounted 的区别

  • created: 在模板渲染成 html 前调用,即通常初始化某些属性值,而后再渲染成视图。
  • mounted: 在模板渲染成 html 后调用,通常是初始化页面实现后,再对 html 的 dom 节点进行一些须要的操作。

说一下 Vue 的生命周期

Vue 实例有⼀个残缺的⽣命周期,也就是从开始创立、初始化数据、编译模版、挂载 Dom -> 渲染、更新 -> 渲染、卸载 等⼀系列过程,称这是 Vue 的⽣命周期。

  1. beforeCreate(创立前):数据观测和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能拜访到 data、computed、watch、methods 上的办法和数据。
  2. created(创立后):实例创立实现,实例上配置的 options 包含 data、computed、watch、methods 等都配置实现,然而此时渲染得节点还未挂载到 DOM,所以不能拜访到 $el 属性。
  3. beforeMount(挂载前):在挂载开始之前被调用,相干的 render 函数首次被调用。实例已实现以下的配置:编译模板,把 data 外面的数据和模板生成 html。此时还没有挂载 html 到页面上。
  4. mounted(挂载后):在 el 被新创建的 vm.$el 替换,并挂载到实例下来之后调用。实例已实现以下的配置:用下面编译好的 html 内容替换 el 属性指向的 DOM 对象。实现模板中的 html 渲染到 html 页面中。此过程中进行 ajax 交互。
  5. beforeUpdate(更新前):响应式数据更新时调用,此时尽管响应式数据更新了,然而对应的实在 DOM 还没有被渲染。
  6. updated(更新后):在因为数据更改导致的虚构 DOM 从新渲染和打补丁之后调用。此时 DOM 曾经依据响应式数据的变动更新了。调用时,组件 DOM 曾经更新,所以能够执行依赖于 DOM 的操作。然而在大多数状况下,应该防止在此期间更改状态,因为这可能会导致更新有限循环。该钩子在服务器端渲染期间不被调用。
  7. beforeDestroy(销毁前):实例销毁之前调用。这一步,实例依然齐全可用,this 仍能获取到实例。
  8. destroyed(销毁后):实例销毁后调用,调用后,Vue 实例批示的所有货色都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。

另外还有 keep-alive 独有的生命周期,别离为 activateddeactivated。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 activated 钩子函数。

虚构 DOM 实现原理?

  • 虚构 DOM 实质上是 JavaScript 对象, 是对实在 DOM 的形象
  • 状态变更时,记录新树和旧树的差别
  • 最初把差别更新到真正的 dom 中

v-show 与 v-if 有什么区别?

v-if 真正 的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是 惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。

v-show 就简略得多——不论初始条件是什么,元素总是会被渲染,并且只是简略地基于 CSS 的“display”属性进行切换。

所以,v-if 实用于在运行时很少扭转条件,不须要频繁切换条件的场景;v-show 则实用于须要十分频繁切换条件的场景。

Vue 要做权限治理该怎么做?管制到按钮级别的权限怎么做?

剖析

  • 综合实际题目,理论开发中常常须要面临权限治理的需要,考查理论利用能力。
  • 权限治理个别需要是两个:页面权限和按钮权限,从这两个方面阐述即可。

思路

  • 权限治理需要剖析:页面和按钮权限
  • 权限治理的实现计划:分后端计划和前端计划论述
  • 说说各自的优缺点

答复范例

  1. 权限治理个别需要是页面权限和按钮权限的治理
  2. 具体实现的时候分后端和前端两种计划:
  3. 前端计划 会把所有路由信息在前端配置,通过路由守卫要求用户登录,用户登录后依据角色过滤出路由表。比方我会配置一个asyncRoutes 数组,须要认证的页面在其路由的 meta 中增加一个 roles 字段,等获取用户角色之后取两者的交加,若后果不为空则阐明能够拜访。此过滤过程完结,剩下的路由就是该用户能拜访的页面,最初通过 router.addRoutes(accessRoutes) 形式动静增加路由即可
  • 后端计划 会把所有页面路由信息存在数据库中,用户登录的时候依据其角色查问失去其能拜访的所有页面路由信息返回给前端,前端再通过addRoutes 动静增加路由信息
  • 按钮权限的管制通常会 实现一个指令 ,例如v-permission,将按钮要求角色通过值传给 v -permission 指令,在指令的 moutned 钩子中能够判断以后用户角色和按钮是否存在交加,有则保留按钮,无则移除按钮
  • 纯前端计划的长处是实现简略,不须要额定权限治理页面,然而保护起来问题比拟大,有新的页面和角色需要就要批改前端代码从新打包部署;服务端计划就不存在这个问题,通过专门的角色和权限治理页面,配置页面和按钮权限信息到数据库,利用每次登陆时获取的都是最新的路由信息,堪称一劳永逸!

可能的诘问

  1. 相似 Tabs 这类组件能不能应用 v-permission 指令实现按钮权限管制?
<el-tabs> 
  <el-tab-pane label="⽤户治理" name="first"> ⽤户治理 </el-tab-pane> 
    <el-tab-pane label="⻆⾊治理" name="third"> ⻆⾊治理 </el-tab-pane>
</el-tabs>
  1. 服务端返回的路由信息如何增加到路由器中?
// 前端组件名和组件映射表
const map = {//xx: require('@/views/xx.vue').default // 同步的⽅式
  xx: () => import('@/views/xx.vue') // 异步的⽅式
}
// 服务端返回的 asyncRoutes
const asyncRoutes = [{ path: '/xx', component: 'xx',...}
]
// 遍历 asyncRoutes,将 component 替换为 map[component]
function mapComponent(asyncRoutes) {
  asyncRoutes.forEach(route => {route.component = map[route.component];
    if(route.children) {route.children.map(child => mapComponent(child))
    }
    })
}
mapComponent(asyncRoutes)

对前端路由的了解

在前端技术晚期,一个 url 对应一个页面,如果要从 A 页面切换到 B 页面,那么必然随同着页面的刷新。这个体验并不好,不过在最后也是无奈之举——用户只有在刷新页面的状况下,才能够从新去申请数据。

起初,扭转产生了——Ajax 呈现了,它容许人们在不刷新页面的状况下发动申请;与之共生的,还有“不刷新页面即可更新页面内容”这种需要。在这样的背景下,呈现了 SPA(单页面利用)。

SPA 极大地晋升了用户体验,它容许页面在不刷新的状况下更新页面内容,使内容的切换更加晦涩。然而在 SPA 诞生之初,人们并没有思考到“定位”这个问题——在内容切换前后,页面的 URL 都是一样的,这就带来了两个问题:

  • SPA 其实并不知道以后的页面“停顿到了哪一步”。可能在一个站点下通过了重复的“后退”才终于唤出了某一块内容,然而此时只有刷新一下页面,所有就会被清零,必须反复之前的操作、才能够从新对内容进行定位——SPA 并不会“记住”你的操作。
  • 因为有且仅有一个 URL 给页面做映射,这对 SEO 也不够敌对,搜索引擎无奈收集全面的信息

为了解决这个问题,前端路由呈现了。

前端路由能够帮忙咱们在仅有一个页面的状况下,“记住”用户以后走到了哪一步——为 SPA 中的各个视图匹配一个惟一标识。这意味着用户后退、后退触发的新内容,都会映射到不同的 URL 下来。此时即使他刷新页面,因为以后的 URL 能够标识出他所处的地位,因而内容也不会失落。

那么如何实现这个目标呢?首先要解决两个问题:

  • 当用户刷新页面时,浏览器会默认依据以后 URL 对资源进行从新定位(发送申请)。这个动作对 SPA 是不必要的,因为咱们的 SPA 作为单页面,无论如何也只会有一个资源与之对应。此时若走失常的申请 - 刷新流程,反而会使用户的后退后退操作无奈被记录。
  • 单页面利用对服务端来说,就是一个 URL、一套资源,那么如何做到用“不同的 URL”来映射不同的视图内容呢?

从这两个问题来看,服务端曾经齐全救不了这个场景了。所以要靠咱们前端自力更生,不然怎么叫“前端路由”呢?作为前端,能够提供这样的解决思路:

  • 拦挡用户的刷新操作,防止服务端自觉响应、返回不合乎预期的资源内容。把刷新这个动作齐全放到前端逻辑里消化掉。
  • 感知 URL 的变动。这里不是说要革新 URL、凭空制作出 N 个 URL 来。而是说 URL 还是那个 URL,只不过咱们能够给它做一些渺小的解决——这些解决并不会影响 URL 自身的性质,不会影响服务器对它的辨认,只有咱们前端感知的到。一旦咱们感知到了,咱们就依据这些变动、用 JS 去给它生成不同的内容。

子组件能够间接扭转父组件的数据吗?

子组件不能够间接扭转父组件的数据。这样做次要是为了保护父子组件的单向数据流。每次父级组件产生更新时,子组件中所有的 prop 都将会刷新为最新的值。如果这样做了,Vue 会在浏览器的控制台中收回正告。

Vue 提倡单向数据流,即父级 props 的更新会流向子组件,然而反过来则不行。这是为了避免意外的扭转父组件状态,使得利用的数据流变得难以了解,导致数据流凌乱。如果毁坏了单向数据流,当利用简单时,debug 的老本会十分高。

只能通过 $emit 派发一个自定义事件,父组件接管到后,由父组件批改。

生命周期钩子是如何实现的

Vue 的生命周期钩子外围实现是利用公布订阅模式先把用户传入的的生命周期钩子订阅好(外部采纳数组的形式存储)而后在创立组件实例的过程中会一次执行对应的钩子办法(公布)

相干代码如下

export function callHook(vm, hook) {
  // 顺次执行生命周期对应的办法
  const handlers = vm.$options[hook];
  if (handlers) {for (let i = 0; i < handlers.length; i++) {handlers[i].call(vm); // 生命周期外面的 this 指向以后实例
    }
  }
}

// 调用的时候
Vue.prototype._init = function (options) {
  const vm = this;
  vm.$options = mergeOptions(vm.constructor.options, options);
  callHook(vm, "beforeCreate"); // 初始化数据之前
  // 初始化状态
  initState(vm);
  callHook(vm, "created"); // 初始化数据之后
  if (vm.$options.el) {vm.$mount(vm.$options.el);
  }
};

虚构 DOM 的解析过程

虚构 DOM 的解析过程:

  • 首先对将要插入到文档中的 DOM 树结构进行剖析,应用 js 对象将其示意进去,比方一个元素对象,蕴含 TagName、props 和 Children 这些属性。而后将这个 js 对象树给保留下来,最初再将 DOM 片段插入到文档中。
  • 当页面的状态产生扭转,须要对页面的 DOM 的构造进行调整的时候,首先依据变更的状态,从新构建起一棵对象树,而后将这棵新的对象树和旧的对象树进行比拟,记录下两棵树的的差别。
  • 最初将记录的有差别的中央利用到真正的 DOM 树中去,这样视图就更新了。

Vue 是如何收集依赖的?

在初始化 Vue 的每个组件时,会对组件的 data 进行初始化,就会将由一般对象变成响应式对象,在这个过程中便会进行依赖收集的相干逻辑,如下所示∶

function defieneReactive (obj, key, val){const dep = new Dep();
  ...
  Object.defineProperty(obj, key, {
    ...
    get: function reactiveGetter () {if(Dep.target){dep.depend();
        ...
      }
      return val
    }
    ...
  })
}

以上只保留了要害代码,次要就是 const dep = new Dep()实例化一个 Dep 的实例,而后在 get 函数中通过 dep.depend() 进行依赖收集。(1)Dep Dep 是整个依赖收集的外围,其要害代码如下:

class Dep {
  static target;
  subs;

  constructor () {
    ...
    this.subs = [];}
  addSub (sub) {this.subs.push(sub)
  }
  removeSub (sub) {remove(this.sub, sub)
  }
  depend () {if(Dep.target){Dep.target.addDep(this)
    }
  }
  notify () {const subs = this.subds.slice();
    for(let i = 0;i < subs.length; i++){subs[i].update()}
  }
}

Dep 是一个 class,其中有一个关 键的动态属性 static,它指向了一个全局惟一 Watcher,保障了同一时间全局只有一个 watcher 被计算,另一个属性 subs 则是一个 Watcher 的数组,所以 Dep 实际上就是对 Watcher 的治理,再看看 Watcher 的相干代码∶

(2)Watcher

class Watcher {
  getter;
  ...
  constructor (vm, expression){
    ...
    this.getter = expression;
    this.get();}
  get () {pushTarget(this);
    value = this.getter.call(vm, vm)
    ...
    return value
  }
  addDep (dep){
        ...
    dep.addSub(this)
  }
  ...
}
function pushTarget (_target) {Dep.target = _target}

Watcher 是一个 class,它定义了一些办法,其中和依赖收集相干的次要有 get、addDep 等。

(3)过程

在实例化 Vue 时,依赖收集的相干过程如下∶
初 始 化 状 态 initState,这 中 间 便 会 通 过 defineReactive 将数据变成响应式对象,其中的 getter 局部便是用来依赖收集的。
初始化最终会走 mount 过程,其中会实例化 Watcher,进入 Watcher 中,便会执行 this.get() 办法,

updateComponent = () => {vm._update(vm._render())
}
new Watcher(vm, updateComponent)

get 办法中的 pushTarget 实际上就是把 Dep.target 赋值为以后的 watcher。

this.getter.call(vm,vm),这里的 getter 会执行 vm._render() 办法,在这个过程中便会触发数据对象的 getter。那么每个对象值的 getter 都持有一个 dep,在触发 getter 的时候会调用 dep.depend() 办法,也就会执行 Dep.target.addDep(this)。方才 Dep.target 曾经被赋值为 watcher,于是便会执行 addDep 办法,而后走到 dep.addSub() 办法,便将以后的 watcher 订阅到这个数据持有的 dep 的 subs 中,这个目标是为后续数据变动时候能告诉到哪些 subs 做筹备。所以在 vm._render() 过程中,会触发所有数据的 getter,这样便曾经实现了一个依赖收集的过程。

正文完
 0