面试-Vue

230 阅读11分钟

那你能讲一讲MVVM吗?

Model层代表数据模型,View代表UI组件,ViewModel是View和Model层的桥梁,数据会绑定到viewModel层并自动将数据渲染到页面中,视图变化的时候会通知viewModel层更新数据。

简单说一下Vue2.x响应式数据原理

Vue是采用数据劫持,结合发布-订阅模式,通过Object.defineProperty来劫持各个属性的setter和getter,数据改变时,发布变化消息给Dep(发布者/依赖收集器),去听通知Watcher(订阅者/观察者)作出对应的回调去更新视图。

再说一下vue2.x中如何监测数组变化

使用了函数劫持的方式,重写了数组的方法,Vue将data中的数组进行了原型链重写,指向了自己定义的数组原型方法。这样当调用数组api时,可以通知依赖更新。如果数组中包含着引用类型,会对数组中的引用类型再次递归遍历进行监控。这样就实现了监测数组变化。

nextTick知道吗,实现原理是什么?

在下次 DOM 更新循环结束之后执行延迟回调。nextTick主要使用了宏任务和微任务。根据执行环境分别尝试采用
Promise
MutationObserver
setTimeout
定义了一个异步方法,多次调用nextTick会将方法存入队列中,通过这个异步方法清空当前队列

说一下Vue的生命周期

beforeCreate 是new Vue()之后触发的第一个钩子,在当前阶段data、methods、computed以及watch上的数据和方法都不能被访问。
created(实例已经创建,已完成数据观测,无法与Dom进行交互) 在实例创建完成后发生,当前阶段已经完成了数据观测,也就是可以使用数据,更改数据,在这里更改数据不会触发updated函数。可以做一些初始数据的获取,在当前阶段无法与Dom进行交互,如果非要想,可以通过vm.$nextTick来访问Dom。
beforeMount(虚拟Dom已经创建) 发生在挂载之前,在这之前template模板已导入渲染函数编译。而当前阶段虚拟Dom已经创建完成,即将开始渲染。在此时也可以对数据进行更改,不会触发updated。
mounted(虚拟Dom已经挂载,可以与Dom进行交互)在挂载完成后发生,在当前阶段,真实的Dom挂载完毕,数据完成双向绑定,可以访问到Dom节点,使用$refs属性对Dom进行操作。
beforeUpdate(响应式数据发生更新) 发生在更新之前,也就是响应式数据发生更新,虚拟dom重新渲染之前被触发,你可以在当前阶段进行更改数据,不会造成重渲染。
updated 发生在更新完成之后,当前阶段组件Dom已完成更新。要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新。

beforeDestroy 发生在实例销毁之前,在当前阶段实例完全可以被使用,我们可以在这时进行善后收尾工作,比如清除计时器。

destroyed 发生在实例销毁之后,这个时候只剩下了dom空壳。组件已被拆解,数据绑定被卸除,监听被移出,子实例也统统被销毁。

actived deactived

你的接口请求一般放在哪个生命周期中?

接口请求一般放在mounted中,但需要注意的是服务端渲染时不支持mounted,需要放到created中。

再说一下Computed和Watch

Computed中定义的属性具备缓存,适用于计算比较消耗性能的计算场景。
Watch更多的是观察的作用,可以监听某些数据执行回调

说一下v-if和v-show的区别

当条件不成立时,v-if不会渲染DOM元素,v-show操作的是样式(display),切换当前DOM的显示和隐藏。

组件中的data为什么是一个函数?

一个组件被复用多次的话,也就会创建多个实例。本质上,这些实例用的都是同一个构造函数。如果data是对象的话,对象属于引用类型,会影响到所有的实例。所以为了保证组件不同的实例之间data不冲突,data必须是一个函数。

说一下v-model的原理

v-model本质就是一个语法糖,可以看成是value + input方法的语法糖。 可以通过model属性的prop和event属性来进行自定义。原生的v-model,**会根据标签的不同生成不同的事件和属性。**

Vue2.x和Vue3.x渲染器的diff算法分别说一下

简单来说,diff算法有以下过程
1、同级比较,再比较子节点
2、先判断一方有子节点一方没有子节点的情况(如果新的children没有子节点,将旧的子节点移除)
3、比较都有子节点的情况(核心diff)
4、递归比较子节点

Vue2的核心Diff算法采用了双端比较的算法,同时从新旧children的两端开始进行比较,借助key值找到可复用的节点,再进行相关操作。相比React的Diff算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅。

Vue3.x借鉴了
ivi算法和 inferno算法
在创建VNode时就确定其类型,以及在mount/patch的过程中采用位运算来判断一个VNode的类型,在这个基础之上再配合核心的Diff算法,使得性能上较Vue2.x有了提升。(实际的实现可以结合Vue3.x源码看。)
该算法中还运用了动态规划的思想求解最长递归子序列。

再说一下虚拟Dom以及key属性的作用

由于在浏览器中操作DOM是很昂贵的。频繁的操作DOM,会产生一定的性能问题。这就是虚拟Dom的产生原因。

