1.1 数据劫持
1.1.1 如何监控一个数据
vue可以直接通过v-model这个指令来实现双向绑定,这是react和小程序都没有,小程序是单向绑定,只能将data中的对象和基本数据类型展示在视图上,却没有办法通过视图来控制data中的数据,需要通过this.setData({})给出一个对象,重新设置数据,达到视图更新。
要达到如图1-1的效果,就要对数据进行监控,只有监控了数据的变化,在数据变化之后,通知视图去自主更新,这就是双向绑定的思路。这个思路很明显涉及到“监控” “更新”两个关键词,就可以联想到观察者模式。
观察一个数据,一旦数据变化,就通知视图执行更新操作。
思路一下子就明了,数据变化还好说,就是拿出一个变量存储旧值,一旦获取到新值,新值与旧值不同时,数据就发生了变化。可问题在于,不可能随时随地对数据进行监控,每分每秒都在取得数据的值去与旧值做对比。
只有当这个数据在被使用时,我们才监控他,拿旧值与新值做对比。
这个过程叫做让数据变为可观察,是通过Object.defineProperty() 来实现。
1.1.2 如何使用Object的静态方法定义属性
Object.defineProperty(obj, prop, descriptor)
-
obj 要在其上定义属性的对象。
-
prop 要定义或修改的属性的名称。
-
descriptor 将被定义或修改的属性描述符。
被这样定义的属性,所有的数据描述符默认为false,也就是不可删除,不可写,不可枚举
属性描述符
MDN文档上有提到
对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值可能是可写的,也可能不是可写的。存取描述符是由getter-setter函数对描述的属性。描述符必须是这两种形式之一;不能同时是两者。
let obj = {
name:1
}
Object.defineProperty(obj,'school',{
configurable:true,//表示configurable可以被删除
writable:true,//为true之后,便可以修改
// enumerable:true,//修改之后才可以被枚举,在遍历时被访问到
value:'zfpx'
})
// delete obj.school;
obj.school = "修改值"
console.log(obj)
for(var key in obj){
console.log(key)
}
只有开启数据描述符为true之后,属性才可被删除,被写入,被枚举打印
getter-setter
这就是数据可监控的关键,使用Object.defineProperty(obj, prop, descriptor)定义的属性,一旦属性被使用,就会被读取,就会调用get函数,一旦属性被写入,就会调用set函数,即可以知道数据一旦发生写入,变化,就可以在set函数中通知视图更新。
由于,
存储描述符get set参数和数据描述符的writeable value存在冲突,二选其一
Object.defineProperty(obj, "school", {
configurable: true, //表示configurable可以被删除
// writable: true,
enumerable: true, //修改之后才可以被枚举,在遍历时被访问到
// value: "zfpx",
get() {
console.log("调用了get方法");
return value;
},
set(newVal) {
console.log("调用了set方法");
value = newVal;
}
});
如图 1-2
1.1.3 数据劫持
知道了get和set的妙用,就可以对数据进行劫持了。
劫持的概念
说白了,就是拿到某数据,持有这个数据,可以操作增删改,也可以不操作,重点在持有他
监听
一旦数据被传入Vue实例就需要对data整个对象实行监听,
这里需要对data中的数据类型进行判断
如果是data中的属性是基本数据类型,只需要监控就好了
如果data中的属性是对象,则需要遍历对象下的所有属性,进行监控
可又有一个疑问,data的属性是对象A,A的属性还包含对象B,B有对象C,所以不能是遍历,而是递归,递归整个对象属性树
<body>
<div id="app">
<p>姓名是{{ name.firstName }}</p>
<div>年龄是{{ age }}</div>
{{ name }}
</div>
<script type="vue.js"></script>
<script type="text/javascript">
let vm = new Vue({
el: "#app",
data: {
name: {
firstName: "姓氏章",
lastName: "名字"
},
age: 12 //通过Obj.defineProperty实现()或者Obj.defineProperties()实现
}
});
</script>
</body>
数据绑定(传入{ 对象的data挂载在vue实例上)
/**
*Vue入口
*@{options} 限定为一个对象,接受这个{}对象
* */
function Vue(options = {}) {
this.$options = options; // 将所有属性挂载在vue实例$options上
var data = (this._data = this.$options.data); //将{}对象的data挂载vue实例上
observe(data);
}
/**
*观察对象变化,如果最开始传入的data是基本数据类型,已经被劫持了,不需要递归再去对属性进行监控
*@{data} 被观察的对象或属性
*/
function observe(data) {
if (typeof data !== "object") return null;
return new Observe(data);
}
class Observe {
constructor(data) {
this.start(data);
}
start(data) {
for (let key in data) {
let val = data[key];
// 如果data中包含属性是对象,则需要递归对象的中属性,进行数据劫持
// 如果data中的属性就是普通数据类型,递归退出 -- 递归出口
Object.defineProperty(data, key, {
enumrable: true,
get() {
console.log("调用get方法");
return val;
},
// 会在数据改变的时候直接设置
set(newVal) {
console.log("调用set方法");
//数据并没有改变
if (newVal === val) {
return;
}
val = newVal;
}
});
}
}
}
上述代码实现了数据劫持和监控数据的功能
接下来是
数据代理(this代理传入的{ }对象去调用data)
编译模板(读取文本节点中的字符串,抽成属性名,通过字面量的形式访问到属性值,去掉双大括号,显示到节点上)
到编译模板这一步,是实现了单向绑定,也就是data中的数据被显示在网页上,如同又通过视图,譬如input输入框,改变data的值。
往后看,谢谢。