vue源码学习---双向数据绑定详细文档带注释

index.js文件

import observe from './observe.js'; // 观察到
import Watcher from './Watcher.js'; //观察者var obj = {a: {m: {n: 5}},b: 10,c: {d: {e: {f: 6666}}},g: [22, 33, 44, 55]
};observe(obj);
// debugger
// new Watcher(obj, 'a.m.n', (val) => {
//     console.log('★我是watcher,我在监控a.m.n', val);
// });
// obj.a.m.n = 88;
// obj.a.m.n = {'abc': 000};
// obj.a.m.n = 77
// obj.a.m.ppp = {
//     'hh': '123'
// }
obj.g.push(66);
// obj.g.push('哈哈哈')
console.log(obj);

observe.js

给对象绑定一个 Observer实例

import Observer from './Observer.js';
// observe 观察
export default function observe(value) {// 如果value不是对象,什么都不做if (typeof value != 'object') return;// 定义obvar ob;if (typeof value.__ob__ !== 'undefined') {ob = value.__ob__;} else {ob = new Observer(value);}return ob;
}

Observer.js

1、每一个Observer的实例身上,都有一个dep,用来存放 id / subs
2、判断value是数组还是对象,如果是对象,通过defineReactive 绑定get / set函数,
如果是数组,Object.setPrototypeOf(value, arrayMethods);将数组中的原型指向修改成重写后的原型
import { def } from './utils.js';
import defineReactive from './defineReactive.js';
import { arrayMethods } from './array.js';
import observe from './observe.js';
import Dep from './Dep.js'; //这里// Observer 观察者
export default class Observer {constructor(value) {// 每个对象或者数组都调用Observer,产生的实例身上都有一个depthis.dep = new Dep(); //这里可以查看每个对象或者数组添加订阅的 subs// 给实例(this,一定要注意,构造函数中的this不是表示类本身,而是表示实例)添加了__ob__属性,值是这次new的实例def(value, '__ob__', this, false);console.log('Observer', value);// console.log('我是Observer构造器', value);// 不要忘记初心,Observer类的目的是:将一个正常的object转换为每个层级的属性都是响应式(可以被侦测的)的object// 检查它是数组还是对象if (Array.isArray(value)) {// 如果是数组,要非常强行的蛮干:将这个数组的原型,指向arrayMethodsObject.setPrototypeOf(value, arrayMethods);// 让这个数组变的observethis.observeArray(value);} else {this.walk(value);}}// 遍历walk(value) {for (let k in value) {defineReactive(value, k);}}// 数组的特殊遍历observeArray(arr) {for (let i = 0, l = arr.length; i < l; i++) {// 逐项进行observeobserve(arr[i]);}}
};

Dep.js文件

1、添加订阅
2、派发更新

/*Dep 有三个地方调用1、 Observer 中 为了数组使用this.dep = new Dep();def(value, '__ob__', this, false); 2、 defineReactive // 获取之前绑定的 dep,用来添加订阅 有 id,subsconst dep = new Dep();get() {dep.depend();childOb.dep.depend();}set() {dep.notify();}3、 Watcher // 绑定target,方便get中添加订阅使用get() {Dep.target = this;try {value = this.getter(obj);} finally {Dep.target = null;}}
*/
var uid = 0;
export default class Dep {constructor() {console.log('我是DEP类的构造器');this.id = uid++;// 用数组存储自己的订阅者。subs是英语subscribes订阅者的意思。// 这个数组里面放的是Watcher的实例this.subs = [];}// 添加订阅addSub(sub) {this.subs.push(sub);}// 添加依赖depend() {// Dep.target就是一个我们自己指定的全局的位置,你用window.target也行,只要是全剧唯一,没有歧义就行if (Dep.target) {this.addSub(Dep.target);}}// 通知更新notify() {console.log('我是notify', this.subs);// 浅克隆一份if (this.subs.length > 0) {const subs = this.subs.slice();// 遍历for (let i = 0, l = subs.length; i < l; i++) {subs[i].update();}}}
};

Watcher.js

添加订阅者( 确定要被观察的数据 new Watcher(obj, a, () => {}) )
访问 obj.a 添加订阅, obj.a = 3 调用setter,notify通知更新,调用watcher实例中的update方法,返回监听到的数据

import Dep from "./Dep";var uid = 0;
export default class Watcher {constructor(target, expression, callback) {console.log('我是Watcher类的构造器');this.id = uid++;this.target = target;this.getter = parsePath(expression);this.callback = callback;this.value = this.get();}update() {this.run();}get() {// 进入依赖收集阶段。让全局的Dep.target设置为Watcher本身,那么就是进入依赖收集阶段Dep.target = this;const obj = this.target;var value;// 只要能找,就一直找try {value = this.getter(obj);} finally {Dep.target = null;}return value;}/*setter发现有新值改变-->触发dep中的notify()-->存放的watcher-->update()-->run()-->getAndInvoke()*/run() {this.getAndInvoke(this.callback);}getAndInvoke(cb) {const value = this.get();if (value !== this.value || typeof value == 'object') {const oldValue = this.value;this.value = value;cb.call(this.target, value, oldValue);}}
};function parsePath(str) {var segments = str.split('.');/*obj哪里来的,首先返回一个函数函数,接收一个对象,这个值就是this.getter传递的对象,在其中寻找,最后返回这个值*/return (obj) => {// debuggerfor (let i = 0; i < segments.length; i++) {if (!obj) return;console.log(segments[i], '66--->');obj = obj[segments[i]]}return obj;};
}

defineReactive.js文件

给每个属性绑定get / set 函数,添加依赖,通知更新

import observe from './observe.js';
import Dep from './Dep.js';
// defineReactive 定义响应式的
/**/
export default function defineReactive(data, key, val) {const dep = new Dep(); //这里// console.log('我是defineReactive', key);if (arguments.length == 2) {val = data[key];}// 子元素要进行observe,至此形成了递归。这个递归不是函数自己调用自己,而是多个函数、类循环调用let childOb = observe(val);// Reactive 反应性的Object.defineProperty(data, key, { // obj.a// 可枚举enumerable: true,// 可以被配置,比如可以被deleteconfigurable: true,// getterget() {console.log('你试图访问' + key + '属性');//1、 如果现在处于依赖收集阶段,当使用 new Watcher时,会获取其中的属性,然后给Dep.target绑定当前指向,并添加到Dep订阅,然后当数据发生改变debuggerif (Dep.target) { //这里是watcher函数绑定的target,就是添加订阅dep.depend();if (childOb) { // 为什么还有chidOb,因为当首次初始化的时候,发现obj.a是对象,增加一个deepchildOb.dep.depend();}}return val;},// setterset(newValue) {console.log('你试图改变' + key + '属性', newValue);if (val === newValue) {return;}val = newValue;// 当设置了新值,这个新值也要被observechildOb = observe(newValue);// 发布订阅模式,通知depdep.notify(); //这里 2、当数据发生改变后,更新订阅的函数,通知dep.update}});
};

Array.js 文件

重写数组的7个方法,

import { def } from './utils.js';// 得到Array.prototype
const arrayPrototype = Array.prototype;// 以Array.prototype为原型创建arrayMethods对象,并暴露
export const arrayMethods = Object.create(arrayPrototype);
debugger
// 要被改写的7个数组方法
const methodsNeedChange = ['push','pop','shift','unshift','splice','sort','reverse'
];methodsNeedChange.forEach(methodName => {// 备份原来的方法,因为push、pop等7个函数的功能不能被剥夺const original = arrayPrototype[methodName];// 取到原来的push pop 方法,给新数组原型中绑定push的空函数,等调用时apply指向调用者,然后执行的是修改后的函数,返回值用的原来的const result = original.apply(this, arguments),数据如果有监听的情况,走dep.notify()// 给拷贝出来的原型定义新的方法,例如:__prototype__中,methodName:push ,添加一个回调函数def(arrayMethods, methodName, function () {debugger// 恢复原来的功能, this:obj.g  arguments:传递的参数const result = original.apply(this, arguments);// 把类数组对象变为数组const args = [...arguments];//把这个数组身上的__ob__取出来,__ob__已经被添加了,为什么已经被添加了?// 因为数组肯定不是最高层,比如obj.g属性是数组, 在遍历obj时候,给每一项绑定observe,发现是对象,调用Observer(obj.g),通过def(value, '__ob__', this, false);绑定,方便在数组中调用notify跟observeArray,因为在observeArray在Observer中已经绑定const ob = this.__ob__;// 有三种方法push\unshift\splice能够插入新项,现在要把插入的新项也要变为observe的let inserted = [];switch (methodName) {case 'push':case 'unshift':inserted = args;break;case 'splice':// splice格式是splice(下标, 数量, 插入的新项)inserted = args.slice(2);break;}// 判断有没有要插入的新项,让新项也变为响应的if (inserted) {ob.observeArray(inserted);}console.log('啦啦啦');ob.dep.notify();return result;}, false);
});

了解每个文件是做什么的。

index.js

observe(obj); 给对象添加get/set 方法

Dep.js

1、收集依赖 depend() { this.addSub(Dep.target) }
2、通知更新 notify() { subs[i].update() }

observe.js

观察obj对象,是否有Observer,如果没有 new Observer(obj)

Observer.js

1、每一个Observer的实例身上,都有一个dep,用来存放 id / subs,
并绑定 def(value, ‘ob’, this, false);
方便外部查看监听的 watcher

2、接收的数据是对象,给每一个对象中的 key 添加 defineReactive 响应式
3、接收的数据是数组,遍历数组中的每一项 observe(arr[i]);
并将数组的原型指向重写后的原型 Object.setPrototypeOf(value, arrayMethods)

defineReactive.js 响应式 / 数据是否发生变化

对象中的key observe(val); 添加get/set

const dep = new Dep();

get() {
// 如果现在处于依赖收集阶段
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
}
}
return val;
},
set(newValue) {
// 发布订阅模式,通知dep
dep.notify();
}

Watcher.js 添加订阅者( 确定要被观察的数据 new Watcher(obj, a, () => {}) )

this.value = this.get();

get() {
// 进入依赖收集阶段。让全局的Dep.target设置为Watcher本身,
1、那么就是进入依赖收集阶段, 因为在访问obj.a的值,就会触发 defineReactive中的 get,如果Dep.target 存在 收集依赖,
2、如果监听到值发生改变,就会触发 defineReactive中的 set 通知dep.notify()来触发 Dep.subs

class Watcher{constructor(){this.value = this.get()}
}
get() {Dep.target = this;const obj = this.target;// 只要能找,就一直找try {value = this.getter(obj); } finally {Dep.target = null;}return value;
}

}

array.js 用来重写数组的7个方法

1、获取到数组的中原型 Array.prototyoe
2、Object.create(Array.prototyoe) 拷贝出来一份新的原型对象
3、遍历 7 个方法,重新绑定回调函数 arrayMethods 为所有原型,拿到 push, 绑定回调函数
4、const ob = this.ob// 把这个数组身上的__ob__取出来,__ob__已经被添加了,为什么已经被添加了?
// 因为数组肯定不是最高层,比如obj.g属性是数组, 在遍历obj时候,给每一项绑定observe,发现是对象,调用Observer(obj.g),通过def(value, ‘ob’, this, false);绑定,方便在数组中调用notify跟observeArray,因为在observeArray在Observer中已经绑定

具体流程如下:

在这里插入图片描述

new Vue()首先执行初始化,对data执行响应化处理,这个过程发生
observe(obj) ->
Observe 给每个对象绑定__ob__-> 判断是数组还是对象 ->

对象中:
遍历给每个属性绑定get/set函数,查看是否需要订阅的属性,

数组中
1、绑定重写数组的方法,改变数组的原型指向 Object.setPrototypeOf(value, arrayMethods); 遍历数组中的每一项,调用 defineReactive 绑定get/set
2、 const ob = this.ob; //把这个数组身上的__ob__取出来,__ob__已经被添加了,为什么已经被添加了?
// 因为数组肯定不是最高层,比如obj.g属性是数组, 在遍历obj时候,给每一项绑定observe,发现是对象,调用Observer(obj.g),通过def(value, ‘ob’, this, false);绑定,方便在数组中调用notify跟observeArray,因为在observeArray在Observer中已经绑定

开始进行模板编译Compile,找到其中动态绑定的数据 v-model=“obj.a”,从data中获取并初始化视图,获取完后在用文档碎片将数据插入到页面

同时定义一个更新函数和Watcher,将来对应数据变化时Watcher会调用更新函数;
由于data的某个key在一个视图中可能出现多次,多个地方打印obj.a,所以每个key都需要一个管家Dep来管理多个Watcher;
将来data中数据一旦发生变化,会首先找到对应的Dep,通知所有Watcher执行更新函数。
主要解决两个问题


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部