双向绑定
核心:对data完成可观察对象的转化+发布订阅模式
- 可观察对象的转化:Object.defineProperty()来设置getter/setter的钩子
- 发布订阅模式: 使用依赖注入的设计思想,通过中间人Dep对象来处理发布-订阅(更新-使用)过程的处理
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h1 id="name"></h1>
<input type="text">
<input type="button" value="改变data内容" onclick="changeInput()">
<script src="observer.js"></script>
<script src="watcher.js"></script>
<script>
function myVue (data, element, key) {
this.data = data;
observable(data);//将数据变的可观测
element.innerHTML = this.data[key];// 初始化模板数据的值
//watcher是data.key.dep.subs这个订阅名单数组中的元素,作用是把DOM对象和已经完成observablelization的对象里的key联系起来
new Watcher(this, key, function (value) {
element.innerHTML = value;
});
return this;
}
var ele = document.querySelector('#name');
var input = document.querySelector('input');
/*
@param ( Object ) data对象
@param ( Object ) 绑定的DOM
@param ( String ) data当中需要和DOM绑定的字段
*/
var myVue = new myVue({
name: 'hello world'
}, ele, 'name');
//改变输入框内容
input.oninput = function (e) {
myVue.data.name = e.target.value;
}
//改变data内容
function changeInput(){
myVue.data.name = "Hi, button changed myVue.data.name";
}
</script>
</body>
</html>observer.js
每一个组件都有一个data对象,使得data对象的每一项(key)都带有3样东西:
1.订阅器Dep,统一管理调度所有的订阅者(watcher/subscriber)
2.Getter钩子 - dep.depend()依赖收集,即把wathcer添加到名单中
3.Setter钩子 - dep.notify()依赖注入,即要告诉_所有的watcher:值发生了改变
function observable (obj) {
if (!obj || typeof obj !== 'object') {
return;
}
let keys = Object.keys(obj);
keys.forEach((key) =>{
defineReactive(obj,key,obj[key])
})
return obj;
}
function defineReactive (obj,key,val) {
let dep = new Dep();
Object.defineProperty(obj, key, {
get(){
dep.depend();
console.log(`${key} was read`);
return val;
},
set(newVal){
val = newVal;
console.log(`${key}was modified`);
dep.notify()//数据变化通知所有订阅者
}
})
}
class Dep {
constructor(){
this.subs = []
}
//增加订阅者
addSub(sub){
this.subs.push(sub);
}
//判断是否增加订阅者
depend () {
if (Dep.target) {
this.addSub(Dep.target)
}
}
//通知订阅者更新
notify(){
this.subs.forEach((sub) =>{
sub.update()
})
}
}
Dep.target = null;watcher.js
class Watcher {
constructor(vm,exp,cb){
this.vm = vm;
this.exp = exp;
this.cb = cb;
this.value = this.get(); // 将自己添加到订阅器的操作
}
get(){
Dep.target = this; // 缓存自己
let value = this.vm.data[this.exp] // 初始化赋值且触发observable当中的getter钩子
Dep.target = null; // 释放自己
return value;
}
update(){
let value = this.vm.data[this.exp];
let oldVal = this.value;
if (value !== oldVal) {
this.value = value;
this.cb.call(this.vm, value, oldVal);
}
}
}从现象/用户体验的角度来看整个过程
1.document/组件加载,中间完成几件事:
- 完成data对象向可观察对象的转化(observablization):data中每一项(key)都带有3样东西:dependencies,getter,setter
- 初始化(第一次触发getter钩子'name was read')
- new一个wathcer,会自动把自己添加到Dep的subscibor当中(并且第二次触发getter钩子'name was read')
2.DOM操作(点击按钮/在input中输入的时候)进行更新,并在DOM对象h1上显示出来新值
- 把DOM值赋值给data.name(触发setter钩子'name was modified')
- setter钩子当中,会调用dep.notify()通知所有subscribor来执行各自的update()函数
- 现在subscribor当中只有一个watcher,在new的时候第三个参数-回调函数(处理业务逻辑)作为update()的主体。此处因为是数据的双向绑定,所以业务逻辑就是把watcher接受到的新值,渲染到DOM对象h1上(第三次触发getter钩子'name was read')

京公网安备 11010502036488号