前端|Vue双向绑定实现原理(一) 数据劫持
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 将被定义或修改的属性描述符。
属性描述符
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 = https://www.it610.com/article/newVal;
}
});
![前端|Vue双向绑定实现原理(一) 数据劫持](https://img.it610.com/image/info8/8fd8d220b0e14da69d84cbfbe9e0dc3b.gif)
文章图片
如图 1-2
1.1.3 数据劫持
知道了get和set的妙用,就可以对数据进行劫持了。
劫持的概念
说白了,就是拿到某数据,持有这个数据,可以操作增删改,也可以不操作,重点在持有他
监听
一旦数据被传入Vue实例就需要对data整个对象实行监听,
【前端|Vue双向绑定实现原理(一) 数据劫持】这里需要对data中的数据类型进行判断
如果是data中的属性是基本数据类型,只需要监控就好了
如果data中的属性是对象,则需要遍历对象下的所有属性,进行监控
可又有一个疑问,data的属性是对象A,A的属性还包含对象B,B有对象C,所以不能是遍历,而是递归,递归整个对象属性树
?
姓名是{{ name.firstName }}
年龄是{{ age }}
{{ name }} ?
数据绑定(传入{ 对象的data挂载在vue实例上)
/**
*Vue入口
*@{options} 限定为一个对象,接受这个{}对象
* */
function Vue(options = {}) {
this.$options = options;
// 将所有属性挂载在vue实例$options上
var data = https://www.it610.com/article/(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的值。
往后看,谢谢。
推荐阅读
- vue-cli|vue-cli 3.x vue.config.js 配置
- 2020-04-07vue中Axios的封装和API接口的管理
- Jsr303做前端数据校验
- VueX--VUE核心插件
- 7、前端--jQuery简介、基本选择器、基本筛选器、属性选择器、表单选择器、筛选器方法、节点操作、绑定事件
- 前端代码|前端代码 返回顶部 backToTop
- vue组件中为何data必须是一个函数()
- 用npm发布一个包的教程并编写一个vue的插件发布
- vuex|vuex 基础结构
- Vue源码分析—响应式原理(二)