VirtualDOM映射到真实DOM要经历VNode的create、diff、patch等阶段。

「key的作用是尽可能的复用 DOM 元素。」
新旧 children 中的节点只有顺序是不同的时候,最佳的操作应该是通过移动元素的位置来达到更新的目的。
需要在新旧 children 的节点中保存映射关系,以便能够在旧 children 的节点中找到可复用的节点。key也就是children中节点的唯一标识。

keep-alive了解吗

keep-alive可以实现组件缓存,当组件切换时不会对当前组件进行卸载。
常用的两个属性include/exclude,允许组件有条件的进行缓存。
两个生命周期activated/deactivated,用来得知当前组件是否处于活跃状态。
keep-alive的中还运用了LRU(Least Recently Used)算法。
(又是数据结构与算法,原来算法在前端也有这么多的应用)

Vue中组件生命周期调用顺序说一下

组件的调用顺序都是先父后子,渲染完成的顺序是先子后父。
组件的销毁操作是先父后子,销毁完成的顺序是先子后父。

Vue2.x组件通信有哪些方式?

props,子->父 $on$emit
$parent$children
Provide、inject
Event Bus
Vuex
$attrs$listeners

Vue中v-if和v-for不能连用的原因:

v-for的优先级比v-if高一些。
如果连用会把v-if给v-for遍历出来的每一个元素都添加一下,容易造成性能浪费!

SSR了解吗?

SSR也就是服务端渲染,也就是将Vue在客户端把标签渲染成HTML的工作放在服务端完成,然后再把html直接返回给客户端。
SSR有着更好的SEO、并且首屏加载速度更快等优点。不过它也有一些缺点,比如我们的开发条件会受到限制,服务器端渲染只支持beforeCreate和created两个钩子,当我们需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于Node.js的运行环境。还有就是服务器会有更大的负载需求。

你都做过哪些Vue的性能优化?

编码阶段:
1、尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
2、v-if和v-for不能连用
3、如果需要使用v-for给每项元素绑定事件时使用事件代理
4、SPA 页面采用keep-alive缓存组件
5、在更多的情况下,使用v-if替代v-show
6、key保证唯一
7、使用路由懒加载、异步组件
8、防抖、节流
9、第三方模块按需导入
10、长列表滚动到可视区域动态加载
11、图片懒加载

SEO优化:
1、预渲染
2、服务端渲染SSR

打包优化:
1、压缩代码Tree Shaking/Scope Hoisting
2、使用cdn加载第三方模块
3、多线程打包happypack
4、splitChunks抽离公共文件
5、sourceMap优化

用户体验:
1、骨架屏
2、PWA

hash路由和history路由实现原理说一下

hash:
window.addEventListener('hashchange', e => {})

history:
replaceState
pushState
window.addEventListener('popstate', e => {})

探索「Vue」与「React」的区别

  • 监听数据变化的实现原理不同
  • Vue 通过 getter/setter以及一些函数的劫持,能精确知道数据变化,不需要特别的优化就能达到很好的性能
  • React 默认是通过比较引用的方式进行的,如果不优化(PureComponent/shouldComponentUpdate)可能导致大量不必要的VDOM的重新渲染 总之,react的性能优化需要手动去做,而vue的性能优化是自动的,但是vue的响应式机制也有问题,就是当state特别多的时候,Watcher也会很多,会导致卡顿,所以大型应用(状态特别多的)一般用react,更加可控。

组件通信的区别

  • 父组件通过 props 可以向子组件传递数据或者回调
  • 可以通过 context 进行跨层级的通信,这其实和 provide/inject 起到的作用差不多。

模板渲染方式的不同

  • React 是通过JSX渲染模板
  • Vue是通过一种拓展的HTML语法进行渲染

使用vue后怎么针对搜索引擎做SEO优化?

你知道vue2.0兼容IE哪个版本以上吗?

IE 9.0以上

你有看过vue推荐的风格指南吗?列举出你知道的几条

cn.vuejs.org/v2/style-gu…

vue中怎么重置data?

Object.assign(this.$data, this.$options.data())

你知道style加scoped属性的用途和原理吗

用途:防止全局同名CSS污染
原理:在标签加上v-data-xxx属性,再在选择器时加上对应[v-data-xxx],即CSS带属性选择器,以此完成类似作用域的选择方式

你有使用过babel-polyfill模块吗?主要是用来做什么的?

Babel默认只转换新的JavaScript句法(syntax),而不转换新的API,比如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转码。

举例来说,ES6在Array对象上新增了Array.from方法。Babel就不会转码这个方法。如果想让这个方法运行,必须使用babel-polyfill,为当前环境提供一个垫片。

Babel默认不转码的API非常多,详细清单可以查看babel-plugin-transform-runtime模块的definitions.js文件。

Vue.observable

vue2.6发布一个新的API,可以处理一些简单的跨组件共享数据状态的问题。

watch的属性用箭头函数定义结果会怎么样?

this是undefined,要更改的属性会报TypeError错误, Cannot read property 'xxx' of undefined

