Firstly, the data response diagram is drawn in the form of graph
You can see from the flowchart that there are several different methods and classes
- Array processing by the Observe and Observer classes that observe data
- DefineReactive to observe the data
- Dep for data dependency
- Add a handler Watch to the dependency
A diagram of several files
- index.js
import defineReactive from './data/defineReactive.js'
import {observe} from './data/observer'
import Watcher from './data/watcher.js'
var obj = {
'a': {
"b": {'n': 5}},'b': 6.'g': ['90'.'76'.'766'.'56']
}
observe(obj)
new Watcher (obj, 'a.b.n'.(val) = > {
console.log('* * * * * * * *',val)
})
obj.a.b.n = 890
Copy the code
- observer.js
import {def} from './utils.js'
import defineReactive from './defineReactive.js'
import {arrayMethods} from './array.js'
import Dep from './dep.js'
export default class Observer {
constructor (value) {
this.dep = new Dep()
// A non-enumerable attribute this refers not to the class itself, but to the instance
// Add an __ob__ attribute to value via def and point to the instance itself
def(value, '__ob__'.this.false)
// The ultimate goal of the Observer is to convert properties at each level in OBJ to reactive
if (Array.isArray(value)) {
// If it is an array, force the prototype to point to the new address
Object.setPrototypeOf(value, arrayMethods)
// Change this array to observe
this.observeArray(value)
}else{
this.walk(value)
}
}
walk (value) {
// Iterate over each KEY value to make each level of attributes responsive
for(let key in value) {
defineReactive(value,key)
}
}
observeArray (arr) {
for (let i=0, l= arr.length; i<l; i++) {
observe(arr[i])
}
}
}
export function observe (value) {
// Check if it is an object
if (typeofvalue ! = ='object') return;
var ob;
// Check if __ob__ is bound to the object
if (typeofvalue.__ob__! = ='undefined') {
ob = value.__ob__
}else {
ob = new Observer(value)
}
return ob
}
Copy the code
- utils.js
// Def uses defineProperty to add attributes and attribute values to an object
export function def (obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value : val,
enumerable, // class enumeration
writable: true.// Is it writable
configurable: true // It is not set by class})}// Returns a higher-order function, used in Watcher, to get the value of an object
export function parsePath (str) {
let segments = str.split('. ')
return (obj) = > {
for (let i =0; i< segments.length; i++) {
if(! obj)return;
obj = obj[segments[i]]
}
return obj
}
}
Copy the code
- array.js
// Here are some functions in the array, using decorator mode to add some new functions-trigger data update
import {def} from './utils.js'
// Find the prototype object of Array
const arrayPrototype = Array.prototype
// Create an arrayMethods prototype with array.prototype so that arrayMethods have an array method
export let arrayMethods = Object.create(arrayPrototype)
let methodNeedChange = [
'push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'
]
methodNeedChange.forEach((methodName) = > {
// Back up the original method
const original = arrayMethods[methodName]
// Redefine the function that needs to be changed
def(arrayMethods,methodName,function() {
// Execute the old method first, using apply,
const result = original.apply(this.arguments)
// The __ob__ attribute is bound to each value of this object
let ob = this.__ob__;
let args = [...arguments] // Change the class array to an array
// There are three ways to add content to the array. Change the added content page to observe
// push unshift splice
let inserted = []
switch (methodName) {
case 'push':
case 'unshift':
inserted = args;
break;
case 'splice':
// splice(start subscript, amount, value to add)
inserted = args.slice(2);
break;
}
if (inserted) {
// Change the new entry to observe
ob.observeArray(inserted)
}
console.log('La la la la')
ob.dep.notify()
return result
},false)})Copy the code
- defineReactive.js
import {observe} from './observer'
import Dep from './dep.js'
export default function defineReactive (data,key,val) {
const dep = new Dep()
if (arguments.length == 2) {
val = data[key]
}
let childOb = observe(val)
Object.defineProperty(data,key,{
enumerable: true.// can be enumerated
configurable: true.// Can be set, such as delete
get () {
// If you are in a dependency collection phase
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
}
}
console.log('You tried to access obj's'+key+'properties' + val)
return val
},
set (newVal) {
console.log('You've got it'+key+'value' + newVal)
if (val===newVal) {
return
}
val = newVal
// When a new value is set, continue to add observations
childOb = observe(val)
dep.notify()
}
})
}
Copy the code
- dep.js
let uid =0;
export default class Dep {
constructor() {
console.log('I'm the constructor of the DEP class')
this.id = uid++
// Publish subscriber pattern creates an array of subscribers to place watcher instances in
this.subs = []
}
addSub (sub) {
this.subs.push(sub)
}
// Add dependencies
depend () {
// dep. target is a global location specified
if (Dep.target) {
this.addSub(Dep.target)
}
}
notify () {
console.log('I'm notify')
// create a shallow clone
let subs = this.subs.slice()
/ / traverse
for (let i=0,l=subs.length; i< l; i++) {
subs[i].update()
}
}
}
Copy the code
- watcher.js
import Dep from './dep.js';
import {parsePath} from './utils.js'
let uid = 0
export default class Watcher{
constructor(target, expression, callBack) {
this.id = uid++
this.target = target;
// parsePath is a higher-order function for parsing expressions
this.getter = parsePath(expression)
this.callBack = callBack;
this.value = this.get()
console.log('I'm watcher's builder.')
}
update () {
this.run()
}
get () {
// Enter the dependency collection phase with the global dep. target set to watcher itself
Dep.target = this;
const obj = this.target;
// Keep looking as long as you can
var value
try {
value = this.getter(obj)
} finally {
Dep.target = null
}
return value
}
// Get and evoke
run () {
this.getAndInvoke(this.callBack)
}
getAndInvoke (cb) {
const value = this.get();
if(value ! = =this.value || typeof value == 'object') {
const oldVal = this.value
this.value = value
// Here are the parameters in watcher, new values and old values
cb.call(this.target,value,oldVal)
}
}
}
Copy the code
Finally, to sum up:
- In Observe, add an __ob__ attribute to objects and arrays by traversing them. The value of the attribute is the Observer instance itself. There is also a DEP bound to each instance
- Each value is rendered responsive by object traversal
- Add the object and its properties to be reactive in defineReactive and invoke Observe on the value to make each layer reactive
- When you call new Watcher, you will first call the get method in Watcher, set the object itself to the static property value of dep. target, and then get the current object, the corresponding property value. This triggers get in defineProperty to collect the dependencies, essentially collecting the Watcher instance itself.
- Changing another value in an object in index.js triggers set in defineProperty, triggers notify, and triggers dependencies collected when viewing the object in DEP. Update = newVal; update = newVal; update = newVal; update = newVal