MVVM =>双向数据绑定 => 数据驱动 => 响应式原理 => Object.defineProperty整体理解
近期发现vue的底层知识其实都是互通的,而且是一步一步递进的,接下来进行我对此的理解
MVVM与MVC
MVC:M(model模型,后端的数据),V(view视图,用户看到的界面),C(controller,控制器,页面业务逻辑),使用C将M与V分开,MVC中M和V单向通信必须要C承上启下; 缺点:大量使用dom操作影响页面渲染效率,导致重绘重排。
MVVM:M(model模型,后端的数据),V(view视图,用户看到的界面),VM(viewmodel,同步M和V的对象),核心是对View和ViewModel的双向数据绑定,功能一个是将视图转换为视图模型,通过dom事件监听,一个是将视图模型转换为视图,通过数据绑定,数据劫持与发布者订阅者模式 MVVM的原理: (1)实现一个数据监听器Observer,能对数据对象的所有属性进行监听,如有变动能拿到最新值并通知订阅者 (2)实现一个指令解析器Compile,对每个元素节点指令进行扫描解析,根据指令模板替换数据以及绑定相应的更新函数 (3)实现一个Watcher,作为连接监听器Observer和解析器Compile的桥梁,能订阅并且收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图
从MVVM => 数据双向绑定
数据的双向绑定:Vue会对数据驱动过程的操作进行监听,是一个指令v-model,可以绑定响应式数据到视图,同时视图中变化能改变此值,结尾会通过js实现数据双向绑定
从数据双向绑定 => 数据驱动
数据驱动:传统前端交互是通过ajax从服务端获取数据操作DOM改变视图,或者前端要改变数据时需要重复一遍操作,Vue.js提供MVVM(核心是提供对View和ViewModel的双向数据绑定)具有双向数据绑定的Js库,让开发省去操作DOM过程,只需要改变数据。
从数据驱动 => 响应式原理
响应式原理: 采用defineProperty来劫持整个对象,然后进行深度遍历所有属性,给每个属性添加getter和getter,实现响应式 vue为什么需要响应式:MVVM框架核心是连接数据层和视图层,通过数据驱动应用,让数据变化使得视图更新,做到这点要求数据必须响应式 响应式的优点:通过响应式加上虚拟DOM,开发人员只需操作数据、关心业务,不用接触繁琐的DOM操作,从而提升开发效率 响应式的缺点:初始化时递归遍历会造成性能损失;新增删除属性需要$set等特殊API;es6的Map,set等数据不支持
响应式原理 => Object.defineProperty
vue2的Object.defineProperty:会直接在对象上定义一个新属性,或者修改一个对象现有属性并返回改对象 Object.defineProperty为什么能实现响应式:通过defineProperty两个属性(get, set) get:属性的getter函数,当访问该属性时会调用此函数,执行时不传入任何参数,但是会传入this对象(由于继承关系),该函数的返回值会被用作属性的值 set:属性的setter函数,当属性值被修改时会被调用,该方法接受一个参数(被赋予的新值),会传入赋值时的this对象,默认是undefined 定义一个响应式函数defineReactive
function update() {
app.innerText = obj.foo
}
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log(`get ${key}:${val}`);
return val
},
set(newVal) {
if (newVal !== val) {
val = newVal
update()
}
}
})
}
当调用definedReactive时,数据发生变化,触发setter函数从而触发upadate从而实现数据响应式,如果对象存在多个key则需要遍历,如果存在嵌套对象则需要在defineReactive进行递归 defineProperty缺点: (1) 检测不到对象属性的添加和删除以及数组的API方法,需要使用$set (2) 需要对每个属性进行遍历监听,如果是嵌套对象需要进行深层监听,造成性能问题
JS实现双向数据绑定
<input type="text" oninput="inputFn()">
<script>
const data = { msg: "msg" }
const vm = {}
Object.defineProperty(vm, "msg", {
set(val) {
data["msg"] = val
docuement.querySelect("input").value = val
},
get() {
return data["msg"]
}
})
function inputFn(e) {
vm.msg = e.target.value
}
vm.msg = data.msg
</script>
|