所谓双向数据绑定就是页面与数据的关联,任何一方改动都会引起另一方的变化。其实现响应性原理是利用了js原生的defineProperties方法实现的。
1.defineProperties方法的特点
defineProperties方法的特点
可以直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。
并且在修改/新增的时候给该属性添加get/set方法
defineProperties get/set方法的特点
只要通过defineProperty给某个属性添加了get/set方法,那么以后只要获取这个属性的值就会自动调用get,设置这个属性的值就会自动调用set.
注意点
如果设置了get/set方法,那么就不能通过value直接赋值,也不能编写writable: true.
2.defineProperties的使用
新增属性
<template>
<div class="home">
<button @click="handelButton">按钮</button>
</div>
</template>
<script>
export default {
name: "Home",
methods: {
// 需求:给obj对象新增一个name对象,取值为"山竹"
handelButton() {
let obj = {};
// 第一个参数是需要操作的对象
// 第二个参数是需要操作的属性
// 第三个参数是一个对象,用来描述需要表达的值
Object.defineProperty(obj, "name", {
// 可以通过value来告诉defineProperty方法新增的属性的取值是什么
value: "山竹",
//需要告诉defineProperty取值可以修改
writable: true,
//需要告诉defineProperty取值可以删除
configurable: true,
//需要告诉defineProperty新增属性可以迭代
enumerable: true,
});
// console.log(obj); //{name:"山竹"}
// // 注意点:默认情况下通过defineProperty新增的属性的取值是不能修改的
// obj.name = "杀生丸";
// console.log(obj); //如果没有更改writable会报错,提示是只读属性
// // 注意点:默认情况下通过defineProperty新增的属性的取值是不能删除的
// delete obj.name;
// console.log(obj); //如果没有更改configurable会报错,提示是只读属性
// 注意点:默认情况下通过defineProperty新增的属性是不能遍历的
for (let key in obj) {
console.log(key, obj[key]);//name 山竹
}
},
},
};
</script>
修改属性
<template>
<div class="home">
<button @click="handelButton">按钮</button>
</div>
</template>
<script>
export default {
name: "Home",
methods: {
handelButton() {
let obj = {name:'杀生丸'};
Object.defineProperty(obj, "name", {
value: "山竹",
writable: true,//设置允许修改
});
console.log(obj);//山竹
},
},
};
</script>
get/set方法
<template>
<div class="home">
<button @click="handelButton">按钮</button>
</div>
</template>
<script>
export default {
name: "Home",
methods: {
handelButton() {
let obj = {};
let oldValue = "杀生丸";
Object.defineProperty(obj, "name", {
get() {
console.log("get被触发啦");
return oldValue;
},
set(newValue) {
console.log("set被触发啦");
// 判断新值老值不一致,则重新赋值
if(oldValue!==newValue){
oldValue=newValue
}
},
});
console.log(obj.name); //杀生丸
obj.name='山竹'
console.log(obj.name);//山竹
},
},
};
</script>
快速监听对象中所有属性的变化
<template>
<div class="home">
<button @click="handelButton">按钮</button>
</div>
</template>
<script>
export default {
name: "Home",
methods: {
handelButton() {
let obj = {
name:{a:'山竹'},
age: 18,
};
class Observer {
// 只要将需要监听的那个对象传递给Observer这个类
// 这个类就可以快速的给传入的对象的所有属性都添加get/set方法
// data代表接收的对象
constructor(data) {
this.observer(data);
}
// 给属性添加get/set;
observer(obj) {
// 判断是不是对象
if (obj && typeof obj === "object") {
//遍历取出传入对象的所有属性,给遍历到的属性都增加get/set方法
for (let key in obj) {
// 参数为:对象,属性,值
this.defineRecative(obj, key, obj[key]);
}
}
}
// obj:需要操作的对象
// attr:需要新增get/set方法的属性
// value:需要新增get/set方法属性的取值
defineRecative(obj, attr, value) {
//如果对象属性里面嵌套对象,则开始递归添加get/set
this.observer(value)
Object.defineProperty(obj, attr, {
// get方法直接返回值
get() {
return value;
},
// set方法接收新值并返回
set(newValue) {
console.log('监听到值变化并更新到UI ');
newValue===value?value:value=newValue
},
});
}
}
new Observer(obj)
obj.name.a='杀生丸'
console.log(obj.name.a);//age:18,name:"杀生丸"
},
},
};
</script>
升级版
普通属性转变成复杂属性修改新属性问题
<template>
<div class="home">
<button @click="handelButton">按钮</button>
</div>
</template>
<script>
export default {
name: "Home",
methods: {
handelButton() {
let obj = {
name: "山竹",
// name:{a:'山竹'},
age: 18,
};
class Observer {
// 只要将需要监听的那个对象传递给Observer这个类
// 这个类就可以快速的给传入的对象的所有属性都添加get/set方法
// data代表接收的对象
constructor(data) {
this.observer(data);
}
// 给属性添加get/set;
observer(obj) {
// 判断是不是对象
if (obj && typeof obj === "object") {
//遍历取出传入对象的所有属性,给遍历到的属性都增加get/set方法
for (let key in obj) {
// 参数为:对象,属性,值
this.defineRecative(obj, key, obj[key]);
}
}
}
// obj:需要操作的对象
// attr:需要新增get/set方法的属性
// value:需要新增get/set方法属性的取值
defineRecative(obj, attr, value) {
//如果对象属性里面嵌套对象,则开始递归添加get/set
this.observer(value);
Object.defineProperty(obj, attr, {
// get方法直接返回值
get() {
return value;
},
// set方法接收新值并返回
set: (newValue) => {
if (value !== newValue) {
// 如果新值里面也有对象,也需要添加get/set
this.observer(newValue);
value = newValue;
console.log("监听到数据的变化,需要去更新UI");
}
},
});
}
}
new Observer(obj);
obj.name = { a: "山竹" };
console.log(obj); //山竹
obj.name.a = "杀生丸";
console.log(obj); //杀生丸
obj.name = "终结";
console.log(obj.name);//终结
},
},
};
</script>