响应式原理
数据绑定(效果)
-
效果
一旦更新了data中的某个属性数据,所有界面上直接使用或间接使用了此属性的节点都会更新。 -
数据劫持(实现的方式)
- 数据劫持是vue中用来实现数据绑定的一种技术。
- 基本思想: 通过defineProperty()来监视data中所有的属性(任意层次)数据的变化,一旦变化就去更新界面。
注意 - 数据代理是针对vm, 数据绑定针对data。
实现响应式主要核心对象
- Dep对象 和 Watcher对象
其实从因为单词意思中是依赖,观察;
vue中依赖的意思:数据和模板表达式依赖关系,一 对 多。
vue中观察的意思:模板表达式和数据的关系, 一 对 多。
由于vue要是实现数据和视图双向绑定,必须要监听数据改变,也要监听视图变化,从而定义两个核心对象,Dep对象收集依赖,Wacher对象监听视图 模板表达式。
整体流程图.png
下面详细介绍两个对象咋样关联的,它们之间到底存在什么联系
Dep | watcher | |
---|---|---|
初始化什么时候参数? | 在observe.js中监听data属性中的某一个属性,会自动生成一个Dep对象 | 在complie.js中模板编译初始化中bind方法,自动产生一个Watcher对象 |
初始对象核心代码位置? | dep.png | watcher.png |
初始化对象对象中属性作用? | dep-key.png | watcher-key.png |
什么时候开始依赖收集? | 当模板编译时通过表达式读取数据时候,进入observe的set方法 | |
收集依赖核心方法流程? | 读取数据=> defineObserve的get方法 => Dep.depend => Watcher.addDep | watcher-addDep.jpg |
|当数据变化了咋样通知视图更新?|设置数据 => defineObserve的set方法 => Dep.notify => 遍历watcher.update => 调用传过来的更新节点cb => 从而更新视图最新状态数据 |
注意
- dep 主要针对 data里面数据的管理; watcher 主要针对 模板中的表达式的管理
-
dep 与 watcher 的关系是双向的,也就是代表 数据 和视图双向的。通过一个dep可以观看到多个watcher,通过一个watcher可以观看到多个dep; 一层套一层。
dep-data.png
手写简易 响应式原理
- MVVM入口js
(function() {
function Vue(options) {
// 配置对象保存到vm
this.$options = options || {};
var data = this._data = this.$options.data;
var me = this;
//数据代理 vm.xxx => vm._data.xxx 不需要递归
this.convertData(data);
// 响应式核心类 发布订阅模式--数据劫持--依赖收集
observe(data);
//模板编译 核心类
this.$compile = new Compile(options.el || document.body, this);
}
Vue.prototype = {
constructor: Vue,
convertData: function(data) {
var me = this;
Object.keys(data).forEach(key => {
me._proxyData(key);
})
},
_proxyData: function(key) {
var me = this;
Object.defineProperty(me, key, {
configurable: false,
enumerable: true,
// writable: true,
get: function proxyGetter() {
return me._data[key];
},
set: function proxySetter(newVal) {
me._data[key] = newVal;
}
})
}
}
return window.Vue = Vue;
})()
- observe.js
function Observe(data) {
this.data = data;
// 初始化
this.init(data);
}
Observe.prototype = {
constructor: Observe,
init: function(data) {
let me = this;
//将所有属性进行劫持
Object.keys(data).forEach(key => {
me.defineReactive(me.data,key,data[key]);
})
},
// 暂时不考虑数组
defineReactive: function(data, key, val) {
// console.log(data, key, val)
// 一个vm实例对象 => 一个observe对象
//一个vm实例对象中的data对象每一个属性 => dep对象
var dep = new Dep();
//间接递归: 目的 把 所有的属性都生成一个 dep对象和添加set/get方法
var childObj = observe(val);
Object.defineProperty(data, key, {
enumerable:true,
configurable:false,
get: function() {
//收集依赖作用就是 与 watcher建立关系的
//作用就是在watcher对象没生成之前不用建立关系
//咋样确定watcher对象是否建立的呢?
// 就是通过在Dep对象属性target判断是否存在watcher并且指向当前watcher
if(Dep.target) {
dep.depend();
// console.log("observe的get方法watcher产生 ",Dep.target)
}
// console.log("observe的get方法watcher没有产生 ",Dep.target)
return val;
},
set: function(newVal) {
if(newVal == val) return;
val = newVal;
//如果新值是对象 进去监听
childObj = observe(newVal);
//针对 数据改变了 要通知有关系的wacher对象,然后让它 去 更新视图
//通知多个 订阅者,因为dep对象 有一个属性subs数组,一 对 多
dep.notify();
}
})
}
}
var uid = 0;
function Dep() {
this.id = uid ++;
this.subs = [];
}
Dep.target = null; // 记录当前this指向
Dep.prototype = {
constructor: Dep,
// watcher 与 dep建立关系
depend: function() {
// 把dep添加到watcher
Dep.target.addDep(this);
},
// 添加watcher
addSub: function(watcher) {
this.subs.push(watcher);
},
//通知 watcher,让watcher 去 更新界面
notify: function() {
this.subs.forEach(watcher => {
watcher.update();
})
},
}
function observe(value) {
// 判断是否是对象
if(!value || typeof value !== "object") return;
return new Observe(value);
}
- watcher.js
function Watcher(vm, exp, cb) {
this.cb = cb;
this.exp = exp;
this.vm = vm;
//先定义depIds,在获取值,否则会报错
this.depIds = {};
this.value = this.get();
}
Watcher.prototype = {
consructor: Watcher,
get: function() {
// 把当前watcher对象赋值给 Dep.target属性
Dep.target = this;
// console.log(this)
// var value = this.parseGetter(this.exp);
var value = this.vm._data[this.exp];
Dep.target = null;
return value;
// console.log(this,value);
},
addDep: function(dep) {
// console.log(this,dep)
// 判断是否建立过关系
//初始化编译模板时候,当读取vm数据时候 就建立关系
if(!this.depIds.hasOwnProperty(dep.id)) {
// 把watcher 添加到dep 中的subs , dep 与 watcher 关系 一对多,
//模板中有几个表达式,就添加几个watcher
dep.addSub(this);
//把dep 添加到 watcher中的depIds, watcher 与 dep 关系 一对多
// 表达式有多少层级,就有几个dep
this.depIds[dep.id] = dep;
console.log(this,dep)
}
// 注意: dep 和 watcher对象之间 方法有通讯方式有两种
//1. 通过函数参数 进行 关联
//2. 通过函数this 进行 关联
},
// 更新操作
update: function() {
//获取最新值
var newVal = this.get();
var oldVal = this.value;
console.log(newVal, oldVal);
if(newVal !== oldVal) {
this.value = newVal;
this.cb.call(this.vm, newVal, oldVal);
}
},
//处理层级嵌套去访问属性 a.b.c
parseGetter: function(exp) {
if(/\./.test(exp)) {
let val;
let exps = exp.split(".");
exps.forEach(exp => {
val = this.recur(exp)
})
return value;
}else {
return this.vm._data[exp]
}
},
//递归
recur: function(exp) {
let value = this.vm._data[exp];
if(typeof value === "object") {
this.recur(value);
}
}
}
网友评论