• vue2源码学习(3)-响应式原理(二)


    observer

    /src/core/observer/index.js

    将一个正常的object转换为每个层级的属性都是响应式(可以被侦测的)的object;

    // Observe.js
    import defineReactive from './defineReactive.js
    import arrayMethods  from './rewriteArray.js'
    class Observer{
    	constructor(value){
    		def(value,'__ob__',this,flase).  //给value对象绑定ob属性,且不可枚举
    		if(Array.isArray(value)){.  //数组对象的处理
    		 value.__proto__ = arrayMethods;  //修改数组的隐式原型对象为更改后的数组原型对象
    		 
    
    
    		}else{
    			const keys = Object.keys(value);
                for (let i = 0; i < keys.length; i++) {
                   const key = keys[i];
                   defineReactive(value, key);
                }
    		}
    	}
    	 observeArray(value) {
            for (let i = 0, l = value.length; i < l; i++) {
                observe(value[i], false);
            }
        }
    }
    
    var obj={
    	a:{
    		m:{
    			n:5
           }
    	},
    	b:[1,2,3] 
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    observe方法是最外层级的,上来先看observe(obj). —>>> obj身上有没有 __obj__ —>>> new Observer() ---->>> 遍历下一层属性,逐个defineReactive

    defineReactive方法

    /src/core/observer/index.js

    //defineReactive.js文件
    import observe from './observe.js
    export default function defineReactive(data,key,val){
    	if(arguments.length===2){
    		val=data[key]
    	}
    	//递归调用子元素,至此形成了递归,这个递归不是函数自己调用自己,而是多个函数,类循环调用;
    	let childOb = observe(val)  //返回val的ob对象
    	Object.defineProperty(data,key,{
    		//可枚举
    		enumrable:true,
    		//可被配置,比如可以被删除delete
    		configurable:true,
    		//getter
    		get(){
    			console.log('你试图访问obj的'+key+ '属性')
    			return val
    		},
    		set(newValue){
    			console.log('你试图改变obj的'+key+'属性‘,newValue)
    			if(val===newValue){ return }
    			val=newValue
    			//当设置了新值,这个新值也要被observer
    			childOb=observe(newValue)
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    //index.js
    import defineReactive from './defineReactive'
    
    
    
    • 1
    • 2
    • 3
    • 4

    observe方法

    // 创建observe函数,注意函数的名字没有r,value其实是obj[key]
    function observe(value){
    	if(typeof value != 'object') return;
    	//定义ob
    	var ob;
    	if(typeof value.__ob__ !== 'undefined'){
    		ob = value.__ob__;
    	}else {
    		ob = new Observer(value)
      }
      return ob
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述
    在这里插入图片描述
    1.obj对象会先通过observe处理,看obj身上有没有__ob__属性;
    2.没有则在Observe构造器中给obj对象添加__ob__属性; (精:def (obj,__ob__属性,this),这里的this指向的是实例化出来的observe对象,然后赋值给__ob__属性)
    3.添加__ob__属性需要调用一个def方法:原因:__ob__属性不可枚举,所以单独使用一个def方法;
    4.在Obsever类的构造器中遍历obj每一个属性,调用definReactive方法,进行数据劫持;
    5.在遍历中如果该obj[key]是一个object对象,

    数组响应式实现

    src/core/observer/array.js

    
    // rewriteArray.js
    //得到Array
    const arrayProto = Array.prototype;
    const arrayMethods = Object.create(arrayProto); //将数组的原型对象绑定在一个对象的原型上
    //对数组的7个方法进行改造
    const methodsToPatch = [
        'push',
        'pop',
        'shift',
        'unshift',
        'splice',
        'sort',
        'reverse'
    ];
    //重写方法
    methodsToPatch.forEach(function (method) {
        // cache original method
        const original = arrayProto[method];
        // mutator是给数组方法重新定义的一个方法 def(obj,key,value)
        def(arrayMethods, method, function mutator(...args) {
            const result = original.apply(this, args);  //this是当前调用数组方法的对象,args是传进来的参数
            const ob = this.__ob__;
            let inserted;   // 给数组对象添加的元素
            switch (method) {
                case 'push':
                case 'unshift':
                    inserted = args;  
                    break;
                case 'splice':
                    inserted = args.slice(2);
                    break;
            }
            if (inserted)
                //给添加的所有元素绑定响应式
                ob.observeArray(inserted);
     
            
            return result;
        });
    });
    export arrayMethods
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    def

    /src/core/util/lang.js

    export function def(obj: Object,key: string, val: any, enumerable?: boolean){
    	Object.definepProperty(obj,key,{
    	   value: val,
    	   enumerable: !!enumberable,
    	   writable: true,
    	   configurable: true,
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    protoAugment

    /src/core/observer/index.js

    /**
      *设置 target._proto_的原型对象为src
      *比如数组对象,arr.__proto__arrayMethods
    */
    function protoAugment(target,src: Object){
    	target.__proto__ = src
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    copyAugment

    /src/core/observer/index.js

    /**
      *在目标对象上定义指定属性
      *比如数组:为数组对象定义那七个方法
    */
    function copyAugment(target: Object, src:Object, keys: Array<string>){
    	for(let i=0; i=keys.length; i<l; i++){
    		const key = keys[i]
    		def(target, key, src[key])
    	}
    }	
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    Dep

    /src/core/observer/dep.js

    import type Watcher from './watcher'
    import {remove} from '../util/index'
    import {config} from '../config'
    
    let uid=0
    /**
      *一个dep对应一个obj.key
      *在读取响应式数据时,负责收集依赖,每个dep(或者说obj.key)依赖的watcher有哪些
      *在响应式数据更新时,负责通知dep中哪些watcher去执行update方法
    */
    export default class Dep {
    	static target: ?Watcher;
    	id: number;
    	subs: Array<Watcher>
    }
    	construcotr(){
    		this.id=uid++
    		this.subs=[]
    	}
      //在dep中添加watcher
      addSub(sub: Watcher){
    	this.subs.push(sub)
    }
    	removeSub(sub:Watcher){
    	remove(this.subs,sub}
    }
    	//向watcher中添加dep
    	depend(){
    		if(Dep.target){
    			Dep.target.addDep(this)
    		}
    	}
    /**
      *通知dep中所有watcher,执行watcher.update()方法
    */
    	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()
    	}
    }
    /**
      *当前正在执行的watcher,同一时间只会有一个watcher执行
      *Dep.target = 当前正在执行的watcher
      *通过调用pushTarget方法完成赋值,调用popTarget方法完成重置(null)
    */
    Dep.target = null
    const targetStack = []
    //在需要进行依赖收集的时候调用,设置Dep.target=watcher
    export function pushTarget (target: ?Watcher){
    	targetStack.push(target)
    	Dep.target = target
    }
    //依赖收集结束调用,设置Dep.target = null
    export function popTarget(){
    	targetStack.pop()
    	Dep.target = targetStack[targetStack.length-1]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    Watcher

    /src/core/observer/watcher.js

    /**
     * 一个组件一个watcher(渲染watcher)或者一个表达式watcher(用户watcher)
     * 当数据更新时watcher会被触发,访问this.computedProperty时也会触发watcher
    */
    export function class Watcher {
    	vm: Component;
    	expression: string;
    	cb: Function;
    	id: number;
    	deep: boolean;
    	user: boolean;
    	lazy: boolean;
    	sync: boolean;
    	dirty: boolean;
    	acive: boolean;
    	deps: Array<Dep>;
    	newDeps: Array<Dep>;
    	depIds: SimpleSet;
    	newDepIds: SimpleSet;
    	before: ?Function;
    	getter: Function;
    	value: any;
    	
    	constructor(
    		vm: Component,
    		exOrFn: string | Function,
    		cb: Function,
    		options? : ?Object,
    		isRenderWatcher? : boolean
    	){
    		this.vm = vm
    		if(isRenderWatcher){
    			vm._watcher = this
    		}
    		vm._watchers.push(this)
    		if(options){
    			this.deep = !!options.deep
    			this.user = !!options.user
    			this.lazy = !!options.lazy
    			this.sync = !!options.sync
    			this.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()
    		this.newDepIds = new Set()
    		this.expression = process.env.NODE_ENV !== 'production'
    			? expOrFn,toString()
    			: ''
    		if(typeof expOrFn === 'function'){
    			this.getter = expOrfn
    		}else {
    			// this.getter = function(){ return this.xx}
    			//在this.get中执行this.getter 时会触发依赖收集
    			//待后续this.xx 更新时就会触发响应式
    			this.getter = parsePath(exOrfn)
    			if(!this.getter){
    				this.getter = noop
    				process.env.NODE_ENV !== 'production' && warn$2("Failed watching path: \"".concat(expOrFn, "\" ") +
                              'Watcher only accepts simple dot-delimited paths. ' +
                              'For full control, use a function instead.', vm);
    			}
    		}
    		this.value = this.lazy
    			? undefined
    			: this.get()
    	}
    /**
     * 执行this.getter,并重新收集依赖
     * this.getter 是实例化watcher时传递的第二个参数,一个函数或者字符串,比如updateComponent或者parsePath返回的读取this.xx属性值的函数
     * 为什么要重新收集依赖?
     * 	因为触发更新说明有响应式数据被更新了,但是被更新的数据虽然已经经过observe观察了,但是却没有进行依赖收集,
     * 所以,在更新页面时,会重新执行一次render函数,执行期间会触发读取操作,这时候进行依赖收集
      */
      get() {
    	//打开Dep.target, Dep.target = this
    	pushTarget(this)
    	//value为回掉函数执行的结果
    	let value
    	const vm = this.vm
    	try{
    		//执行回掉函数,比如updateComponent ,进入patch阶段
    		value = this.getter.call(vm,vm)
    	}catch(e){
    		if(this.user){
    			handleError(e,vm,`getter for watcher "${this.expression}"`}
    		}else {
    			throw e
    		}
    	}finally{
    		if(this.deep){
    			traverse(value)
    		}
    		//关闭Dep.target, Dep.target=null
    		popTarget()
    		this.cleanupDeps()
    	}
    	return value
    	}
    	/**
    	  * Add a dependency to this directive
    	  * 1.添加dep给自己(watcher)
    	  * 2.添加自己(watcher)到dep
    	*/
    	addDep(dep: Dep){
    		//判重,如果dep已经存在则不重复添加
    		const id = dep.id
    		if(!this.newDepIds.has(id){
    		//缓存dep.id,用于判重
    		this.newDepIds.add(id)
    		//添加
    		this.newDeps.push(dep)
    		// 避免在dep中重复添加watcher,this.depIds 的设置在cleanDeps 方法中
    		if(!this.depIds.has(id){
    			//添加watcher自己到dep
    			dep.addSub(this)
    		}
    		}
    	}
    /**
     * Clean up for dependency collection
    */
    	cleanupDeps (){
    		let i = this.deps.length;
    		while(i--){
    			const dep = this.deps[i]
    			if(!this.newDepsIds.has(dep.id)){
    				dep.removeSub(this)
    			}
    		}
    		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 = {}
    	}
    /**
      *根据watcher配置项,决定接下来怎么走,一般是queueWatcher
    */
    	update(){
    		if(this.lazy){
    			//懒执行走这里,比如computed
    			//将dirty 置为true,可以让computedGetter执行重新计算computed回掉函数的执行结果
    			this.dirty = true
    		}else if(this.sync){
    			//同步执行,在使用vm.$watch或者watch选项时可以传一个sync选项
    			//当为true时在数据更新时该watcher就不走异步更新队列,直接执行this.run
    			//方法进行更新
    			//这个属性在官方文档中没有出现
    			this.run()
    		}else{
    			//更新时一般走这里,将watcher放入wacher队列
    			queueWatcher(this)
    		}
    	}
    /**
     * 由刷新队列函数flushSchedulerQueue调用,完成以下几件事:
     * 1.执行实例化watcher传递的第二个参数,updateComponent或者获取this.xx的的一个函数(parsePath 返回的函数)
     * 2.更新旧值为新值
     * 3.执行实例化watcher时传递的第三个参数,比如用户watcher的回掉函数
    */
    	run(){
    		if(this.active){
    			//调用this.get方法
    			const value = this.get()
    			if(
    			  value !== this.value ||
    			  isObject(value) || 
    			  this.deep
    			){
    				// 更新旧值为新值
    				const oldValue = this.value
    				this.value = value
    				if(this.user){
    				//如果是用户watcher,则执行用户传递的第三个参数 ---回掉函数,参数为val和oldVal
    					try{
    						this.cb.call(this.vm,value,oldValue)
    					}catch(e){
    						handleError(e,this.vm,`callback for watcher "${this.expression}")
    					}
    				}else{
    					//渲染watcher,this.cb=noop,一个空函数
    					this.cb.call(this.vm,value,oldValue)
    				}
    			}
    		}
    	}
    /**
     * 懒执行watcher 会调用该方法
     *   比如:computed,在获取vm.computedProperty 的值时会调用该方法
     * 然后执行this.get,即watcher的回调函数,得到返回值
     * this.dirty被置为fales,作用是页面在本次渲染只会一次执行computed.key的回掉函数,
     * 这也就是大家常说的computed和methods区别之一就是computed有缓存的原理所在
     * 而页面更新后会this.dirty会重新置为true,这一步是在this.update方法中完成的
    */
    	 evaluate(){
    		this.value = this.get()
    		this.dirty = false
    	}
    /**
     * Depend on all deps collected by this watcher
    */
    	depend(){
    		let i = this.deps.length;
    		while(i--){
    			this.deps[i].depend()
    		}
    	}
    /**
     * Remove self form all dependencies subscriber list
    */
    	teardown(){
    		if(this.active){
    			// remove self from vm's watcher list
    			// this is a somewhat expensive operation so we skip it
    			// if the vm is being destroyed
    			if(!this.vm._isBeingDestoryed){
    				remove(this.vm._watcher,this)
    			}
    			let i = this.deps.length
    			while(i--){
    				this.deps[i].removeSub(this)
    			}
    			this.active = false
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236

    methods、computed、watch比较

    使用场景:

    • methods一般用于封装一些较为复杂的处理逻辑(同步、异步)
    • computed 一般用于封装一些简单的同步逻辑,将浸过处理的数据返回,然后显示在模版中,以减轻模版的重量
    • watch一般用于当需要在数据变化时执行异步或开销较大的操作

    区别:

    • methods VS computed
      通过示例发现,如果在一次渲染中,由多个地方使用了同一个methods或computed属性,methods会被执行多次,而computed的回掉函数则会被执行一次;
      通过阅读源码我们知道,在一次渲染中,多次访问computedProperty,只会在第一次执行computed属性的回掉函数,后续的其它访问,则直接使用第一次的执行结果(watcher.value),而这一切的实现原理是通过watcher.dirty属性的控制实现的。而methods,每一次的访问则是简单的方法调用(this.xxMethods).
    • computed VS watch
      通过阅读源码知道,computed和watch本质上是一样的,内部都是通过Watcher来实现的,其实没什么区别,非要说区别的话就两点:1.使用场景上的区别 2.computed默认是懒执行,切不可更改。
    • methods VS watch
      methods 和 watch之间其实没什么可比的,完全是两个东西,不过在使用上可以把watch中的一些逻辑抽到methods中,提高代码的可读性。
  • 相关阅读:
    FluentCRM 2.5 – 大量新功能,自动化你的业务!
    Unity 内存性能分析器 (Memory Profiler)
    2022年上半年信息系统项目管理师下午案例分析真题及答案解析
    Windows Server 2022 安全功能重大更新
    java计算机毕业设计-英杰学堂网上教学平台-源程序+mysql+系统+lw文档+远程调试
    Scala中使用 break 和 continue
    VUE项目中调用高德地图
    Linux 程序打包
    windbg查看模块中的方法时报错no code found
    认识电磁干扰?|深圳比创达EMC
  • 原文地址:https://blog.csdn.net/weixin_44374938/article/details/127379596