[1]什么叫双向数据绑定?
- 视图中的数据发生了变化,data中的数据也要对应改变;
- data中的数据发生了变化,视图上的数据也要对应改变;
[2]双向绑定原理
vue 双向数据绑定是通过 数据劫持 结合 发布订阅模式的方式来实现的, 也就是说数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变;
vue2.x实现双向绑定
在vue2.x中数据双向绑定的核心是使用 Object.defineProperty(对象,key,{get(),set()})
方法来给对象的属性添加get
、set
方法实现的!
Object.defineProperty对象(es6新增)
语法
Object.defineProperty(obj,key,{
enumerable:false , // 控制属性是否可以枚举,默认值为false
writable:false, // 控制属性是否可以被修改,默认值为false
configurable:false, // 控制属性是否可以被删除,默认值为false
// 当获取该属性时就会调用get方法 ,返回值为获取到的值
get(){
return xxx
}
// 当给该属性进行赋值时就会调用set方法
set(val){
}
})
举例说明
需求:现在有如下代码,希望person对象存在一个age属性,属性值为number变量的值且 number值与age属性值能够实时双向绑定
let number = 18
let person = {
name:'chaochao',
sex:'女'
}
实现:
let number = 20
let person = {
name:'chaochao',
sex:'女'
}
Object.defineProperty(person,'age',{
get(){
console.log('@@@获取age')
return number
},
set(val){
console.log('@@@修改age')
number = val
}
})
结果:
踩坑 - 给某属性进行双向绑定
在此示例中给data对象的name属性通过defineProperty进行 读取/赋值;
let data = {
name: 'chaochao'
}
Object.defineProperty(data, 'name', {
get () {
// [2] 若是在返回时直接通过点语法获取会再次调用get方法 -> 递归却没有递归边界---死循环
return data.name
},
set (value) {
data.name = value
}
})
// [1]在此处获取了data的name属性值,就会调用name属性的get方法
data.name
改正:
let data = {
name: 'chaochao'
}
let _name = data.name
Object.defineProperty(data, 'name', {
get () {
return _name
},
set (value) {
data.name = value
}
})
// [1]在此处获取了data的name属性值,就会调用name属性的get方法
data.name
踩坑 - 给对象的所有属性进行双向绑定
let data = {
name: 'chaochao',
sex:'女',
say:''
}
let _name = data.name
for(let key in data){
Object.defineProperty(data, key, {
get () {
return data[key] // 无限调用
},
set (str) {
data[key] = str // error: Maximum call stack size exceeded
}
})
}
改正:
let data = {
name:'chaochao',
sex:'女',
say:''
}
let _name = data.name
Object.keys(data).forEach((key) => {
defineProperty(data, key, data[key])
})
function defineProperty(obj,key,val) {
Object.defineProperty(obj, key, {
get () {
console.log('@@@get', val)
return val
},
set (str) {
console.log('@@@set', val,str)
val = str
}
})
}
vue2.x实现双向绑定
在通过new关键字实例化对象时,传入的配置项中添加了 data属性
const vm = new Vue({
el:'#app',
data:{
name:'chaochao',
sex:'女',
say:'hello word'
}
})
vue会将data存在在实例化对象的_data
属性中;
const data = {
name:'chaochao',
sex:'女',
say:'hello word'
}
const vm = new Vue({
el:'#app',
data
})
console.log(data==vm._data) // true
将data中的数据平铺在vue实例化对象身上
使用Object.defineProperty实现数据代理。
模拟数据代理过程
// 模拟配置项data
const data = {
name:'chaochao',
sex:'女',
say:'hello word'
}
// 模拟vue实例化对象 代码中更新的是_data中的数据,视图上更改的是vue实例化对象身上的数据
const vm ={
_data:data, // 存储数据
...data // 数据劫持-> 更新试图
}
for(let key in vm._data){
Object.defineProperty(vm._data, key , {
// 当获取属性值时返回vue实例化对象身上的对应的属性的属性值
get(){
return vm[key]
},
// 当修改属性值时-> 将vue实例化对象身上对应的属性值一起修改
set(val){
vm[key] = val
}
})
}
vue2.x实现双向绑定缺点
- 对象:只能给对象中已经存在的属性进行双向绑定!
- 新增属性、删除属性,视图不会更新
- 数组:直接通过下标修改数组,视图不会更新
所以在vue2.x中有时会出现双向绑定失败(实例化对象中的数据改变了,但是视图上并没有重新渲染)的现象-可以使用 $set
解决
vue3.x实现双向绑定
在vue2.x中数据双向绑定的核心是使用new Proxy(对象,{get(),set()})
方法来给对象的属性添加get
、set
方法实现的!
Proxy构造函数(es6新增)
语法
let data = {
name:'chaochao',
sex:'女',
say:''
}
//data为源数据 p为代理数据
const p = new Proxy(data,{
// 拦截读取属性值 -> 当读取p身上的属性时就会触发该方法,该函数return的值就是读取到的值
// target:源数据; prop:key值
get (target, prop) {
console.log('@@@读取数据了')
return target[prop] // 默认返回的是源数据的值
},
// 拦截设置属性值或添加新属性 -> 当修改p身上的属性的属性值或给p添加新属性时就会触发该方法
set (target, prop, value) {
console.log('@@@修改数据')
target[prop] = value // 当修改了代理数据就去修改源数据的值
},
// 拦截删除属性 -> 当删除p身上的属性时就会触发该方法
deleteProperty (target, prop) {
console.log('@@@删除数据 ')
return delete[prop] // 当删除了代理数据的属性就删除源数据的属性并返回是否删除成功
}
})
Reflect对象
我们可以通过Reflect对象去增删改查对象中的属性
语法
let data = {
name:'chaochao',
sex:'女',
say:''
}
// 新增属性
console.log(Reflect.set(data,'age', 18)) // true
// 获取属性
console.log(Reflect.get(data,'name'), Reflect.get(data,'age')) // chaochao, 18
// 修改属性
console.log(Reflect.set(data,'name','niuniu')) // true
// 删除属性
console.log(Reflect.deleteProperty(data,'sex')) // true
// 查看对象
console.log(data) // {name: 'niuniu', say: '', age: 18}
优点是容错性强!
vue3.x双向绑定
new Proxy(data,{
get (target, prop) {
return Reflect.get(target,prop)
},
set (target, prop, value) {
Reflect.set(target,prop,value)
},
deleteProperty (target, prop) {
return Reflect.deleteProperty(target, prop)
}
})
- 通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
- 通过Reflect(反射): 对源对象的属性进行操作。