前端⾯试Vue⾼频原理篇+详细解答,还有105道vue⾯试题集合
⼩编在⾥看到⼀句话,说三件套加vue就可以到实习,三件套估计说的是HTML+JavaScript+CSS,但还有这好事?这可不得赶紧把vue⾯试题安排上。实不实习不重要,主要是想学习哈哈哈哈(加狗头)。
也有⼩伙伴私信⼩编有没有vue的⾯试题,看来之前的满⾜不了⼤家呀,⼩编⼜整理了⼀套vue⾯试题集合,需要的⼩伙伴
⾯试题篇
1.⽼⽣常谈之, MPA/SPA 的理解,优缺点是什么?
MPA多页⾯应⽤。
构成:有多个页⾯html构成,
跳转⽅式:页⾯的跳转是从⼀个页⾯到另⼀个页⾯
刷新的⽅式:全页⾯刷新
页⾯数据跳转:依赖URL/cookie/localStorage
跳转后的资源会重新加载
优点:对 SEO ⽐较友好,开发难度低⼀点。
SPA单页⾯应⽤
页⾯组成:由⼀个外壳页⾯包裹,多个页⾯(组件)⽚段组成
跳转⽅式:在外壳页⾯中跳转,将⽚段页⾯(组件)显⽰或隐藏
刷新⽅式:页⾯⽚段的局部刷新
页⾯的数据跳转:组件间的传值⽐较容易
跳转后的资源不会重新加载
缺点:对 SEO 搜索不太友好需要单独做配置,开发难度⾼⼀点需要专门的开发框架
iframe 实际上是MPA,但是可以实现SPA的⼀些效果,但是本⾝由不少问题。
2.⽼⽣常谈之,为什么需要有这些 MVC/MVVM 模式?谈谈你对 MVC,MVVM 模式的区别,
⽬的:借鉴后端的思想,职责划分和分层
Vue, React 不是真正意义上的 MVVM 更不是 MVC,两者核⼼只处理视图层view。
MVC模式
单向的数据,⽤户的每⼀步操作都需要重新请求数据库来修改视图层的渲染,形成⼀个单向的闭环。⽐如jQuery+underscore+backbone。
M:model数据存放层
V: view:视图层页⾯
C: controller:控制器 js 逻辑层。
controller控制层将数据层model层的数据处理后显⽰在视图层view层,同样视图层view层接收⽤户的指令也可以通过控制层controller,作⽤到数据层model。所以MVC的缺点是视图层不能和数据层直接交互。
MVVM模式
隐藏了controller控制层,直接操控View视图层和Model数据层。
M:model 数据模型
V: view 视图模板
VM:view-model 视图数据模板(vue处理的层,vue 中的definedProperty 就是处理 VM 层的逻辑)
双向的数据绑定:model数据模型层通过数据绑定Data Bindings直接影响视图层View,同时视图层view通过监听Dom Listener也可以改变数据模型层model。
数据绑定和DOM事件监听就是viewModel层Vue主要做的事。也就是说:只要将数据模型层Model的数据挂载到ViewModel层Vue就可以实现双向的数据绑定。
加上vuex/redux可以作为vue和react的model数据层。
var vm = new Vue()
复制代码
vm 就是view-model数据模型层,data:就是vm view-model层所代理的数据。
综上两者的区别:MVC 的视图层和数据层交互需要通过控制层controller属于单向链接。MVVM 隐藏了控制层controller,让视图层和数据层可以直接交互属于双向连接。
3. 说⼀下对 Vue 中响应式数据的理解
⼩tip:响应式数据指的是数据发⽣了变化,视图可以更新就是响应式的数据
vue中实现了⼀个definedReactive⽅法,⽅法内部借⽤Object.definedProperty()给每⼀个属性都添加了get/set的属性。
definedReactive只能监控到最外层的对象,对于内层的对象需要递归劫持数据。
数组则是重写的7个push pop shift unshift reverse sort splice来给数组做数据拦截,因为这⼏个⽅法会改变原数组
扩展:
// src\core\observer\index.js
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// 准备给属性添加⼀个 dep 来依赖收集 Watcher ⽤于更新视图。
const dep = new Dep()
// some code
// observe() ⽤来观察值的类型,如果是属性也是对象就递归,为每个属性都加上`get/set`
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
// 这⾥取数据时依赖收集
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
// childOb 是对对像进⾏收集依赖
if (childOb) {
childOb.dep.depend()
//这⾥对数组和内部的数组进⾏递归收集依赖,这⾥数组的 key 和 value 都有dep。
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
// 属性发⽣改变,这⾥会通知 watcher 更新视图
}
})
}
复制代码
上⾯的 Dep(类) 是⽤来⼲嘛的?答:⽤来收集渲染的Watcher,Watcher⼜是⼀个啥东西?答:watcher是⼀个类,⽤于更新视图的
4. Vue 是怎么检测数组的变化的?
vue 没有对数组的每⼀项⽤definedProperty()来数据拦截,⽽是通过重写数组的⽅法push pop shift unshift reverse sort splice。
⼿动调⽤ notify,通知 render watcher,执⾏ update
数组中如果有对象类型(对象和数组)的话会进⾏数据拦截。
所以通过修改数组下标和数组长度是不会进⾏数据拦截的,也就不会有响应式变化。例如arr[0] = 1, arr.length = 2都不会有响应式
扩展:
// src\core\observer\array.js
const methodsToPatch = ['push','pop','shift','unshift','splice','sort','reverse']
methodsToPatch.forEach(function (method) {
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
// 新增的类型再次观察
if (inserted) ob.observeArray(inserted)
// ⼿动调⽤ notify 派发更新
ify()
return result
})
})
复制代码
5.Vue 是怎样依赖收集的?(dep 和 Watcher 是什么关系)
tip:Dep是⼀个⽤来负责收集Watcher的类,Watcher是⼀个封装了渲染视图逻辑的类,⽤于派发更新的。需要注意的是Watcher 是不能直接更新视图的还需要结合Vnode经过patch()中的diff算法才可以⽣成真正的DOM
每⼀个属性都有⾃⼰的dep属性,来存放依赖的Watcher,属性发⽣变化后会通知Watcher去更新。
在⽤户获取(getter) 数据时 Vue 给每⼀个属性都添加了dep属性来(collect as Dependency)收集Watcher。在⽤户setting设置属性值时ify()通知收集的Watcher重新渲染。
详情见上⾯的defineReactive()
Dep依赖收集类其和Watcher类是多对多双向存储的关系
每⼀个属性都可以有多个Watcher 类,因为属性可能在不同的组件中被使⽤。
同时⼀个Watcher 类也可以对应多个属性。
6. Vue 中的模板编译
Vue中模板编译:其实就是将template转化成render函数。说⽩了就是将真实的DOM(模板)编译成虚拟dom(Vnode)js合并两个数组
第⼀步是将template 模板字符串转换成ast 语法树 (parser 解析器),这⾥使⽤了⼤量的正则来匹配标签的名称,属性,⽂本等。
第⼆步是对 AST 进⾏静态节点static标记,主要⽤来做虚拟 DOM 的渲染优化(optimize优化器),这⾥会遍历出所有的⼦节点也做静态标记第三步是使⽤ast语法树重新⽣成render 函数代码字符串 code。(codeGen 代码⽣成器)
为什么要静态标记节点,如果是静态节点(没有绑定数据,前后不需要发⽣变化的节点)那么后续就不需要 diff 算法来作⽐较。
7. ⽣命周期钩⼦实现原理
vue 中的⽣命周期钩⼦只是⼀个回调函数,在创建组件实例化的过程中会调⽤对应的钩⼦执⾏。
使⽤Vue.mixin({})混⼊的钩⼦或⽣命周期中定义了多个函数,vue 内部会调⽤mergeHook()对钩⼦进⾏合并放⼊到队列中依次执⾏
扩展
// src\core\util\options.js
function mergeHook (
parentVal: ?Array<Function>,
childVal: ?Function | ?Array<Function>
): ?Array<Function> {
const res = childVal
parentVal
: Array.isArray(childVal)
childVal
: [childVal]
: parentVal
return res
dedupeHooks(res)
: res
}
复制代码
8.⽼⽣常谈之 vue ⽣命周期有哪些,⼀般在哪⾥发送请求?
beforeCreate: 刚开始初始化 vue 实例,在数据观测observer之前调⽤,还没有创建data/methods等属性
created: vue 实例初始化结束,所有的属性已经创建。
beforeMount: 在 vue 挂载数据到页⾯上之前,触发这个钩⼦,render 函数此时被触发。
mounted: el 被创建的vm.$el替换,vue 初始化的数据已经挂载到页⾯之上,这⾥可以访问到真实的 DOM。⼀般会在这⾥请求数据。
beforeUpdate: 数据更新时调⽤,也就是在虚拟 dom 重新渲染之前。
updated: 数据变化导致虚拟 dom 发⽣重新渲染之后发⽣。
beforeDestroy: 实例销毁之前调⽤该钩⼦,此时实例还在。vm.$destroy触发两个⽅法。
destroyed: Vue 实例销毁之后调⽤。所有的事件监听都会被接触。
请求数据要看具体的业务需求决定在哪⾥发送ajax
9.Vue.mixin({})的使⽤场景和原理
使⽤场景:⽤于抽离⼀个公共的业务逻辑实现复⽤。
实现原理:调⽤mergeOptions()⽅法采⽤策略模式针对不同的属性合并。混⼊的数据和组件的数据有冲突就采⽤组件本⾝的。
Vue.mixin({})缺陷,1.可能会导致混⼊的属性名和组件属性名发⽣命名冲突;2. 数据依赖的来源问题
扩展
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
// some code
if (!child._base) {
if (ds) {
parent = mergeOptions(parent, ds, 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)
}
return options
}
复制代码
10.⽼⽣常谈之 vue 组件中的data 为什么必须是⼀个函数?
这和 js 本⾝机制相关,data函数中返回的对象引⽤地址不同,就能保证不同组件之间的数据不相互污染。
Vue.mixin()中如果混⼊data属性,那么data也必须是⼀个函数。因为Vue.mixin()也可以多处使⽤。
实例中data可以是⼀个对象也可以是⼀个函数,因为我们⼀个页⾯⼀般只初始化⼀个Vue实例(单例)
11. ⽼⽣常谈之 vue 中 vm.$nextTick(cb)实现原理和场景
场景:在 dom 更新循环结束后调⽤,⽤于获取更新后的 dom 数据
实现原理:vm.$nextTick(cb)是⼀个异步的⽅法为了兼容性做了很多降级处理依次有promise.then,MutationObserver,setImmediate,setTimeout。在数据修改后不会马上更新视图,⽽是经过set⽅法 notify 通知Watcher更新,将需要更新的Watcher放⼊到⼀个异步队列中,nexTick的回调函数就放在Watcher的后⾯,等待主线程中同步代码执⾏借宿然后依次清空队列中,所以vm.nextTick(callback)是在dom更新结束后执⾏的。
上⾯将对列中Watcher依次清空就是vue 异步批量更新的原理。提⼀个⼩思考:为什么不直接使⽤setTimeout代替?因为setTimeout是⼀个宏任务,宏任务多性能也会差。12.⽼⽣常谈之 watch 和 computed 区别
computed内部就是根据Object.definedProperty()实现的
computed具备缓存功能,依赖的值不发⽣变化,就不会重新计算。
watch是监控值的变化,值发⽣变化时会执⾏对应的回调函数。
computed和watch都是基于Watcher类来执⾏的。
computed缓存功能依靠⼀个变量dirty,表⽰值是不是脏的默认是true,取值后是false,再次取值时dirty还是false直接将还是上⼀次的取值返回。
// src\core\instance\state.js computed 取值函数
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) {  // 判断值是不是脏 dirty
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
// src\core\instance\state.js watch 实现
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
// 实例化 watcher
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
const info = `callback for immediate watcher "${pression}"`
pushTarget()
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
popTarget()
}
return function unwatchFn () {
}

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。