什么是数据响应式
即数据双向绑定,改变Model时,View自动更新。改变View时,Model也会自动更新。
数据响应式原理
- 在Vue2.x版本中,利用Object.defineProperty()重新定义对象获取属性值(get)和设置属性值的操作实现数据响应。
- 在Vue3.0版本中,采用ES6的Proxy对象来实现。
数据响应简单实现
Object.defineProperty()实现数据响应
- 根据上图实现整体的架构:MVVM类(即Vue类)、Watcher类():订阅—发布者设计模式。
- 实现M到V,即将模型数据绑定到视图。
<script>
//发布者
class Vue {
constructor(options) {
this.options = options;
this.$el = document.querySelector(options.el);
this.$data = options.data;
this._directive = {}; //充当一个容器,存放订阅者
// 因为性能的原因,重拍重绘一般采用局部渲染,所以订阅容器格式为:
// {myText:[订阅者1,订阅者2],myBox:[订阅者3,订阅者4]}
// 如果需要采用广播,可以使用{订阅者1,订阅者2,订阅者3,订阅者4}这种格式。
this.Observer(this.$data);
this.Compile(this.$el);
}
//劫持数据
Observer(data) { //将实例中的data对象的属性添加到容器中,本例是 data:{myText: "大吉大利",myBox: "落地成盒"}
for (let key in data) {
this._directive[key] = [];//初始化容器
//添加之后_directive={myText:[],myBox:[]}
}
}
//解析指令
// 找出所有的指令,进行依赖收集(找指令),方便去订阅data里面的属性,进行数据更新
Compile(el) { //参数el是div#app节点
let nodes = el.children; //获取所有子节点(dom)
for (let i = 0; i < nodes.length; i++) {
let node = nodes[i];
if (node.children.length) {
this.Compile(node);
}
if (node.hasAttribute(`v-text`)) {
//node:当前元素对象
//attrVal:当前元素对象的指令的值
let attrVal = node.getAttribute(`v-text`);//获取到值
this._directive[attrVal].push(new Watcher(node, this, attrVal, "innerHTML"));//push的是订阅者
}
if (node.hasAttribute(`v-model`)) {
let attrVal = node.getAttribute(`v-model`);//获取到值
this._directive[attrVal].push(new Watcher(node, this, attrVal, "value"));
}
console.log( this._directive);
}
}
}
//订阅者
class Watcher { //更新视图
constructor(el, vm, exp, attr) {
this.el = el; //元素对象 div input
this.vm = vm; //实例
this.exp = exp; //属性值
this.attr = attr; //节点内容
this.update();
}
update() { //更新视图中的内容
this.el[this.attr] = this.vm.$data[this.exp];
//div[innerText] = data[myText]
}
}
</script>
<div id="app">
<h1>数据响应式绑定:</h1>
<div>
<div v-text='myText'></div>
<div v-text='myBox'></div>
<input type="text" v-model='myText'>
<input type="text" v-model='myBox'>
</div>
</div>
<script>
const app = new Vue({
el: "#app",
data: {
myText: "大吉大利",
myBox: "落地成盒"
}
});
</script>
- 实现V—M,即将输入框的文本触发更新模型中的数据,同时更新对应视图。
(同时也是最终代码)
<script>
//发布者
class Vue {
constructor(options) {
this.options = options;
this.$el = document.querySelector(options.el);
this.$data = options.data;
this._directive = {}; //充当一个容器,存放订阅者
// 因为性能的原因,重拍重绘一般采用局部渲染,所以订阅容器格式为:
// {myText:[订阅者1,订阅者2],myBox:[订阅者3,订阅者4]}
// 如果需要采用广播,可以使用{订阅者1,订阅者2,订阅者3,订阅者4}这种格式。
this.Observer(this.$data);
this.Compile(this.$el);
}
//劫持数据
Observer(data) { //将实例中的data对象的属性添加到容器中,本例是 data:{myText: "大吉大利",myBox: "落地成盒"}
for (let key in data) {
this._directive[key] = [];//初始化容器
//添加之后_directive={myText:[],myBox:[]}
//第三部内容:进行数据劫持,并修改data属性的get和set
//vue实例对象没有update函数,但有Watcher实例对象,拥有update函数
let val = data[key];//属性值
let watch = this._directive[key];//获取到容器内某一data对象属性的所有订阅者数组。
Object.defineProperty(this.$data,key,{//针对实例的data对象,修改(或定义)其属性名的get和set方法。
//即Object.defineProperty只在视图引起数据改变时使用
get:function(){
return val;
},
set:function(newVal){
if(newVal !== val){
val = newVal;
watch.forEach(element => {
element.update();//订阅者实例调用update函数
});
}
}
});
}
}
//解析指令
// 找出所有的指令,进行依赖收集(找指令),方便去订阅data里面的属性,进行数据更新
Compile(el) { //参数el是div#app节点
let nodes = el.children; //获取所有子节点(dom)
for (let i = 0; i < nodes.length; i++) {
let node = nodes[i];
if (node.children.length) {
this.Compile(node);
}
if (node.hasAttribute(`v-text`)) {
//node:当前元素对象
//attrVal:当前元素对象的指令的值
let attrVal = node.getAttribute(`v-text`);//获取到值
this._directive[attrVal].push(new Watcher(node, this, attrVal, "innerHTML"));//push的是订阅者
}
if (node.hasAttribute(`v-model`)) {
let attrVal = node.getAttribute(`v-model`);//获取到值
this._directive[attrVal].push(new Watcher(node, this, attrVal, "value"));
//第三步内容:给元素绑定事件
node.addEventListener('input',()=>{
this.$data[attrVal] = node.value;
});
}
console.log( this._directive);
}
}
}
//订阅者
class Watcher { //更新视图
constructor(el, vm, exp, attr) {
this.el = el; //元素对象 div input
this.vm = vm; //实例
this.exp = exp; //属性值
this.attr = attr; //节点内容
this.update();
}
update() { //更新视图中的内容
this.el[this.attr] = this.vm.$data[this.exp];
//div[innerText] = data[myText]
}
}
</script>
<div id="app">
<h1>数据响应式绑定:</h1>
<div>
<div v-text='myText'></div>
<div v-text='myBox'></div>
<input type="text" v-model='myText'>
<input type="text" v-model='myBox'>
</div>
</div>
<script>
const app = new Vue({
el: "#app",
data: {
myText: "大吉大利",
myBox: "落地成盒"
}
});
</script>
Proxy实现数据响应
通过Proxy创建了实例myText。
通过拦截myText中的text属性set方法,来更新视图变化。
实现一个极为简单的双向数据绑定。
<div id="app">
<h3 id="paragraph"></h3>
<input type="text" id="input">
</div>
<script>
// 获取节点元素
const paragraph = document.getElementById("paragraph");
const input = document.getElementById("input");
// 需要代理的数据对象
const data = {
text:"hello"
}
const hander = {
// 监控data中的text属性变化
set:function(target, prop, value){//目标对象(data)、属性名(text)、属性值(hello)和 Proxy 实例本身(可选)。
if(prop === "text"){
// 更新值
target[prop] = value;
// 更新视图
paragraph.innerHTML = value;
input.value = value;
return true;
}else{
return false;
}
}
}
// 构造proxy对象
const myText = new Proxy(data,hander);//对data对象进行代理,重定义了属性的设置(set)
// 添加input监听事件
input.addEventListener('input',(e)=>{
myText.text = e.target.value;
},false);
// 初始化值
myText.text = data.text;
</script>
版权声明:本文为qq_39403733原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。