vue2源码学习-响应式原理

Posted ~往无前

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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] 


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)
		
	

//index.js
import defineReactive from './defineReactive'


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.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

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,
	

protoAugment

/src/core/observer/index.js

/**
  *设置 target._proto_的原型对象为src
  *比如数组对象,arr.__proto__arrayMethods
*/
function protoAugment(target,src: Object)
	target.__proto__ = src

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])
	
	

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]

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,在获取

以上是关于vue2源码学习-响应式原理的主要内容,如果未能解决你的问题,请参考以下文章

vue2源码学习-响应式原理

vue2源码学习-响应式原理

深入 Vue3 源码,学习响应式原理

深入 Vue3 源码,学习响应式原理

深入 Vue3 源码,学习响应式原理

深入 Vue3 源码,学习响应式原理