1、谈一下你对MVVM原理的理解
- 传统的mvc指的是,用户操作会请求服务端路由,路由会调用对应的控制器来处理,控制器会获取数据。将结果返回给前端,页面重新进行渲染。
- mvvm:传统的前端会将数据手动渲染到页面上,mvvm模式不需要用户收到操作dom元素,将数据绑定到viewModel层上,会自动将数据渲染到页面中,视图变化会通知viewModel层更新数据。viewModel就是我们mvvm模式中的桥梁。
2、请说一下响应式数据的原理?
理解:
- 1、核心点:object.defineProperty
- 2、默认vue在初始化数据时,会给data中的object.defineProperty重新定义所有属性,当页面取到对应属性时。会进行依赖收集(收集当前组件的watcher)如果属性发生变化会通知相关依赖进行更新操作。
原理:
- initData 初始化用户传入的data数据
- new Observer 将数据进行观测
- this.walk(value) 进行对象的处理
- defineReactive 循环对象属性定义响应式变化
- Object.defineProperty 使用Object.defineProperty重新定义数据
拦截属性的获取-进行依赖收集 拦截属性的更新操作,对相关依赖进行通知
Object.defineProperty(obj,key,{
enumerable:true,
configurable:true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend() // ** 收集依赖 **/
if (childOb) {
childOb.dep.depend()
if(Array.isArray(value)){
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
if(newVal === value || (newVal !== newVal && value !== value)) {
return
}
if(process.env.NODE_ENV !== 'Production' && customSetter) {
countomSetter()
}
val = newVal
childOb = shallow && observe(newVal)
dep.notify() /** 通知相关依赖进行更新 **/
}
)
3、vue中是如何检测数组变化?
理解:
- 使用函数劫持的方式,重写了数组的方法
- Vue将data中的数组,进行原型链重写。指向了自定义的数组原型方法,这样当调用数组api时,可以通知依赖更新。如果数组中包含着引用类型。会对数组中的引用类型再次进行监控。
原理:
- initData 初始化用户传入的data数据
- new Observer 将数据进行观测
- protoAugment(value,arryMethods) -> 对数组的原型方法进行重写
- observeArray 商都观察数组中的每一项(对象类型)
const arrayProto = Array.prototype
export const arrayMethods = Objcet.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'unshift',
'splice',
'sort',
'reverse'
]
methodsToPath.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
swtch(method){
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
ob.dep.notify() // 当调用数组方法后,手动通知视图更新
return result
})
})
this.observeArray(value) // 进行深度监控
4、为何vue采用异步渲染视图
理解:
因为如果不采用异步更新,那么每次更新数据都会对当前组件进行渲染,所以为了性能考虑。Vue会在本轮数据更新后,再去异步更新视图!
原理:
- dep.notify() 通知watcher进行更新操作
- subs[i].update() 依次调用watcher的update
- queueWatcher 将watcher去重放到队列中
- nextTick(flushSchedulerQueue) 异步清空watcher队列
notify () { // 通知存储的依赖更新
// stabilize the subscriber list first
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update() // 依赖中的update方法
}
}
}
update () {
/* istanbul ignore else */
if (this.lazy) { // 计算属性
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
queueWatcher(this); // 将watcher放入队列
}
}
function queueWatcher (watcher) {
const id = watcher.id; // 过滤watcher 多个属性依赖同一个watcher
if (has[id] == null) {
has[id] = true;
if (!flushing) {
queue.push(watcher); // 将watcher放到队列中
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
let i = queue.length - 1;
while (i > index && queue[i].id > watcher.id) {
i--;
}
queue.splice(i + 1, 0, watcher);
}
// queue the flush
if (!waiting) {
waiting = true;
if (!config.async) {
flushSchedulerQueue();
return
}
nextTick(flushSchedulerQueue); // 在下一个tick中刷新watcher队列
}
}
}