1、如何理解MVVM原理?
先解释MVVM是什么:也就是当M层数据进行修改时,VM层会监测到变化,并且通知V层进行相应的修改,反之修改V层则会通知M层数据进行修改,以此也实现了视图与模型层的相互绑定;
2、v-model实现原理?
model只不过是一个语法糖而已,真正的实现靠的还是
v-bind
:绑定响应式数据
触发oninput
事件并传递数据
<input v-model="sth" />
// 等同于
<input :value="sth" @input="sth = $event.target.value" /> //自html5开始,input每次输入都会触发oninput事件,所以输入时input的内容会绑定到sth中,于是sth的值就被改变;
//$event 指代当前触发的事件对象;
//$event.target 指代当前触发的事件对象的dom;
//$event.target.value 就是当前dom的value值;
3、响应式数据的原理是什么?(双向数据绑定原理?)
- 核心点: Object.defineProperty
- 默认 Vue 在初始化数据时,会给 data 中的属性使用 Object.defineProperty 重新定义所有属
性(只会劫持已经存在的属性)多层对象通过递归实现劫持,当页面取到对应属性时,会进行依赖收集(收集当前组件的watcher) 如果属性发生变化会通知相关依赖进行更新操作。
4、vue是如何检测数组变化的?
- 数组考虑到性能问题没有使用defineProperty对数组的没一项进行拦截,而是选择对数组的方法进行重写。
- 在vue中修改数组的索引和长度是无法进行监控的。需要通过以上7中变异的方法修改数组才会触发数组对应的watcher进行更新。数组中如果有对象类型也会递归劫持。
5、vue中模板编译原理?
- 将template模板转化成ast语法树
- 根据ast语法树生成render函数(字符串拼接 + new Function + with(this){ } )
- 使用render函数生成虚拟dom,在根据虚拟DOM创建正式dom替换
为什么要使用虚拟dom?
- 虚拟dom就是用js对象描述真实dom,是真实dom的抽象,直接操作dom性能低,但操作js层效率高
- 可以使用diff算法对比差异来更新,减少真实DOM操作
6、vue中的diff原理?
7、生命周期钩子如何实现的?
- Vue的生命周期钩子就是回调函数,当创建组件实例的过程中会调用响应的钩子方法。
- 内部主要使用callHook方法来调用对应的方法。核心是一个发布订阅模式,将钩子订阅好(内部Vue.$options内部会把全局钩子和实例的钩子进行合并,采用数组方式存储),在对应的阶段进行发布!
import {mergeOptions} from '../utils/index'
export function initGlobalAPI(Vue){
// 整合了所有全局相关的内容
Vue.options = {}
Vue.mixin = function(mixin){
// this是Vue
// mixin 合并 把mixin()混入进来的数据和Vue.options合并在一起
this.options = mergeOptions(this.options,mixin)
}
// 使用
// Vue.mixin({
// a:1,
// beforeCreate() {
// console.log('mixin1')
// }
// })
// Vue.mixin({
// b:2,
// beforeCreate() {
// console.log('mixin2')
// }
// })
// console.log(Vue.options)
}
-------------------------重点mergeOptions()
/ 合并策略 后续可以添加不同的策略
let strats = {}
// 添加生命周期的合并策略
LIFECYLE_HOOKS.forEach(hook=>{
strats[hook] = mergeHook
})
// 生命周期的合并策略
function mergeHook(parentVal,childVal){
if(childVal){ // 有新值
if(parentVal){ // 有新值 也有老值
return parentVal.concat(childVal)
}else{ // 只有新值 没有老值
return [childVal]
}
}else { // 没有新值
return parentVal
}
}
// 合并
export function mergeOptions(parent,child){
const options = {}
for(let key in parent){ // 把parent的属性和child比较,并合并到options
mergeField(key)
}
for(let key in child){ // 把child上没有合并过的属性 合并到options上
if(!parent.hasOwnProperty(key)){
mergeField(key)
}
}
// 默认的合并合并策略 但是有些属性 需要有特殊的合并方式
function mergeField(key){
if(strats[key]){ // 合并策略上有指定合并策略就使用合并策略
return options[key] = strats[key](parent[key],child[key])
}
// parent:{data:{name:'haha'}} child:{data:{age:'lalal'}} 两者key都是对象
if(typeof parent[key] === 'object' && typeof child[key] === 'object'){
options[key] = {
...parent[key],
...child[key]
} // 有重复的属性,child覆盖parent的属性
}else if(child[key] == null){ //child没有可以,使用parent的属性
options[key] = parent[key]
}else{ // parent和child的属性值一个是对象一个是值或者两个都是值 使用儿子的覆盖父亲的
options[key] = child[key]
}
}
return options;
}
---------------------------------- 初始化时new的实例会和Vue类上的的option合并
// 将用户传递的options和全局的options合并到自己身上
vm.$options=mergeOptions(vm.constructor.options,options) // vue中使用 this.$options 指代的就是用户new Vue是传递进来的数据
--------------------------- callHook调用指定生命周期
export function callHook(vm,hook){
const handlers = vm.$options[hook]
if(handlers){ // 找到对应生命钩子执行执行
for(let i=0;i<handlers.length;i++){
// call 为了保证当前生命周期内this指向当前实例
handlers[i].call(vm)
}
}
}
8、如何进行依赖收集的?
- 使用dep类来进行依赖管理,depend()方法用来收集依赖,notify()用来通知watcher更新
- vue在渲染的时候会new watcher来渲染,渲染中会对变量进行取值触发get进行依赖收集,更新时触发set去通知weather更新视图
9、为何Vue采用异步渲染?
因为如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染.所以为了性能考虑。 Vue会在本轮数据更新后,再去异步更新视图.内部调用的是nextTick实现延迟更新.
10、nextTick在哪里使用?原理是?
- nextTick的回调是dom更新循环结束之后执行的延迟回调。在修改数据之后立即使用这个方法,可以获取更新后的dom。原理:就是
异步方法
(promise/mutationObserver/setImmediate/setTimeout)- 补充回答:vue多次更新数据,最终会进行批量更新。内部调用的是nextTick实现延迟更新,用户自定义的nextTick回调会被延迟到更新完后调用,从而获取到更新后的dom
// 数据更新会触发update()方法,update()方法里面会调用queueWatcher(this)传入当前watcher
//schedular.js
let queue = [] // 存放watcher
let has = {}
import {nextTick} from '../utils/next-tick'
function flushSchedularQueue(){
queue.forEach(watcher=>watcher.run())
queue = []
has = {}
}
export function queueWatcher(watcher){
const id = watcher.id
if(has[id] == null){
queue.push(watcher)
has[id] = true
// vue里面使用Vue.nextTick
// Vue.nextTick = promise/mutationObserver/setImmediate/setTimeout
nextTick(flushSchedularQueue)
// setTimeout(function(){
// queue.forEach(watcher=>watcher.run())
// queue = []
// has = {}
// },0)
}
-----------------------
//next-tick.js
// nextTick 在修改数据之后立即使用这个方法,获得更新后的dom
// 所以我们 延迟执行
let callbacks = [] // 任务队列 [flushSchedularQueue,usenextTick]
let waiting = false
function flushCallback(){
callbacks.forEach(cb=>cb())
waiting = false
callbacks=[]
}
export function nextTick(cb){
// 第一次调用nextTick的肯定是我们vue中数据更新后调用的
// 之后 用户在多次调用 nextTick 是在修改数据之后
// 我们先执行 flushSchedularQueue 进行更新渲染,在执行usenextTick用户的回调
// 多次调用nextTick 如果没有刷新的时候就先把他放到数组中
// 刷新后更改 waiting
callbacks.push(cb)
if(waiting===false){
setTimeout(flushCallback,0)
waiting = true
}
}
11、vue的优缺点
优点:
- 轻量级框架
- 简单易学
- 数据驱动视图,不用再重复大量操作dom
- 组件化开发
缺点:
不适于seo优化,而且封装的比较厉害,可扩展性稍差,适合单人开发,适合中小型项目
12、vue中可以做的性能优化
-
data中的数据尽可能扁平化以免vue初始化时深度递归监测
-
不改变的数据使用object.freeze冻结,不让vue设置为响应式的数据
-
根据场景来选择v-if和v-show
-
对于没有使用vue语法的静态结构使用v-pre不让vue去解析
-
长列表中,不去直接渲染,而采用虚拟列表渲染 插件 - vue-virtual-scroll-list
-
组件懒加载 + 骨架屏
(组件懒加载)异步组件写法:
异步组件+骨架屏 (骨架屏同步导入的)
-
对于图片采用图片懒加载 插件 - vue-lazyload
-
UI组件库按需加载