响应式用了obejct.defineProperty属性可以看看前面的文章:
https://www.jianshu.com/p/7f0ff748eb76
首先我们先不看Dep类和Watcher类。先把简单的解决。再来添加;
开始
首先先说大体的思路:
- 入口 observe 会先查看 对象有没有
__ob__
的属性,如果没有的话,并且 实例化Observer方法(方法会把__ob__
属性挂载到对象上)。- Observer 会遍历下一层属性, 逐个调用 defineReactive方法, 转换属性为响应式。 Observer 会把
__ob__
属性 挂载到对象上。 (如果是属性对应的值是数组,会把数组的方法改写,以便使用数组的push,pop等方法也会触发响应式)- defineReactive方法, 会把对象 上的属性转换为 响应式。 如果属性 的值(或者 修改后的值) 又是 一个对象的话,又会调用 observe方法 重新 走一遍1,2,3 把对应值的属性转换为响应式;
首先比如我们有个对象
var obj = {
a: {
m: {
n: 999
}
},
b: 4,
g: [1,2,3,4]
}
observe(obj)
要让这个对象响应式,那么我们需要一个 函数 来转换它。
这个时候会引入一个 observe函数(判断是否有__ob__
,没有就调用 Observer)
// observe.js
import Observer from './Observer'
// 入口文件 实现动态监听
// 创建observe 函数 判断是否有__ob__,没有就调用 Observer
export default function observe (value) {
//如果 value 不是对象, 直接返回(只能为对象服务)
if (typeof value !== 'object') return;
// 定义 ob
var ob;
// __ob__ 存储有 Observer类 的实例
if (typeof value.__ob__ !== 'undefined') {
ob = value.__ob__;
} else {
ob = new Observer(value);
}
return ob;
}
observer 方法里面要注意 判断 对象是不是数组,对象我们改变 值的时候可以触发 响应式 ,但是对于数组的push,pop,shift等方法 需要单独处理,才能实现响应式
// observer.js
import { def } from './util.js'
import defineReactive from './defineReactive.js'
import { arrayMethods } from './array.js'
import observe from './Observe.js'
// 把对象 转换 成 每个层级的属性 都是响应式的对象(可以被侦测); 比如: obj:{a:{b:{c: 5}}}
export default class Observer {
constructor(value) {
// 给value(对象) 添加了 __ob__ 的属性,值是 这次new 的实例(构造函数中的this 表示的是实例),并且不可枚举
def(value, '__ob__', this, false)
// 检查value 是不是 数组
if (Array.isArray(value)) {
// 如果value是数组, 把value的原型,指向 arrayMethods(push等7个方法 被 改写过)
Object.setPrototypeOf(value, arrayMethods) // 或者用 value.__proto__ = arrayMethods
// 数组 实现响应式
this.observeArray(value)
} else {
// 对象 直接 调用walk 方法 转换 为响应式
this.walk(value)
}
}
// 遍历 value对象上的每一个 属性,并且 调用 defineReactive方法 转换为响应式
walk (value) {
for (let k in value) {
defineReactive(value, k)
}
}
// 数组的特殊遍历
observeArray (arr) {
for (let i = 0, l = arr.length; i < l; i++) {
// 逐项进行observe
observe(arr[i])
}
}
}
//util.js
// def 方法定义对象 并且属性是否可以被枚举
export const def = function (obj, key, value, enumerable) {
Object.defineProperty(obj, key, {
value,
enumerable, // 是否可枚举
writable: true, //可写
configurable: true, //可删除
})
}
// defineReactive.js
import observe from './Observe.js'
// 包裹 Object.defineProperty, 把 对象上的属性 转换为响应式
// (为什么要包裹? 因为:如果key为常量的时,get返回值永远是一个值。包裹之后才能 修改);
export default function defineReactive (data, key, val) {
// 如果值传入两个参数 ,那么 val 就是 对象对应的值
if(arguments.length===2){
val = data[key]
}
//如果val 是对象,对象上的属性也要变化动态的。如果不是对象(常数、undefined等),observe函数里面不会处理
// 子元素也要 observe; (observe,observer,defineReactive三个函数相互调用,形成了类似递归的效果)
let chilOb = observe(val)
Object.defineProperty(data, key, {
enumerable: true, //可以被枚举
configurable: true, // 该属性的描述符能被改变,能被删除
// 访问的时候触发 get函数
get () {
// console.log('触发getter');
// 只要调用了observe 监听对象后,有获取对象上值的时候 就会触发get方法
return val
},
// 修改的时候触发 set 函数
set (newValue) {
// console.log('触发setter', newValue);
if (val === newValue) return;
val = newValue
// 当设置 新值的时候也要调用 observe 方法把 新值(如果是对象)转换为响应式
chilOb = observe(newValue)
}
})
}
// array.js
// 重写 数组的七个方法 以便实现响应式(使用方法的时候触发依赖), 把重写后的 方法暴露出去
// 为什么要有这个方法 。改变对象的值,可以触发依赖更新。但是 数组的 push,pop等方法不会触发依赖更新。所有需要重写数组的七个方法 以实现 使用push,pop等方法时触发依赖更新
import { def } from './util.js'
// 得到 Array.prototype
const arrayPrototype = Array.prototype
// 需要被改写的 数组方法
const methodsNeedChange = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
// 以 Array.prototype 为原型创建 arrayMethods 对象
export const arrayMethods = Object.create(arrayPrototype)
methodsNeedChange.forEach(methodName => {
// 备份原来的方法, 方法原来的功能需要保留
const original = arrayPrototype[methodName]
// 定义新的方法(方法是对象,所有可以调用前面封装的def方法)
def(arrayMethods, methodName, function () {
// 恢复原来的功能,执行数组方法
const result = original.apply(this, arguments)
// push, unshift, splice 会插入新值。 新值需要调用observe方法也 转换为 响应式
let inserted = null
// 获得 数组身上 的__ob__
const ob = this.__ob__;
// arguments 是类数组 ,没有slice方法 ,把arguments 转换为数组
const ars = [...arguments]
switch (methodName) {
case 'push':
case 'unshift':
inserted = arguments;
break;
case 'splice':
// splice (位置,删除几项, 插入的第1个值,插入的第2个值,插入的第3个值。。。)
inserted = ars.slice(2)
break;
}
// 判断有没有inserted, 有新值 也变为响应式
if (inserted) {
ob.observeArray(inserted)
}
console.log('触发array.js_啦啦啦啦啦啦');
// pop,shift 会有返回值
return result
}, false)
})
Dep 和 Watcher
上面代码里面的注释已经很详细了。如果看懂了。那接下来我们使用Dep 和 Watcher 是数据变化后真正可以更新页面上的数据;
- Dep 类 是干什么的呢 ? Dep 类收集 依赖 然后 触发依赖更新 (其中每一个依赖都是Watcher实例)
- Watcher 是 监听对象 的表示式 然后改变的时候 调用回调,这个时候我们就能更新页面的数据了
这里有几个点
- Dep 类 把所有依赖收集到一起, 专门用来管理依赖的类。 每一个Observer的实例 ,成员都有一个Dep的实例。
- Watcher是一个中介, 数据发生改变的时候, 通过Watcher中转,通知组件。
- 在getter 中收集依赖, 在setter中 触发依赖 (其中每一个依赖 其实就是 watcher)
- 代码的巧妙之处是: watcher 把自己设置到一个全局位置(Dep类的身上,Dep类只有一个,当然也可以设置到window上只要唯一就行),当读取数据是,触发这个数据的 getter,getter中 获取正在读取数据的wathcer(依赖),把这个watcher 收集到Dep中
- Dep 使用发布订阅模式,只有有数据变化,就把依赖列表(收集了很多依赖),循环通知一遍。
比如下面:
// index.js
import observe from './Observe.js'
import Watcher from './Watcher.js'
var obj = {
a: {
m: {
n: 999
}
},
b: 4,
g: [1,2,3,4]
}
observe(obj)
// watcher obj的时候,obj已经被observe观察了。
// 此时 在watcher中要获取 obj.a.m.n的值的时候,就会触发defineReactive 中 getter方法。
// getter方法中就会收集当前这个watcher的实例到依赖列表中
new Watcher(obj, 'a.m.n', (newValue, oldValue)=>{
console.log('**************');
// 去更新页面上 {{a.m.n}}
})
// 由于a.m.n的实例已经被收集到依赖列表中了
// 此时改变 a.m.n是就会 调用 上面watcher的回调函数了。
obj.a.m.n = 198
开始:
Dep类
// dep.js
var uid = 0;
// Dep 类收集 依赖 然后 触发依赖更新 (其中每一个依赖都是Watcher实例)
export default class Dep {
constructor() {
this.id = uid++;
// 存储自己的订阅者(收集的watcher实例)。
this.subs = []
}
// 添加订阅
addSub (sub) {
this.subs.push(sub)
}
// 添加依赖
depend(){
// Dep.target 自定义的一个全局唯一位置。也可以用window.__myOnly__随便啥都行;
// Dep.target 上是正在读取数据的watcher, 之后会重置为null;
console.log('depend', Dep.target);
if(Dep.target){
this.addSub(Dep.target)
}
}
// 触发依赖 通知更新
notify () {
// 浅克隆
const subs = this.subs.slice();
// 遍历
for (let i = 0, l = subs.length; i < l; i++) {
// 每一个 watcher 调用 update更新方法
subs[i].update()
}
}
}
Watcher类
// watcher.js
import Dep from './Dep'
// 监听对象 的表示式 触发回调
// vm.$watch('a.b.c.d', ()=>{}) a.b.c.d是下面的表达式
var uid = 0;
export default class Watcher {
constructor(target, expressiong, callback) {
this.id = uid++;
// 需要监听的对象
this.target = target;
// 获取 表达式 解析后的函数
this.getter = parsePath(expressiong)
// 回调
this.callback = callback;
// 获取 Watcher 实例的 值
this.value = this.get()
}
// 更新
update () {
this.getAndInvoke(this.callback)
}
// 得到并唤起
getAndInvoke (cb) {
const value = this.get()
// 如果更新后 到的值不是 当前的值 或者 是一个对象 。 那么更新value 并执行回调
if (value !== this.value || typeof value == 'object') {
const oldValue = this.value;
this.value = value
cb.call(this, value, oldValue)
}
}
// 获取
get () {
console.log('进入wathcer');
// 进入依赖收集 阶段 (把watcher 设置全局唯一位置,以便Dep收集 当前的watcher实例)
Dep.target = this;
const obj = this.target
// 获取 对象中表示式 对应的值
let value;
// 只要能找,就一直找
try {
// obj对象 在watcher 实例之前 就已经observe了。 (此时要获取值,就会触发 getter方法)
// 所以这个时候 调用this.getter(obj) 会触发 defineReactive 的getter方法 。
// getter方法中 判断 如果 Dep.target存在(此时存在了,就是这个Watcher实例),就会收集 watcher实例到 dep中
value = this.getter(obj)
} finally {
// get结束后 ,说明读结束了。要把全局位置 让出来
Dep.target = null;
}
console.log('进入wathcer-end', Dep.target);
return value
}
}
// 把a.b.c.d 处理 返回一个函数 调用返回的函数 可以获取 函数的值
// 比如 var fn = parsePath('a.b.c.d') ; fn({a:{b:{c:{d:88}}}}) 获取 88
function parsePath (str) {
var list = str.split('.')
return (obj) => {
if (!obj) return;
for (let i = 0; i < list.length; i++) {
obj = obj[list[i]]
}
return obj
}
}
- 挂载dep ; 每一个observer的实例都会有一个dep;这个dep是用来收集 数组 的依赖的
// observer.js
import Dep from './Dep.js'
export default class Observer {
constructor(value) {
// *new add* 每一个Observer的实例身上,都有一个Dep(也就是说__ob__上会有 dep). 目的是挂载上dep
// *new add* 每一个 Observer 实例上会有 dep 用来收集 数组 的依赖
// *new add* 在对对象调用 defineReactive 实现依赖收集时,如果判断 对象上一项是 数组,就会 收集依赖 到 这个 dep上
this.dep = new Dep()
}
- 然后是收集依赖 和触发依赖
对象中 收集和触发
// defineReactive.js
import observe from './Observe.js'
import Dep from './Dep.js'
import { dependArray } from './array.js'
export default function defineReactive (data, key, val) {
// *new add* 获取Dep 的实例 便于收集和触发依赖
// *new add* 每一个 对象监听的 属性,都需要一个 dep 收集依赖
// *new add* 数组的依赖 需要到 Observer上的一个dep上 ,下面的
const dep = new Dep()
if(arguments.length===2){
val = data[key]
}
// *new add* 如果val 是对象,对象上的属性也要变化动态的。如果不是对象(常数、undefined等),observe函数里面不会处理
// *new add* 子元素也要 observe; (observe,observer,defineReactive三个函数相互调用,形成了类似递归的效果)
// *new add* observe 返回 Observer的实例, 实例上有 收集数组 的dep
let chilOb = observe(val)
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get () {
// *new add* 如果处于依赖收集阶段, 那么就收集依赖
// Dep.target 是全局的一个唯一值,是正在被watcher的实例
if(Dep.target) {
dep.depend()
// 如果子元素 存在的话, 也要收集依赖
if (chilOb) {
chilOb.dep.depend() // *new add* 数组收集当前渲染的watcher
dependArray(val) // *new add* 收集儿子的依赖
}
}
return val
},
// 修改的时候触发 set 函数
set (newValue) {
if (val === newValue) return;
val = newValue
chilOb = observe(newValue)
// *new add* 这里是对象 改变 触发依赖 通知 dep
dep.notify()
}
})
}
然后 数组中我们调用push,pop等方法是也要触发依赖
// array.js
methodsNeedChange.forEach(methodName => {
const original = arrayPrototype[methodName]
def(arrayMethods, methodName, function () {
const result = original.apply(this, arguments)
let inserted = null
const ob = this.__ob__;
const ars = [...arguments]
switch (methodName) {
case 'push':
case 'unshift':
inserted = arguments;
break;
case 'splice':
inserted = ars.slice(2)
break;
}
if (inserted) {
ob.observeArray(inserted)
}
// *new add* 这里是 数组变化 触发依赖
// 上面给每一个observer都挂载了dep,那么`__ob__`上也会有dep
ob.dep.notify()
return result
}, false)
})
// *new add* dependArray() 收集儿子的依赖
export function dependArray(value) {
for (let i = 0; i < value.length; i++) {
let currentItem = value[i]
currentItem.__ob__&& currentItem.__ob__.dep.depend()
if(Array.isArray(currentItem)){
dependArray(currentItem) // 递归收集依赖
}
}
}
这个加上Dep 和 Watcher 之后是有点绕,需要慢慢想一想;
// new add 每一个 Observer 实例上会有 dep 用来收集 数组 的依赖
// new add 在对对象调用 defineReactive 实现依赖收集时,如果 对象上 某一项是 数组或对象,就会 收集依赖 到 这个 dep上
// 以便于 没有触发 defineReactive 的 set 方法中 dep.notify(),我们可以 使用 Observer 实例上的 ob 上的 另一个 dep 触发 notify
// Observer实例上的 dep 和 Observer实例上(对象)上 某一项 obj[key] 中 key 的 dep 收集的是同一个依赖。所有 修改 obj[key] 或者 调用 数组的push等等触发的__ob__.dep.notify
都会 通知依赖更新比如:
obj1={list1: [1,2,3]}
=>
Observe(obj1)
=>
生成Observer实例obj1_Observer = new Observer(obj1)
=>
实例 生成一个Dep实例dep1
=>obj1_Observer__ob__dep1
然后 对obj1.list1
调用defineReactive(obj1, list1)
会生成一个新的Dep 实例dep2
(监听obj1.list1
的修改)
同时obj1.list1
对应的[1,2,3]
是一个数组或对象
又会调用observe([1,2,3])
返回一个新的 Observer实例observer2
observer2
上又会产生一个 新的Dep实例dep3
=>observer2__ob__dep3
当 在 get 访问中 访问obj1.list1
时, 会收集dep2
的依赖,并且收集dep3
。(由于访问都是又Watcher触发的,所有收集的都是相同的依赖)
当修改对象 的具体 数值的时候, 会触发defineReactive中
的set
方法, 然后set
方法 可以访问 到dep2
, 所有调用dep2.notify
触发依赖更新
由于数组的更改比如push
,shift
,splice
等无法触发defineReactive
中的set
方法 。我们 修改 了Observer实例 observer2
中的 数组 方法。
当调用数组修改后 的方法时,我们 就调用Observer实例 observer2
上的observer2__ob__dep3
=>dep3.notify
触发依赖 更新
总结来说
。我们对 对象 某一项 转换为响应式的 时候,不仅仅 收集 可以通过defeineProperty
修改某一项的 依赖; 还留一个口, 用来无法通过defeineProperty
监听的数据,我们通过其他方法 收集依赖
比如上面我们通过 修改 obj1.list1 = 2 可以触发 dep2 更新依赖
通过 obj1.list1.push(3) 可以触发 dep3 更新依赖
而依赖都是同一个 Watcher 。所有都是处理 同一个事件,比如更新模板
下面是所有的代码:
index.js
import defineReactive from './defineReactive.js'
import observe from './Observe.js'
import Watcher from './Watcher.js'
var obj = {
// a: {
// m: {
// n: 999
// }
// },
// b: 4,
g: [1,2,3,4]
}
observe(obj)
new Watcher(obj, 'g', (newVal, oldVal)=>{
console.log('watch', newVal, oldVal);
})
obj.g.push(5)
// new Watcher(obj, 'a.m.n', (newValue, oldValue)=>{
// console.log('**************', newValue, oldValue);
// // 去更新页面上 {{a.m.n}}
// })
// obj.a.m.n = 198
// console.log('aaaaaaaaaaaaaa', a);
// obj.a.m.n = 44
// // obj.g.splice(2,1,66,77,88)
// obj.g.push({"test": 6})
// console.log('obj', obj);
// 先不说依赖 和 watch Dep类 和 watcher类
//1. 入口 observe 会先查看 对象有没有 __ob__ 的属性,如果没有的话,并且 实例化Observer方法(方法会把__ob__属性挂载到对象上)。
//2. Observer 会遍历下一层属性, 逐个调用 defineReactive方法, 转换属性为响应式。 Observer 会把 __ob__ 属性 挂载到对象上。 (如果是属性对应的值是数组,会把数组的方法改写,以便掉数组的push,pop等方法也会触发响应式)
//3. defineReactive方法, 会把对象 上的属性转换为 响应式。 如果属性 的值(或者 修改后的值) 又是 一个对象的话,又会调用 observe方法 重新 走一遍1,2,3 把对应值的属性转换为响应式。 并且 属性值是对象 会返回 Observer的实例,实例上有一个dep,就可以用来收集数组依赖
// Dep 类 把所有依赖收集到一起, 专门用来管理依赖的类。 每一个Observer的实例 ,成员都有一个Dep的实例, 用来收集数组的依赖
// Watcher是一个中介, 数据发生改变的时候, 通过Watcher中转,通知组件。
// 在getter 中收集依赖, 在setter中 触发依赖 (其中每一个依赖 其实就是 watcher)
// 代码的巧妙之处是: watcher 把自己设置到一个全局位置(Dep类的身上,Dep类只有一个,当然也可以设置到window上只要唯一就行),当读取数据是,触发这个数据的 getter,getter中 获取正在读取数据的wathcer(依赖),把这个watcher手机到Dep中
// Dep 使用发布订阅模式,只有有数据变化,就把依赖列表(收集了很多依赖),循环通知一遍。
Observe.js
import Observer from './Observer'
// 入口文件 实现动态监听
// 创建observe 函数
export default function observe (value) {
//如果 value 不是对象, 直接返回(只能为对象服务)
if (typeof value !== 'object') return;
// 定义 ob
var ob;
// __ob__ 存储 Observer类 的实例
if (typeof value.__ob__ !== 'undefined') {
ob = value.__ob__;
} else {
ob = new Observer(value);
}
return ob;
}
Observer.js
import { def } from './util.js'
import defineReactive from './defineReactive.js'
import { arrayMethods } from './array.js'
import observe from './Observe.js'
import Dep from './Dep.js'
// 把对象 转换 成 每个层级的属性 都是响应式的对象(可以被侦测); 比如: obj:{a:{b:{c: 5}}}
export default class Observer {
constructor(value) {
// *new add* 每一个Observer的实例身上,都有一个Dep(也就是说__ob__上会有 dep). 目的是挂载上dep
// *new add* 每一个 Observer 实例上会有 dep 用来收集 数组 的依赖
// *new add* 在对对象调用 defineReactive 实现依赖收集时,如果 对象上 某一项是 数组或对象,就会 收集依赖 到 这个 dep上
// 以便于 没有触发 defineReactive 的 set 方法中 dep.notify(),我们可以 使用 Observer 实例上的 __ob__ 上的 另一个 dep 触发 notify
// Observer实例上的 dep 和 Observer实例上(对象)上 某一项 obj[key] 中 key 的 dep 收集的是同一个依赖。所有 修改 obj[key] 或者 调用 数组的push等等触发的 __ob__.dep.notify 都会 通知依赖更新
/**
比如:
obj1={list1: [1,2,3]} =>
Observe(obj1) ;=>
生成Observer实例 obj1_Observer = new Observer(obj1) =>
实例 生成一个Dep实例 dep1 obj1_Observer__ob__dep1
然后 对 obj1.list1 调用 defineReactive(obj1, list1)
会生成一个新的Dep 实例 dep2 (监听 obj1.list1的修改)
同时 obj1.list1对应的 [1,2,3] 是一个数组或对象
又会调用 observe([1,2,3])
返回一个新的 Observer实例 observer2
observer2 上又会产生一个 新的Dep实例 dep3 observer2__ob__dep3
当在 在 get 访问中 访问 obj1.list1时, 会收集 dep2的依赖,并且收集dep3。(由于访问都是又Watcher触发的,所有收集的都是相同的依赖)
当修改对象 的具体 数值的时候, 会触发 defineReactive中的set 方法, 然后 set 方法 可以访问 到 dep2, 所有调用dep2.notify触发依赖更新
由于数组的更改比如push,shift,splice等无法触发 defineReactive中的set 方法 。我们 修改 了 Observer实例 observer2 中的 数组 方法。
当调用数组修改后 的方法时,我们 就调用 Observer实例 observer2 上的 observer2__ob__dep3 dep3.notify 触发依赖 更新
总结来说。我们对 对象 某一项 转换为响应式的 时候,不仅仅 收集 可以通过defeineProperty修改某一项的 依赖; 还留一个口, 用来无法通过 defeineProperty监听的数据,我们通过其他方法 收集依赖
比如上面我们通过 修改 obj1.list1 = 2 可以触发 dep2 更新依赖
通过 obj1.list1.push(3) 可以触发 dep3 更新依赖
而依赖都是同一个 Watcher 。所有都是处理 同一个事件,比如更新模板
* **/
this.dep = new Dep()
// 给value(对象) 添加了 __ob__ 的属性,值是 这次new 的实例(构造函数中的this 表示的是实例),并且不可枚举
def(value, '__ob__', this, false)
// 检查value 是不是 数组
if (Array.isArray(value)) {
// 如果value是数组, 把value的原型,指向 arrayMethods(push等7个方法 被 改写过)
Object.setPrototypeOf(value, arrayMethods) // 或者用 value.__proto__ = arrayMethods
// 数组 实现响应式
this.observeArray(value)
} else {
// 对象 直接 调用walk 方法 转换 为响应式
this.walk(value)
}
}
// 遍历 value对象上的每一个 属性,并且 调用 defineReactive方法 转换为响应式
walk (value) {
for (let k in value) {
defineReactive(value, k)
}
}
// 数组的特殊遍历 (数组的每一项都要调用 转换为响应式 ,可能数组里面也有数组或对象)
observeArray (arr) {
for (let i = 0, l = arr.length; i < l; i++) {
// 逐项进行observe
observe(arr[i])
}
}
}
util.js
// 定义对象 并且属性是否可以被枚举
export const def = function (obj, key, value, enumerable) {
Object.defineProperty(obj, key, {
value,
enumerable, // 是否可枚举
writable: true, //可写
configurable: true, //可删除
})
}
array.js
// 重写 数组的七个方法 以便实现响应式, 把重写后的 方法暴露出去
// 为什么要有这个方法 。改变对象的值,可以触发依赖更新。但是 数组的 push,pop等方法不会触发依赖更新。所有需要重写数组的七个方法 以实现 使用push,pop等方法时触发依赖更新
import { def } from './util.js'
// 得到 Array.prototype
const arrayPrototype = Array.prototype
// 需要被改写的 数组方法
const methodsNeedChange = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
// 以 Array.prototype 为原型创建 arrayMethods 对象
export const arrayMethods = Object.create(arrayPrototype)
methodsNeedChange.forEach(methodName => {
// 备份原来的方法, 方法原来的功能需要保留
const original = arrayPrototype[methodName]
// 定义新的方法(方法是对象,所有可以调用前面封装的def方法)
def(arrayMethods, methodName, function () {
// 恢复原来的功能,执行数组方法
const result = original.apply(this, arguments)
// push, unshift, splice 会插入新值。 新值需要调用observe方法也 转换为 响应式
let inserted = null
// 获得 数组身上 的__ob__
const ob = this.__ob__;
// arguments 是类数组 ,没有slice方法 ,把arguments 转换为数组
const ars = [...arguments]
switch (methodName) {
case 'push':
case 'unshift':
inserted = arguments;
break;
case 'splice':
// splice (位置,删除几项, 插入的第1个值,插入的第2个值,插入的第3个值。。。)
inserted = ars.slice(2)
break;
}
// 判断有没有inserted, 有新值 也变为响应式
if (inserted) {
ob.observeArray(inserted)
}
console.log('触发array.js_啦啦啦啦啦啦');
// *new add* 这里是 数组变化 触发依赖
ob.dep.notify()
// pop,shift 会有返回值
return result
}, false)
})
// *new add* dependArray() 收集儿子的依赖
export function dependArray(value) {
for (let i = 0; i < value.length; i++) {
let currentItem = value[i]
currentItem.__ob__&& currentItem.__ob__.dep.depend()
if(Array.isArray(currentItem)){
dependArray(currentItem) // 递归收集依赖
}
}
}
defineReactive.js
import observe from './Observe.js'
import Dep from './Dep.js'
import { dependArray } from './array.js'
// 包裹 Object.defineProperty, 把 对象上的属性 转换为响应式
// (为什么要包裹? 因为:如果key为常量的时,get返回值永远是一个值。包裹之后才能 修改);
export default function defineReactive (data, key, val) {
// *new add* 获取Dep 的实例 便于收集和触发依赖
// *new add* 每一个 对象监听的 属性,都需要一个 dep 收集依赖
// *new add* 数组的依赖 需要到 Observer上的一个dep上 ,下面的
const dep = new Dep()
// 如果值传入两个参数 ,那么 val 就是 对象对应的值
if (arguments.length === 2) {
val = data[key]
}
// *new add* 如果val 是对象,对象上的属性也要变化动态的。如果不是对象(常数、undefined等),observe函数里面不会处理
// *new add* 子元素也要 observe; (observe,observer,defineReactive三个函数相互调用,形成了类似递归的效果)
// *new add* observe 返回 Observer的实例, 实例上有 收集数组 的dep
let chilOb = observe(val)
Object.defineProperty(data, key, {
enumerable: true, //可以被枚举
configurable: true, // 该属性的描述符能被改变,能被删除
// 访问的时候触发 get函数
get () {
// *new add* 如果处于依赖收集阶段, 那么就收集依赖
if (Dep.target) {
dep.depend()
// 如果子元素 存在的话, 也要收集依赖
if (chilOb) {
chilOb.dep.depend() // *new add* 数组收集当前渲染的watcher
dependArray(val) // *new add* 收集儿子的依赖
}
}
return val
},
// 修改的时候触发 set 函数
set (newValue) {
if (val === newValue) return;
val = newValue
// 当设置 新值的时候也要调用 observe 方法把 新值(如果是对象)转换为响应式
chilOb = observe(newValue)
// *new add* 这里是对象 改变 触发依赖 通知 dep
// data.__ob__.notify()
dep.notify()
}
})
}
Dep.js
var uid = 0;
// Dep 类收集 依赖 然后 触发依赖更新 (其中每一个依赖都是Watcher实例)
export default class Dep {
constructor() {
this.id = uid++;
// 存储自己的订阅者(收集的watcher实例)。
this.subs = []
}
// 添加订阅
addSub (sub) {
this.subs.push(sub)
}
// 添加依赖
depend(){
// Dep.target 自定义的一个全局唯一位置。也可以用window.__myOnly__随便啥都行;
// Dep.target 上是正在读取数据的watcher, 之后会重置为null;
console.log('depend', Dep.target);
if(Dep.target){
this.addSub(Dep.target)
}
}
// 触发依赖 通知更新
notify () {
// 浅克隆
const subs = this.subs.slice();
// 遍历
for (let i = 0, l = subs.length; i < l; i++) {
// 每一个 watcher 调用 update更新方法
subs[i].update()
}
}
}
Watcher.js
import Dep from './Dep'
// 监听对象 的表示式 触发回调
// vm.$watch('a.b.c.d', ()=>{}) a.b.c.d是下面的表达式
var uid = 0;
export default class Watcher {
constructor(target, expressiong, callback) {
this.id = uid++;
// 需要监听的对象
this.target = target;
// 获取 表达式 解析后的函数
this.getter = parsePath(expressiong)
// 回调
this.callback = callback;
// 获取 Watcher 实例的 值
this.value = this.get()
}
// 更新
update () {
this.getAndInvoke(this.callback)
}
// 得到并唤起
getAndInvoke (cb) {
const value = this.get()
// 如果更新后 到的值不是 当前的值 或者 是一个对象 。 那么更新value 并执行回调
if (value !== this.value || typeof value == 'object') {
const oldValue = this.value;
this.value = value
cb.call(this, value, oldValue)
}
}
// 获取
get () {
console.log('进入wathcer');
// 进入依赖收集 阶段 (把watcher 设置全局唯一位置,以便Dep收集 当前的watcher实例)
Dep.target = this;
const obj = this.target
// 获取 对象中表示式 对应的值
let value;
// 只要能找,就一直找
try {
// obj对象 在watcher 实例之前 就已经observe了。 (此时要获取值,就会触发 getter方法)
// 所以这个时候 调用this.getter(obj) 会触发 defineReactive 的getter方法 。
// getter方法中 判断 如果 Dep.target存在(此时存在了,就是这个Watcher实例),就会收集 watcher实例到 dep中
value = this.getter(obj)
} finally {
// get结束后 ,说明读结束了。要把全局位置 让出来
Dep.target = null;
}
console.log('进入wathcer-end', Dep.target);
return value
}
}
// 把a.b.c.d 处理 返回一个函数 调用返回的函数 可以获取 函数的值
// 比如 var fn = parsePath('a.b.c.d') ; fn({a:{b:{c:{d:88}}}}) 获取 88
function parsePath (str) {
var list = str.split('.')
return (obj) => {
if (!obj) return;
for (let i = 0; i < list.length; i++) {
obj = obj[list[i]]
}
return obj
}
}