Observer
class Observer{
// Define attributes
value;
dep;
vmCount;
// constructor
constructor(value){
this.value = value
this.dep = new Dep()
this.vmCount = 0
// Mount an __ob__ attribute for value that instantiates an OB object
def(value,'__ob__'.this)
if(Array.isArray(value)){
// To determine whether __proto__ is available, mount Array properties and methods to the value object
if(hasProto){
protoAugment(value,arrayMethods)
}else{
copyAugment(value,arrayMethods,arrayKeys)
}
this.observeArray(value)
}else{
this.walk(value)
}
}
// Iterate over value to observe
walk(obj){
const keys = Object.keys(obj)
for(let i = 0; i < keys.length; i ++){ defineReactive(obj,keys[i]) } }observeArray(items){
for(let i = 0, l = items.length; i < l; i ++){
observe(items[i])
}
}
}
Copy the code
observe
// Determine whether the current object is monitored, otherwise instantiate
export function observe(value,asRootData){
if(! isObject(value) || valueinstanceof VNode){
return
}
let ob
if(hasOwn(value,'__ob__') && value.__ob__ instanceof Observer){
ob = value.__ob__
}else if(shouldObserve && ! isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && ! value._isVue){ ob =new Observer(value)
}
if(asRootData && ob){
ob.vmCount ++
}
return ob
}
Copy the code
defineReactive
export function defineReactive(obj,key,val,customSetter,shallow){
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj,key)
if(property && property.configurable === false) {return
}
const getter = property && property.get
const setter = property && property.set
// Get the key attribute of the default obj
if((! getter || setter) &&arguments.length === 2 ){
val = obj[key]
}
// Get val's Observe
letchildOb = ! shallow && observe(val)// Redefine the read and write of key values
Object.defineProperty(obj,key,{
enumerable: true.configurable: true.get: function reactiveGetter(){
const value = getter ? getter.call(obj) : val
if(Dep.target){
dep.depend()
if(childOb){
childOb.dep.depend()
if(Array.isArray(value)){
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter(newVal){
const value = getter ? getter.call(obj) : val
if(newVal === value || (newVal ! == newVal && value ! == value)){return
}
if(process.env.NODE_ENV ! = ='production' && customSetter){
customSetter()
}
if(getter && ! setter)return
if(setter){
setter.call(obj,newVal)
}else{ val = newVal } childOb = ! shallow && observe(newVal) dep.notify() } }) }Copy the code
Dep
let uid = 0
export default class Dep{
static target;
id;
subs;
constructor(){
this.id = uid ++
this.subs = []
}
addSub(sub){
this.subs.push(sub)
}
removeSub(sub){
remove(this.subs,sub)
}
depend(){
if(Dep.target){
Dep.target.addDep(this)}}notify(){
const subs = this.subs.slice()
if(process.env.NODE_ENV ! = ='production' && !config.async){
subs.sort((a,b) = > a.id - b.id)
}
for(let i = 0, l = subs.length; i < l; i++){
subs[i].update()
}
}
}
Dep.target = null
const targetStatck = []
// Ensure that dep. target is always the latest one
function pushTarget(target){
targetStack.push(target)
Dep.target = target
}
function popTarget(){
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]}Copy the code
Watcher
class Watcher{
vm,expression,cb,id,deep,user,lazy,sync,dirty,active,deps,newDeps,depIds,newDepIds,before,getter,value;
constructor(vm,expOrFn,cb,options,isRenderWatcher){
this.vm = vm
// Mount _watchers to vm
if(isRenderWatcher){
vm._watcher = this
}
vm._watchers.push(this)
if(options){
this.deep = !! options.deepthis.user = !! options.userthis.lazy = !! options.lazythis.sync = !! options.syncthis.before = options.before
}else{
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid
this.active = true
this.dirty = this.lazy
this.deps = []
this.newDeps = []
this.depIds = new Set(a)this.newDepIds = new Set(a)this.expression = process.env.NODE_ENV ! = ='production' ? expOrFn.toString() : ' '
if(typeof expOrFn === 'function') {this.getter = expOrFn
}else{
this.getter = parsePath(expOrFn)
}
this.value = this.lazy ? undefined : this.get()
}
get(){
// Set dep. target and addDep when reading data
pushTarget(this)
let value
const vm = this.vm
try{
value = this.getter.call(vm,vm)
}catch(e){
...
}finally{
if(this.deep){
traverse(value)
}
// Dep. Target is cleared and cleanupDeps is executed
popTarget()
this.cleanupDeps()
}
return value
}
addDep(dep){
// Save to deP and save to Watcher
const id = dep.id
if(!this.newDepIds.has(id)){
this.newDepIds.add(id)
this.newDeps.push(dep)
if(!this.depIds.has(id)){
dep.addSub(this)}}}cleanupDeps(){
// If the backup deP does not have the current DEP, delete the current DEP watcher permanently
let i = this.deps.length
while(i --){
const dep = this.deps[i]
if(!this.newDepIds.has(dep.id)){
dep.removeSub(this)}}// Set depIds and deps as backups of newDepIds and newDeps
let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
tmp = this.deps
this.deps = this.newDeps
this.newDeps = tmp
this.newDeps.length = 0
}
update(){
// Determines whether the run-watch callback is executed immediately or the updated callback is executed
if(this.lazy){
this.dirty = true
}else if(this.sync){
this.run()
}else{
queueWatcher(this)}}run(){
// Get newValue and call back
if(this.active){
const value = this.get()
if(value ! = =this.value || isObject(value) || this.deep){
const oldValue = this.value
this.value = value
if(this.user){
try{
this.cb.call(this.vm,value,oldValue)
}catch(e){
handleError(e,this.vm,`callback for watcher "The ${this.expression}"`)}}else{
this.cb.call(this.vm,value,oldValue)
}
}
}
}
evalute(){
this.value = this.get()
this.dirty = false
}
depend(){
let i = this.deps.length
while(i --){
this.deps[i].depend()
}
}
/ / remove the watcher
teardown(){
if(this.active){
if(!this.vm._isBeingDestroyed){
remove(this.vm._watchers,this)}let i = this.deps.length
while(i --){
this.deps[i].removeSub(this)}this.active = false}}}Copy the code
Summary – Bidirectional binding
- DefineReactive — Get and set are redefined with Object.defineProperty
- Getter: Determines the get of the original property and adds watcher via deP
- Setter: Judge setter, call watcher’s update via deP, call watch callback or update hook function; Also set childOb/Observer according to newVal, updated at GET