当前位置:   article > 正文

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

vue2响应式源码

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中,提高代码的可读性。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/248197
推荐阅读
相关标签
  

闽ICP备14008679号