MVVM model view
mvvm与mvc最大的区别:
MVVM实现了view与model的自动同步,也就是model属性改变的时候, 我们不需要再自己手动操作dom元素去改变view的显示,而是改变属性后该属性对应的view层会自动改变。
1.响应式原理
发布订阅模式+数据劫持
简易版:
const Dep = {
list: {}, //容器
//添加订阅
listen: function(key,fn) {
if(!this.list[key]) {
this.list[key] = []
}
this.list[key].push(fn);
},
//发布
trigger: function() {
let key = Array.prototype.shift.call(arguments),
fns = this.list[key];
if(!fns || fns.length === 0) {
return false;
}
fns.forEach(fn=>{
fn.apply(this, arguments);
})
}
}
//数据劫持
let initData = function({data, tag, dataKey, selector}) {
let value = '',el = document.querySelector(selector);
Object.defineProperty(data,dataKey,{
get: function() {
return value;
},
set: function(tag,val) {
this.value = val;
Dep.trigger(tag,val);
}
})
Dep.listen(tag,(text)=>{
text.innerHTML = text;
})
}
let obj = {};
initData({
data: obj,
tag: 'view-1',
dataKey: 'one',
selector: '.box'
})
obj.one = '999';
Object.defineProperty的缺点:
- 深度监听需要递归到底,一次性计算量大
- 无法监听新增属性、删除属性(需要用Vue.set Vue.delete)
- 无法原生监听数组,需要特殊处理
Proxy的优点:
1.可以直接监听对象,不用递归遍历,提高了性能 2.可以监听数组
如何监听数组 vue重新定义了数组原型的方法
const arrPro = Object.create(Array.prototype);
['push','pop','shift','unshift','splice'].forEach(methname => {
arrPro[methname] = function() {
Array.prototype[methname].call(this, ...arguments)
};
})
vue监听数组的变化
vue无法监听数组变化的场景:
1.通过数组索引改变数组元素的值 比如:this.list[2].checked = true;数据改变了视图没有更新
2.改变数组的长度
解决无法监听的方法:
1.this.$set(arr, index, newval);
2.通过splice(index, num, val);
3.使用临时变量作为中转,重新赋值数组
能监听数组变化的场景:
1.通过赋值的形式改变正在监听的数组;
2.通过splice/push
vue监听对象的变化:
vue无法监听到对象的场景:
1.增加/删除/修改无法被vue监听
解决方法:
1.this.$set()
2.Object.assign(),直接赋值的原理(推荐)
源码写法:
const Observe = function(data) {
for(let key in data) {
defineReactive(date, key);
}
}
const defineReactive = function(obj, key) {
const dep = new Dep();
let val = obj[key];
if(typeof val !== 'object') return fasle;
Observe(val);
Object.defineProperty(obj,key,{
get() {
dep.depend();
return val;
},
set(newval) {
dep.notify();
val = newval;
}
})
}
const Watcher = function(vm, fn) {
this.vm = vm;
Dep.target = this;
this.update = function(){
fn();
}
this.value = fn;
Dep.target = null;
}
const Dep = function() {
this.target = null;
this.subs = [];
this.depend = function() {
if(Dep.target) {
this.addSub(Dep.target);
}
}
this.addSub = function(watcher){
this.subs.push(watcher);
}
this.notify = function() {
for(let i=0;i<this.subs.length;i++) {
this.subs[i].update();
}
}
}
2.diff算法
diff是一种算法,当数据发生变化是,虚拟dom就会变化,为了高效的比对虚拟dom,vue引入了diff算法,通过diff比对,将变化的地方更新在真实dom中。尽可能复用节点,减少dom的新增创建操作,提高性能。
diff的对比遵循深度优先,同层比较。首先从顶层比较,如果两者都是元素,那就判断两者是否有孩子,有则向下递归,比较过程中首先假设首尾节点可能相同,做四次对比尝试,如果没有找到相同节点才按照通用方式遍历查找,查找结束再按情况处理剩下的节点,这个过程中借用key可以更精确的找到相同节点,尽可能的复用节点减少dom操作从而使性能更高效
diff算法同层级比较,前前后后,前后 后前比较,为了尽可能复用减少dom操作
vue3.0 静态模板:vue2.0 diff算法后,相同节点复用之后会更新属性,即使是节点的属性是静态不变的也会更新;vue3.0模板分为静态模板和动态模板,:id='id'属于动态的,对于静态的不做更新,对于动态的才做更新。
3.vue-router原理
vue的路有有两种模式mode:hash和history,hash是带有#的,默认hash模式;
hash模式:通过window.location.hash获取到当前url的hash;hash模式下通过hashchange方法可以监听url中hash的变化。
window.addEventListener("hashchange", function(){}, false)
特点:兼容性好,hash变化的时候会在浏览器history中添加一条记录,可实现浏览器的前进后退功能
history模式
它基于HTML5的history对象,通过location.pathname获取到当前url的路由地址;history模式下,通过pushState和replaceState方法可以修改url地址,结合popstate方法监听url中路由的变化
history模式的特点是实现更加方便,可读性更强,同时因为没有了#,url也更加美观;
它的劣势也比较明显,当用户刷新或直接输入地址时会向服务器发送一个请求,所以history模式需要服务端同学进行支持,将路由都重定向到根路由
3.nextTick
一般是在数据改变了,视图没有按预期渲染,就会考虑是不是dom没有渲染完,就会考虑用nextTick,它的作用就是等dom渲染完再执行下一步操作。 nextTick主要是为了延迟回调,在下次dom更新结束之后执行延迟回调。在修改数据之后立即使用此方法,获得更新后的dom
兼容性问题处理:
if 浏览器支持Promise 就用Promise.then
else if 支持MutationObseve 就用支持MutationObseve
else if 支持setImmedate就用支持setImmedate就用
else 用setTimeout
4.keepalive原理
一个抽象组件,自身不会渲染一个DOM元素也不会出现在父组件链中,可以保存组件渲染状态。 原理:主要是用了缓存策略,将组件实例缓存到了this.cache对象里