变化侦听
为什么是变化侦听重要?
因为它是MV*框架的基石,变化侦听可以说是各种自动化前端框架的基础,将view和model 单向或者说双向绑定在一起。
变化侦听原理:代理模式 发布订阅机制
这些变化侦听的思路就是给被侦听的对象加上一个包装,由包装实现功能,就是代理模式,
- 实现对象的变化侦听
- 实现数组的变化侦听
- 为什么Vue要用Proxy重写变化侦听
- 如何使用Proxy实现变化侦听
实现对象的变化侦听
对于每一个被对象我们用Observe类其包装起来。
对于每一个监听者我们用watch类将其注册到被监听对象上。
题外话: 在旧版的vue上,每一个vnode都被注册到监听对象上,这种粒度太细了,导致会浪费很多的内存空间,于是到了后面vue只讲每个组件注册到监听对象上,组件再更新一组新的vnode出来和旧的vnode进行比较(diff 算法) , 最后渲染。
Observer
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
removeSub(sub) {
remove(this.subs,sub);
}
depend() {
if (window.target) {
this.addSub(window.target);
}
}
notify() {
for(let i=0;i<this.subs.length;i++) {
// console.log(this.subs[i])
this.subs[i].update();
}
}
}
function remove(arr,sub) {
if (arr.length) {
const index = arr.indexOf(sub);
if (index > -1) {
return arr.splice(index,1);
}
}
}
export class observe {
constructor(data) {
this.data = data;
if (!Array.isArray(this.data)) {
this.handle(this.data);
}
}
handle(data) {
const keys = Object.keys(data)
for (let i=0;i<keys.length;i++) {
defineReactive(data,keys[i],data[keys[i]]);
}
}
}
// 包装每个属性的set和get
function defineReactive(data,key,val) {
// 如果属性还是属性 那么递归处理
if (typeof val === 'object') {
new observe(val);
}
let dep = new Dep();
Object.defineProperty(data,key,{
enumerable: true,
configurable: true,
// get 和 set方法不能处理set和delete方法--所以vue又有vm.$delete 和 vm.$add Api
get() {
// 添加到广播列表中
dep.depend();
return val;
},
set(newVal) {
if (val===newVal) {
return;
}
val = newVal;
// 通知数据改变
dep.notify();
}
})
}
watcher
// 注册自己到被Observe里
export class watcher {
constructor(vm,path,update) {
this.vm = vm;
this.getter = parsePath(path);
this.cb = update;
// 通过调用getter将自己注入到Observe中
this.value = this.get();
}
get() {
window.target = this;
const value = this.getter.call(this.vm,this.vm);
window.target = null;
return value;
}
update() {
const oldValue = this.value;
this.value = this.getter(this.vm,this.vm);
this.cb.call(this.vm,this.value,oldValue);
}
}
function parsePath(path) {
const segs = path.split('.');
return function(obj) {
for (let i=0;i<segs.length;i++) {
if (!obj)return;
obj = obj[segs[i]];
}
return obj;
}
}
效果
可以看到数字不断增加
<div id="app"></div>
<script type="module">
import {observe,watcher} from "./detectChange/dep.js"
let vm = {
app: {},
init() {
this.app = document.getElementById('app');
},
}
function update(val) {
console.log(this);
this.app.innerText = val;
}
vm.data = {
age: 1,
}
vm.init();
update.call(vm,vm.data.age);
setInterval(()=>{
vm.data.age++
},1000)
new observe(vm.data);
new watcher(vm,"data.age",update);
</script>
实现Array的变化侦听
上面的方法对于数组我们只能监听数组指向的那些元素,而对于数组本身调用push,等待方法我们无法进行监听,所以对于数组我们要单独处理
- 封装数组本来的api
- 替换api
- 依赖收集
具体实现可以看vue代码或者其他人的代码