MVVM
View 视图 Model 数据模型 ViewModel 桥梁、实例
在MVVM中,最核心的也就是数据双向绑定,例如 Angluar 的脏数据检测,Vue2中的数据劫持,Vue3中的数据代理。
MVC
View 视图 Model 数据模型 Controller 控制器
React
脏数据检测
当触发了指定事件后会进入脏数据检测,这时会调用 $digest 循环遍历所有的数据观察者,判断当前值是否和先前的值有区别,如果检测到变化的话,会调用 $watch 函数,然后再次调用 $digest 循环直到发现没有变化。循环至少为二次 ,至多为十次。
脏数据检测虽然存在低效的问题,但是不关心数据是通过什么方式改变的,这在 Vue 中的双向绑定是存在问题的。并且脏数据检测可以实现批量检测出更新的值,再去统一更新 UI,减少了操作 DOM 的次数。所以低效也是相对的。
数据劫持
# Vue2.0内部使用Object.defineProperty()来实现双向绑定,监听数据的get和set事件,搭配发布订阅模式。
var data = { name: 'yck' }
observe(data)
let name = data.name
data.name = 'yyy'
function observe(obj) {
if (!obj || typeof obj !== 'object') {
return
}
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key])
})
}
function defineReactive(obj, key, val) {
observe(val)
let dp = new Dep()
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
console.log('get value')
if (Dep.target) {
dp.addSub(Dep.target)
}
return val
},
set: function reactiveSetter(newVal) {
console.log('change value')
val = newVal
dp.notify()
}
})
}
class Dep {
constructor() {
this.subs = []
}
addSub(sub) {
this.subs.push(sub)
}
notify() {
this.subs.forEach(sub => {
sub.update()
})
}
}
Dep.target = null
function update(value) {
document.querySelector('div').innerText = value
}
class Watcher {
constructor(obj, key, cb) {
Dep.target = this
this.cb = cb
this.obj = obj
this.key = key
this.value = obj[key]
Dep.target = null
}
update() {
this.value = this.obj[this.key]
this.cb(this.value)
}
}
var data = { name: 'yck' }
observe(data)
new Watcher(data, 'name', update)
data.name = 'yyy'
Proxy 与 Object.defineProperty 对比
Object.defineProperty缺陷:
只能对属性进行数据劫持,需要深度遍历整个对象
对数组不可用(相当于重写数组的几个api然后做了监听处理)
路由原理
本质就是监听URL的变化然后匹配路由规则,显示相应的页面,无需刷新。
目前单页面使用的路由两种实现方式:
hash模式
http:
history模式
切换
history.go(-2);
history.go(2);
history.back();
hsitory.forward();
修改
window.history.pushState({color:'red'}, 'red', 'red')
window.history.replaceState(state, title, url)
window.onpopstate = function(event){
console.log(event.state)
}
缺点
害怕刷新,因为hash模式是修改#中的信息,浏览器请求不带,但是history可以自由修改path,如果服务器没有相应的资源会404。
Virtual Dom
因为操作DOM很费性能,可以通过JS对象模拟DOM,判断旧的对象和新的对象之间的差异,渲染差异。
判断差异的算法分为了两步:
首先从上至下,从左往右遍历对象,也就是树的深度遍历,这一步中会给每个节点添加索引,便于最后渲染差异
一旦节点有子元素,就去判断子元素是否有不同
两个节点对比会出现的几种情况:
新的节点的tagName或者key和旧的不同,这种情况代表需要替换旧的节点,并且也不再需要遍历新旧节点的子元素了,因为整个旧节点都被删掉了
新的节点的 tagName 和 key(可能都没有)和旧的相同,开始遍历子树
没有新的节点,那么什么都不用做
判断属性的更改也分三个步骤:
遍历旧的属性列表,查看每个属性是否还存在于新的属性列表中
遍历新的属性列表,判断两个列表中都存在的属性的值是否有变化
在第二步中同时查看是否有属性不存在旧的属性列列表中
判断列表差异也分三个步骤(该算法只对有key的节点做处理):
遍历旧的节点列表,查看每个节点是否还存在于新的节点列表中
遍历新的节点列表,判断是否有新的节点
在第二步中同时判断节点是否有移动
渲染差异
深度遍历树,将需要做变更操作的取出来
局部更新DOM