数据代理 Object.defineProperty
vm.xxx(data配置的数据)=vm. _ data.xxx
通过vm.xxx代理对vm. _ data.xxx的操作(读/写),这样通过vm直接获取数据的时候就调用getter,获取_ data中的值,当修改的时候调用setter修改 _ data 中的值
vue中vm(vue实例)对data内的全部属性进行了数据代理。
Vue中数据代理的好处:更加方便的操作data(模型-Model)中的数据。
既然vm上挂的属性就是_data中的数据代理,那么{{vm._data.name}}和{{name}}
是等价的,{{vm_data.name='szk2'}}和{{name='szk2'}}
也是等价的
所以就是为了写代码的方便,在{{}}直接写数据,或者直接修改就能操作到_data中
数据劫持Observer
数据劫持,指的是在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果
_data里做的数据劫持,将vue代码里我们写的data加工了一下,让每个属性有了getter和setter
让vue做到data里的数据改变后马上做到页面更新
实际上vue通过监听者observer来监听data中的数据,这个getter和setter就是监听者里面的方法
getter就是监听者获取data中数据的,setter则是监听当数据发生变化的时候执行操作的
当修改属性的时候,setter被调用,在setter方法中就会让订阅者执行重新解析模板的操作,从而改变了页面
vue响应式
数据驱动视图,我们修改数据视图随之响应更新。
Vue2.x是借助Object.defineProperty()对数据进行劫持同时结合发布-订阅者模式(==
通过Object.defineProperty()来劫持data中各个属性的setter、getter,在数据变动时,发布消息给订阅者,触发响应的监听回调。==),而Vue3.x是借助Proxy实现的。
Vue 利用 Object.defineProperty 创建一个 observer 来劫持监听所有的属性,把这些属性全部转为 getter 和 setter。
Vue 中每个组件实例都会对应一个 watcher 实例,它会在组件渲染的过程中把使用过的数据属性通过 getter 收集为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
发布-订阅者模式
发布-订阅者模式的作用:可处理一对多的场景,应用于不同情况下的不同函数调用
vue.js采用数据劫持结合发布-订阅者模式,通过Object.defineProperty()来劫持data中各个属性的setter、getter,在数据变动时,发布消息给订阅者,触发响应的监听回调
其实一种多对一的关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知
发布者observe与订阅者watcher
observe是一个发布者,执行了发布者的权利,增加了订阅者,并且在改变时通知了订阅者。
数据劫持data各个属性的setter与getter,数据变动后发布消息给订阅者,触发监听回调
详细可见Vue响应式原理探究之“发布-订阅”模式
vue2响应式的局限
Vue 不能检测数组和对象的变化
对象
Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以只有在初始化实例时就存在于data的property才是响应式的,那么就无法检测 property 的添加或移除
解决:
Vue.set(object, propertyName, value)
方法或者其别名vm.$set
向嵌套对象添加响应式 property。
(因为vue不允许动态添加根级别的响应式 property)
数组
vue不能检测以下数组的变动:
当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
当你修改数组的长度时,例如:vm.items.length = newLength
,对数组进行push、pop 等操作都不会触发更新
解决:重写数组原型上的方法同时不污染全局
定义监听数组的原型
主要分为三步:
第一步:创建一个对象,将数组的原型赋值给该对象
const oldArrayProperty = Array.prototype
第二步:创建新对象,原型指向该对象
const arrProperty = Object.create(oldArrayProperty)
第三步:重写该对象上的方法
arrProperty.push = function(){} ...
arrProperty.pop = function(){} ...
// 重新定义数组原型,加入触发更新的机制
const oldArrayProperty = Array.prototype
// 创建新对象,原型指向oldArrayProperty
const arrProperty = Object.create(oldArrayProperty)
// 重写原型上的方法(可以所有都重写,这里只进行少量举例)
// arrProperty.push = function(){}
// arrProperty.pop = function(){}
// 优化写法
const methods = ['push','pop','shift','unshift','splice']
methods.forEach(method => {
arrProperty[method] = function(){
updateView()
Array.prototype[method].call(this, ...arguments)
}
})
将需要监听的数组原型指向自定义的特殊原型
对原来的 observe 进行修改,加入数组判断,如果是数组则修改该数组的原型,至此,数组监听完成,下面是 observe 修改后代码以及测试例子
// 监听对象属性
function observe(target){
if(typeof target !== 'object' || target === null) {
// 不是数组或对象
return target
}
// 如果是数组则修改该数组的原型
if(Array.isArray(target)){
target.__proto__ = arrProperty
return
}
// 重新定义属性
for(let key in target) {
defineReactive(target, key, target[key])
}
}
// 测试数据
const data = {
myCars: ['Bugatti','Koenigsegg']
}
// 监听数据
observe(data)
// 测试
data.myCars.push('AE86') // (监听成功)输出 --> 数据更新
v-model双向绑定
<input type="text" :value="testMessage" @input="testMessage = $event.target.value">