In the last two articles we explored abstract syntax trees and diff algorithms. Today we’ll talk about the principle of responsiveness.
Vue2. X – Principle of data response
Object.defineProperty()
methods
Object.defineProperty()
Method defines a property directly on an object, or modifies an existing property of an object and returns the object.
var obj = {}
Object.defineProperty(obj, 'a', { value: 3 })
Object.defineProperty(obj, 'b', { value: 5 })
console.log(obj)
console.log(obj.a, obj.b)
Copy the code
var obj = {}
Object.defineProperty(obj, 'a', {
// getter
get() {
console.log(Visit a ' ')},// setter
set() {
console.log(Change 'a')}})Object.defineProperty(obj, 'b', { value: 5.enumerable: true })
console.log(obj)
console.log(obj.a++, obj.b)
Copy the code
defineReactive
function
getter/setter
Variable turnover is required to work
var obj = {}
var temp
Object.defineProperty(obj, 'a', {
// getter
get() {
console.log(Visit a ' ')
return temp
},
// setter
set(newValue) {
console.log(Change 'a')
temp = newValue
}
})
Copy the code
defineReactive
function
function defineReactive(data, key, val) {
// Closure environment
Object.defineProperty(data, key, {
/ / can be enumerated
enumerable: true.// Can be configured
configurable: true.// getter
get() {
console.log('access' + key)
return val
},
// setter
set(newValue) {
console.log('change' + key, newValue)
if (val === newValue) return
val = newValue
}
})
}
var obj = {}
defineReactive(obj, 'a'.10)
console.log(obj.a)
obj.a = 60
obj.a += 10
console.log(obj.a)
Copy the code
Recursively detects all attributes of an object
Observer
: Converts a normal object into an object whose properties at each level are responsive (detectable)index.js
import observe from './observe'
var obj = {
a: {
m: {
n: 10}},b: 20
}
observe(obj)
obj.b++
console.log(obj.a.m.n)
Copy the code
observe.js
import Observer from './Observer'
// Create an Observer function
export default function (value) {
// If value is not an object, do nothing
if (typeofvalue ! = ='object') return
/ / define the ob
var ob
if (typeofvalue.__ob__ ! = ='undefined') {
ob = value.__ob__
} else {
ob = new Observer(value)
}
return ob
}
Copy the code
Observer.js
import { def } from './utils'
import defineReactive from './defineReactive'
export default class Observer {
constructor(value) {
(this is not the class itself, but the instance)
// Add an __ob__ attribute with the value being an instance of new this time
def(value, '__ob__'.this.false)
console.log('Observer constructor ', value)
// The purpose of the Observer class is to convert a normal object into an object whose properties at each level are responsive (detectable)
this.walk(value)
}
/ / traverse
walk(value) {
for (const key in value) {
defineReactive(value, key)
}
}
}
Copy the code
defineReactive.js
import observe from './observe'
export default function (data, key, val) {
// Closure environment
if (arguments.length === 2) {
val = data[key]
}
// The child element has to observe, so this is a recursion. Instead of calling itself, multiple functions and classes are called in a loop
let childOb = observe(val)
Object.defineProperty(data, key, {
/ / can be enumerated
enumerable: true.// Can be configured
configurable: true.// getter
get() {
console.log('access' + key)
return val
},
// setter
set(newValue) {
console.log('change' + key, newValue)
if (val === newValue) return
val = newValue
// When a new value is set, the new value must also be observed
childOb = observe(newValue)
}
})
}
Copy the code
utils.js
export const def = function (obj, key, value, enumerable) {
Object.defineProperty(obj, key, {
value,
enumerable,
writable: true.configurable: true})}Copy the code
Reactive processing of arrays
Vue
The bottom layer has been rewrittenArray
7 native methods:['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
- These seven methods are in
Array.prototype
上 index.js
import observe from './observe'
var obj = {
a: {
m: {
n: 10}},b: 20.g: [22.33.55]
}
observe(obj)
obj.g.splice(2.1[Awesome!.888])
obj.g.push(66)
console.log(obj)
// obj.b++
// console.log(obj.a.m.n)
// defineReactive(obj, 'a')
// console.log(obj.a.m.n)
Copy the code
array.js
import { def } from './utils'
/ / get Array. The prototype
const arrayPrototype = Array.prototype
// Create an arrayMethods object with array. prototype
export const arrayMethods = Object.create(arrayPrototype)
// The 7 array methods to be overwritten
const methodsNeedChange = [
'push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'
]
methodsNeedChange.forEach((methodName) = > {
// Back up the original method. The functionality of the 7 methods cannot be stripped
const original = arrayPrototype[methodName]
__ob__ has been added. Why has __ob__ been added? Because arrays are definitely not the highest level
For example, if obj is an array, obj cannot be an array. The first time we traverse the first layer of obj, we have already assigned g to it
// Add an __ob__ attribute
// Define a new method
def(
arrayMethods,
methodName,
function () {
// Restore the original function
const result = original.apply(this.arguments)
const ob = this.__ob__
// Change arguments to arrays
const args = [...arguments]
// There are three methods push/unshift/splice can insert a new item. Now you need to change the inserted item to observe
let inserted
switch (methodName) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
// Splice format is splice(subscript, quantity, new item inserted)
inserted = args.slice(2)
break
}
// Determine if there are any new items to insert, so that the new items also become responsive
if (inserted) {
ob.observeArray(inserted)
}
console.log('lalalala')
return result
},
false)})Copy the code
Observer.js
import { def } from './utils'
import defineReactive from './defineReactive'
import { arrayMethods } from './array'
import observe from './observe'
export default class Observer {
constructor(value) {
(this is not the class itself, but the instance)
// Add an __ob__ attribute with the value being an instance of new this time
def(value, '__ob__'.this.false)
console.log('Observer constructor ', value)
// The purpose of the Observer class is to convert a normal object into an object whose properties at each level are responsive (detectable)
// Check whether it is an array or an object
if (Array.isArray(value)) {
// If it is an array, point the prototype of the array to arrayMethods
Object.setPrototypeOf(value, arrayMethods)
// Make this array observe
this.observeArray(value)
} else {
this.walk(value)
}
}
/ / traverse
walk(value) {
for (const key in value) {
defineReactive(value, key)
}
}
// Special traversal of the array
observeArray(arr) {
for (let i = 0, l = arr.length; i < l; i++) {
/ / observe item by item
observe(arr[i])
}
}
}
Copy the code
Depend on the collection
What is dependency?
- Where data is needed, it is called a dependency
Vue1.x
.fine-grainedDependent, using dataDOMIs dependent onVue2.x
.Medium sizeDependent, using datacomponentIs dependent on- in
getter
Collect dependencies insetter
Trigger dependency in
Dep
Classes andWatcher
类
- Encapsulate the code that depends on the collection into one
Dep
Class, which is dedicated to managing dependencies,eachObserver
Each of the members has an instance ofDep
An instance of the Wacther
Is a mediation through which data changesWacther
Relay, notification component
-
Dependency is Watcher. Only getters triggered by Watcher collect dependencies, and whichever Wacther fires the getter is collected into the Dep.
-
Dep uses a publisk-subscribe model, which loops through the list to notify all watchers when data changes.
-
The code implementation is clever: Watcher sets itself to a specified location globally, then reads the data, and because it reads the data, it fires the getter for that data. In the getter, you get the Watcher that is currently reading, and you collect that Watcher into the Dep
code
index.js
import observe from './observe'
import Watcher from './Watcher'
var obj = {
a: {
m: {
n: 10}},b: 20.g: [22.33.55]
}
observe(obj)
obj.a.m.n = 8
obj.g.splice(2.1[Awesome!.888])
obj.g.push(66)
console.log(obj)
new Watcher(obj, 'a.m.n'.(val) = > {
console.log('* I'm Watcher and I'm monitoring A.M.N. ', val)
})
obj.a.m.n = 8888
Copy the code
Observer.js
import { def } from './utils'
import defineReactive from './defineReactive'
import { arrayMethods } from './array'
import observe from './observe'
import Dep from './Dep'
export default class Observer {
constructor(value) {
// On each Observer instance, there is a DEP
this.dep = new Dep()
(this is not the class itself, but the instance)
// Add an __ob__ attribute with the value being an instance of new this time
def(value, '__ob__'.this.false)
console.log('Observer constructor ', value)
// The purpose of the Observer class is to convert a normal object into an object whose properties at each level are responsive (detectable)
// Check whether it is an array or an object
if (Array.isArray(value)) {
// If it is an array, point the prototype of the array to arrayMethods
Object.setPrototypeOf(value, arrayMethods)
// Make this array observe
this.observeArray(value)
} else {
this.walk(value)
}
}
/ / traverse
walk(value) {
for (const key in value) {
defineReactive(value, key)
}
}
// Special traversal of the array
observeArray(arr) {
for (let i = 0, l = arr.length; i < l; i++) {
/ / observe item by item
observe(arr[i])
}
}
}
Copy the code
defineReactive.js
import observe from './observe'
import Dep from './Dep'
export default function (data, key, val) {
const dep = new Dep()
// Closure environment
if (arguments.length === 2) {
val = data[key]
}
// The child element has to observe, so this is a recursion. Instead of calling itself, multiple functions and classes are called in a loop
let childOb = observe(val)
Object.defineProperty(data, key, {
/ / can be enumerated
enumerable: true.// Can be configured
configurable: true.// getter
get() {
console.log('access' + key)
// If you are in the dependency collection phase
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
}
}
return val
},
// setter
set(newValue) {
console.log('change' + key, newValue)
if (val === newValue) return
val = newValue
// When a new value is set, the new value must also be observed
childOb = observe(newValue)
// Publish and subscribe to notify deP
dep.notify()
}
})
}
Copy the code
Dep.js
var uid = 0
export default class Dep {
constructor() {
console.log('Dep constructor ')
this.id = uid++
// Use an array to store your own subscribers. Subs stands for subscribes
// This array contains instances of Watcher
this.subs = []
}
// Add a subscription
addSub(sub) {
this.subs.push(sub)
}
// Add dependencies
depend() {
// dep. target is a global location that we specify ourselves
if (Dep.target) {
this.addSub(Dep.target)
}
}
// Notification update
notify() {
console.log('I'm notify')
// create a shallow clone
const 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'
var uid = 0
function parsePath(str) {
var segments = str.split('. ')
return (obj) = > {
for (let i = 0; i < segments.length; i++) {
if(! obj)return
obj = obj[segments[i]]
}
return obj
}
}
export default class Watcher {
constructor(target, expression, callback) {
console.log('Watcher constructor ')
this.id = uid++
this.target = target
this.getter = parsePath(expression)
this.callback = callback
this.value = this.get()
}
update() {
this.run()
}
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)
}
}
get() {
// Set the global dep. target to Watcher itself, then you are in the dependency collection phase
Dep.target = this
const obj = this.target
var value
// Keep looking as long as you can
try {
value = this.getter(obj)
} catch (error) {
console.log(error)
} finally {
Dep.target = null
}
return value
}
}
Copy the code
Finally, the source code address is attached
Gitee.com/yokeney/vue…