*1. 关于Vue的⽣命周期,下列哪项是不正确的?[单选题]
A、Vue 实例从创建到销毁的过程,就是⽣命周期。
B、⻚⾯⾸次加载会触发beforeCreate, created, beforeMount, mounted, beforeUpdate,
updated。
C、created表示完成数据观测,属性和⽅法的运算,初始化事件,$el属性还没有显示出来。
D、DOM 渲染在 mounted 中就已经完成了。
*2. 对于Vue中数据响应式原理的说法,下列哪项是不正确的?[多选题]
A、采⽤数据劫持⽅式,即Object.defifineProperty()劫持data中各属性来实现数据响应式
B、视图中的变化通过Watcher更新data中数据
C、若data中某属性多次发⽣变化,Watcher仅会进⼊更新队列⼀次
D、通过编译过程进⾏依赖收集
*3. 关于Vue组件间的参数传递,下列哪项是不正确的?[单选题]
A、⼦组件给⽗组件传值,使⽤$emit⽅法
B、⼦组件使⽤$emit('someEvent')派发事件,⽗组件使⽤@someEvent监听
C、祖孙组件间可以使⽤provide和inject⽅式跨层级相互传值
D、⽗组件给⼦组件传值,⼦组件通过props接受数据
4. 下列关于v-model的说法,哪项是不正确的?[单选题]
A、v-model能够实现双向绑定
B、v-model本质上是语法糖。它负责监听⽤户的输⼊事件以更新数据
C、v-model是内置指令,不能⽤在⾃定义组件上
D、对input使⽤v-model实际上是指定其:value和:input
5. 下列说法不正确的是哪项?[单选题]
A、key的作⽤主要是为了⾼效的更新虚拟DOM
B、若指定了组件的template选项,render函数不会执⾏
C、使⽤vm.$nextTick可以确保获得DOM异步更新的结果
D、若没有el选项,vm.$mount(dom)可将Vue实例挂载于指定元素上
6. 下列说法不正确的是哪项?[单选题]
A、使⽤ this.$parent查找当前组件的⽗组件。
B、使⽤ this.$children按顺序查找当前组件的直接⼦组件。
C、使⽤ this. 查 找 根 组 件 , 并 可 以 配 合 children遍历全部组件。
D、使⽤ this.$refs查找命名⼦组件。
7. 下列关于vuex描述,不正确的是哪项?[单选题]
A、Vuex 是⼀个状态管理模式
B、Vuex主要⽤于多视图间状态全局共享与管理
C、在Vuex中改变状态可以通过mutations和actions
D、Vuex通过Vue实现状态响应式,因此只能⽤于Vue
*8. 下列关于vue-router的描述,不正确的是哪项?[单选题]
A、vue-router常⽤模式有hash和history两种
B、可以通过addRoutes⽅法动态添加路由
C、可以通过beforeEnter对单个组件进⾏路由守卫
D、vue-router借助Vue实现路由信息响应式,因此只能⽤于Vue
*9. 关于vue服务端渲染,下列哪项说法是不正确的?[单选题]
A、通过服务端渲染,可以优化SEO抓取,提升⾸⻚加载速度
B、某些声明周期钩⼦函数(如beforeCreate、created)能同时运⾏在服务端和客户端
C、服务端渲染的vue.js是同构开发,因此vue扩展库可以在服务端应⽤中正常运⾏
D、组件渲染为服务器端的 HTML 字符串,将它们直接发送到浏览器,最后在客户端上"激活"为可
交互的应⽤
*10. 关于typescript在vue中的应⽤,哪项说法是不正确的?[单选题]
A、使⽤TypeScript可获得静态类型检查以及最新的ECMAScript特性
B、TypeScript是JavaScript类型的超集,它可以编译成纯JavaScript。意味着你完全可以⽤JS语法
编写TS代码
C、使⽤Vue.extend({})⽅式声明组件不能获得TypeScript类型推断能⼒
D、基于类的Vue组件中如果要声明初始数据可以直接声明为实例的属性,如: message: string
= 'Hello!'
11. 下列关于vue说法哪些是不正确的?[单选题]
A、vue简单易上⼿,性能⾼效,还便于与第三⽅库或既有项⽬整合
B、vue构建的项⽬复杂度增加较快,仅适合中⼩型项⽬
C、vue基于组件构建应⽤,代码组织简洁、易理解、易维护
D、vue借助虚拟DOM实现跨平台,服务端渲染,以及性能良好的DOM更新策略
*12. 下列关于vue原理哪些是正确的?[多选题]
A、Vue中数组变更通知通过拦截数组操作⽅法实现
B、编译器⽬标是创建渲染函数,渲染函数执⾏将得到VNode树
C、组件内data发⽣变化时会通知其对应Watcher执⾏异步更新
D、patching算法⾸先进⾏同层级⽐较,可能执⾏的操作是节点的增加、删除和更新
答案:
1B
2BD
3C
4C
5B
6B
7C
8C
9C
10C
11B
12ABCD
1. vue的生命周期
beforeCreated
created
beforeMount
mounted
beforeUpdate
updated
beforeDestory
destoryed
2. 第一个进入组件调用的生命周期
beforeCreated
created
beforeMount
mounted
如果有keep-alive ,后面只会重复触发activated
3.v-if v-show区别
v-if 是动态插入dom,重新触发渲染,页面也会触发重排重绘,切换时比较慢。
v-show只是样式的隐藏 和显示,占用空间但是,初次显示比较慢,但切换速度快。
4.v-if v-for优先级
v-for优先级更高,所以会执行所有for的内容里面在判断if
5. keep-alive了解
keep-alive是vue内置的组件,可以保留组件状态。
结合路由使用,activated 和 deactivated两个钩子函数,通过include和 exclude匹配过滤组件。
6.ref是什么
dom的引用,用于直接操作dom
7.nextTick是什么
vue改变dom元素结构后,会触发nextTick方法来实现dom数据更新后,执行的代码逻辑。
vue是异步执行dom更新,一旦发现数据变化,会把watcher加入到事件队列里。如果同一个watcher被多次触发,也只会推送一次,最终只执行一次dom更新,在下一次事件循环的时候,清空队列并执行dom更新。nextTick就是在执行dom更新后执行的钩子。
8.路由导航守卫有那些?
全局
router.beforeEach
router.beforeResolve
router.afterEach
路由
beforeEnter
组件内
beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave
9.vue 如何做样式穿透
sass和less 使用/deep/
stylus 使用 >>>
通用 ::v-deep
10.scoped原理
样式只有在当前组件生效,vue是通过postcss 转译实现
在对应的组件生成别名
<template>
<div class="example">111</div>
</template>
<style scoped>
.example{ color:red}
</style>
//生成...
<template>
<div class="example" data-v-212122>111</div>
</template>
<style>
.example[data-v-212122]{ color:red}
</style>
11.mvvm的理解
model 数据模型层
view 视图层
vm 连接mode和view的中间层 ,负责数据绑定 和 视图监听。
12.mvvm双向数据绑定原理
13.vuex是单向数据流,还是双向
单向
14.什么是虚拟dom
由于频繁直接操作dom,都需要遍历整个dom树,效率很低。通过一个js对象,记录变更的信息,由于只是修改对象属性,所以很高效,最后一次性 比对老的dom 只更新变化的内容。
15.组件间传值
props,emit , eventBus 也可以直接是Vue
16.什么是spa,有什么优点缺点
spa是单页面应用
优点:一次加载后,后面都是本地浏览器缓存,有点像客户端软件,交互快。
通过异步请求方式交互数据,有效的前后端分离,后端只关心接口处理,
减轻服务端的压力,展示和渲染都在前端浏览器完成。
后端的接口可以复用,通过json格式,可以在pc移动端都通用
缺点:首屏加载慢,很多资源都要第一次一次性加载完,容易白屏
不利于SEO优化
17.props和data优先级问题
props > methods > data > computed > watch
1. v-if和v-for哪个优先级更高?有哪些优化策略?
<template>
<p v-for="child in children" v-if="isFolder">{{child.title}}</p>
</template>
const app = new Vue({... })
console.log(app.$options.render);
从打印输出可以看出,_l 循环遍历的for ,里面再执行(isFolder)? 条件判断。
(function anonymous(
) {
with(this){return _c('div',{attrs:{"id":"demo"}},[_c('h1',[_v("xxx")]),_v(" "),
_l((children),function(child){return (isFolder)?_c('p',
[_v(_s(child.title))]):_e()})],2)}
})
vue会先将template模板 编译成AST抽象语法树。
从源码compiler/codegen/index.js 分析。
在genElement方法
export function genElement (el: ASTElement, state: CodegenState): string {
if (el.parent) {
el.pre = el.pre || el.parent.pre
}
if (el.staticRoot && !el.staticProcessed) {
return genStatic(el, state)
} else if (el.once && !el.onceProcessed) {
return genOnce(el, state)
} else if (el.for && !el.forProcessed) { //1.for命令优先执行
return genFor(el, state)
} else if (el.if && !el.ifProcessed) {//2.这里才执行if
return genIf(el, state)
} else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
return genChildren(el, state) || 'void 0'
} else if (el.tag === 'slot') {
return genSlot(el, state)
} else {
...}
答:
- 在源码中分析我们得出,vue把模板编译成AST抽象语法树时,genElement方法是先执行genFor方法,再执行genIf。所以for优先。
2.由于for先执行,无论如何判断,循环都会执行,浪费了性能。
3.优化:
(1) 把if提取到for的外部,优先判断。
(2) 如果出现在条件内部,可以提前把整个list 先过滤一遍,再交给template循环。
2. Vue组件data为什么必须是个函数而Vue的根实例则没有此限制?
<div id="demo">
<h1>test</h1>
<comp></comp>
<comp></comp>
</div>
Vue.component('comp', {
template:'<div @click="counter++">{{counter}}</div>',
data: {counter: 0}
})
// 创建实例
const app = new Vue({
el: '#demo',
}); //这里会报错
整体思路
由于局部组件存的动态创建,过程比较复杂,这里使用全局组件来对比。
- vue初始化,先调用initAssetRegisters方法,把所有全局组件的构造函数先初始化好,并且全局组件合并到vue的配置信息上。
- 这过程中会触发mergeOptions的strats.data 方法判断如果是组件时候,判断data是否为function 如果不是则警告提示。
- 根Vue或者组件实例化时,都会统一调用initData(),初始化data。
从源码中分析 src/core/global-api/assets.js vue初始化的时候调用了initAssetRegisters方法
import { ASSET_TYPES } from 'shared/constants'
import { isPlainObject, validateComponentName } from '../util/index'
export function initAssetRegisters (Vue: GlobalAPI) {
ASSET_TYPES.forEach(type => {
Vue[type] = function (
id: string,
definition: Function | Object
): Function | Object | void {
if (!definition) {
return this.options[type + 's'][id]
} else {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && type === 'component') {
validateComponentName(id)
}
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id
definition = this.options._base.extend(definition)
// this.options._base 等价于访问 Vue根实例
//这里最终调用Vue.extend方法,传入vue的component配置信息。
}
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition }
}
this.options[type + 's'][id] = definition
return definition
}
}
})
}
src/core/global-api/extend.js 定义了Vue.extend方法
Vue.extend = function (extendOptions: Object): Function {
extendOptions = extendOptions || {}
const Super = this
const SuperId = Super.cid
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
}
const name = extendOptions.name || Super.options.name
if (process.env.NODE_ENV !== 'production' && name) {
validateComponentName(name)
}
const Sub = function VueComponent (options) {
this._init(options)
}
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
Sub.cid = cid++
Sub.options = mergeOptions( //这里把根Vue和当前组件的配置信息进行合并,返回当前组件的构造函数
Super.options,
extendOptions
)
Sub['super'] = Super
...
}
}
src/core/util/options.js mergeOptions方法 进行合并
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
if (process.env.NODE_ENV !== 'production') {
checkComponents(child)
}
if (typeof child === 'function') {
child = child.options
}
normalizeProps(child, vm)
normalizeInject(child, vm)
normalizeDirectives(child)
if (!child._base) {
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm)
}
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
}
const options = {}
let key
for (key in parent) {
mergeField(key)
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)//这里通过vm是否有来判断是根实例,还是组件
}
return options
}
strats.data = function (
parentVal: any,
childVal: any,
vm?: Component
): ?Function {
if (!vm) {
if (childVal && typeof childVal !== 'function') {
process.env.NODE_ENV !== 'production' && warn(
'The "data" option should be a function ' +
'that returns a per-instance value in component ' +
'definitions.',
vm
)
return parentVal
}
return mergeDataOrFn(parentVal, childVal)
}
return mergeDataOrFn(parentVal, childVal, vm)
}
最终,无论是根vue实例化,还是组件实例化,都会统一走initData初始化。先实例化根vue,再实例化组件。
src\core\instance\state.js initState -> initData()
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm) //初始化
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm) //当是函数时候,会执行getData函数返回对象data
: data || {} //直接使用传入的data
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
...
}
export function getData (data: Function, vm: Component): any {
// #7573 disable dep collection when invoking data getters
pushTarget()
try {
return data.call(vm, vm) //这里只返回对象
} catch (e) {
handleError(e, vm, `data()`)
return {}
} finally {
popTarget()
}
}
总结:
1.在根Vue实例或者全局组件实例化时,会先把全局组件的构造函数初始化一次,然后通过initData统一实例化。
- 由于全局组件共用一个构造函数,所以在initData 初始化的时候,如果使用对象定义data,所有组件实例都会共用一个data对象,产生数据污染。而采用函数的方式通过工厂函数统一生成新data对象,有效规避的实例间共用污染问题。
- vue根实例全局只有一个,所以不会给其他实例修改,