在vue项目中如果methods的方法用箭头函数定义结果会怎么样?

this是undefined,要更改的属性会报TypeError错误, Cannot read property 'xxx' of undefined

在vue事件中传入$event,使用e.target和e.currentTarget有什么区别

currentTarget 始终是监听事件者,而 target 是事件的真正发出者

vue给组件绑定自定义事件无效怎么解决?

你可能有很多次想要在一个组件的根元素上直接监听一个原生事件。这时,你可以使用 v-on 的 .native 修饰符

vue如果想扩展某个现有的组件时,怎么做呢?

  • 使用Vue.extend直接扩展
  • 使用Vue.mixin全局混入
  • HOC封装
  • 加slot扩展

vue怎么实现强制刷新组件?

1.如果要在组件内部中进行强制刷新 调用this.$forceUpdate()强制重新渲染组件 2.如果是刷新某个子组件 利用v-if指令的特性 当组件的key 值变更时,会自动的重新渲染

v-once的使用场景有哪些?

v-once 只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。

说说你对vue的表单修饰符.lazy的理解

input标签v-model用lazy修饰之后,vue并不会立即监听input Value的改变,会在input失去焦点之后,才会触发input Value的改变

怎么访问到子组件的实例或者子元素?

this.$refs

使用vue渲染大量数据时应该怎么优化?说下你的思路!

Object.freeze,其实本质就是不想要 vue 去观测吧。

<template></template>有什么用?

当做一个不可见的包裹元素,减少不必要的DOM元素,整个结构会更加清晰。

vue的is这个特性你有用过吗?主要用在哪些方面?

  • vue中is的属性引入是为了解决dom结构中对放入html的元素有限制的问题
  • 切换组件

你了解什么是函数式组件吗?

Stateless(无状态):组件自身是没有状态的 Instanceless(无实例):组件自身没有实例,也就是没有this

export default {
  functional: true,
  render(createElement, { data, children }) {
    return createElement( 'button', data, children );
  }
};

组件中写name选项有什么作用?

  • 项目使用keep-alive时,可搭配组件name进行缓存过滤
  • DOM做递归组件时需要调用自身name
  • vue-devtools调试工具里显示的组见名称是由vue中组件name决定的

说说你对provide和inject的理解

通过在父组件中provide一些数据然后再所有子组件中都可以通过inject获取使用该参数,主要是为了解决一些循环组件比如tree, menu, list等, 传参困难, 并且难以管理的问题, 主要用于组件封装, 常见于一些ui组件库

vue如何优化首页的加载速度?

  • 异步路由和异步加载
  • 还有分屏加载, 按需加载, 延时加载图片等, cdn, 域名才分
  • 不要什么东西动不动就打包到vendor中, 恶心
  • ssr直出,
  • webpack压缩HTML/CSS/JS,
  • 首屏css单独提取内联,
  • 关键资源Proload,
  • 图片:不缩放,使用webp、小图片base64,iconfont,
  • gzip,
  • dns-prefetch,
  • 静态资源单独域名,去掉cookie

jsonp是如何实现跨域的?

js文件是可以不受安全策略的限制而随便下载的

vue能监听到数组变化的方法有哪些?为什么这些方法能监听到呢?

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

白屏问题

首页加载慢,1可以采用js分包、懒加载。2可以利用SSR同构渲染。

你知道nextTick的原理吗?

用法:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

提到DOM的更新是异步执行的,只要数据发生变化,将会开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。

简单来说,就是当数据发生变化时,视图不会立即更新,而是等到同一事件循环中所有数据变化完成之后,再统一更新视图。

说说你对v-clock和v-pre指令的理解

v-cloak指令只是在标签中加入一个v-cloak自定义属性,在HTML还编译完成之后该属性会被删除。 v-pre可以用来阻止预编译,有v-pre指令的标签内部的内容不会被编译,会原样输出。

vue 不能检测数组和对象的变化。尽管如此我们还是有一些办法来回避这些限制并保证它们的响应性。

Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的。

// 单属性
this.$set(this.someObject,'b',2)
// 多属性
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })

Vue 不能检测以下数组的变动:

  • 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
  • 当你修改数组的长度时,例如:vm.items.length = newLength
vm.$set(vm.items, indexOfItem, newValue)
vm.items.splice(newLength)

vue项目有做过单元测试吗?

vue-test-utils.vuejs.org/zh/

如何解决vue打包vendor过大的问题?

1、在webpack.base.conf.js新增externals配置,表示不需要打包的文件,然后在index.html中通过CDN引入

externals: {
    "vue": "Vue",
    "vue-router": "VueRouter",
    "vuex": "Vuex",
    "element-ui": "ELEMENT",
    "BMap": "BMap"
}

www.ahwgs.cn/vue-cli3-bu…

vue-loader是什么?它有什么作用?

解析和转换 .vue 文件,提取出其中的逻辑代码 script、样式代码 style、以及 HTML 模版 template,再分别把它们交给对应的 Loader 去处理。