Vue 数据绑定解析

1,124 阅读4分钟

vue 对对象的数据绑定

Vue.js之所有能实现数据的双向绑定,最核心也是最基础的是Object.defineProperty的使用。

那么 new Vue() 之后,option.data 的数据具体是如何绑定到 Vue,又如何进行数据双向绑定的呢?

new Vue() -> 
this._init() -> 
initState(vm) -> 
observe(vm._data = {}, true) -> 
new Observer(value) -> 
this.observeArray(value),this.walk(value) ->
defineReactive() -> 
Object.defineProperty

官网流程图: 解析流程图:

第一步:初始化Vue

this._init()

第二步:初始化相关的State

initState(vm)

第三步:有data参数就执行initData,执行observe方法。没有data,给一个默认的{}值。

observe(vm._data = {}, true)

第四步:把data变成可观察者对象

new Observer(value) 

第五步:新建一个Dep对象,用来收集依赖。数组执行observerArray方法。JSON执行walk方法

this.observeArray(value),this.walk(value)

第六步:全局的target参数,值为Vue初始化的watcher对象。

defineReactive(),Object.defineProperty

总结:

Vue首先会调用所有使用的数据,从而触发所有的 getter 函数,进而通过Dep对象收集所有响应式依赖,调用所有Watcher执行Render 操作,其中会进行虚拟Dom的存储和比较,进而渲染页面。

当有数据变更时会触发 setter 函数,触发dep.notify(),进而调用Watcher的update,推入Vue的异步观察队列中,最终调用Watch的getter或者cb方法进而调用vm._update(),再调用vm__patch__方法进行虚拟DOM的diff,并最终渲染到页面。

vue对数组的操作

Object.definePropert只能把对象属性改为getter/setter,而对于数组的方法就无能为力了,其内部巧妙的使用了数组的属性来实现了数据的双向绑定。

  • copy原生对象
  • 对数组的push、pop、shift、unshift、splice、sort、reverse方法重写
  • 对于push、unshift、splice操作会推入新的数据操作,执行observerArray方法把数据变成可观察者对象
  • 通知watcher更新视图

总结

Vue的数据双向绑定有3大核心:Observer(观察响应式),Dep(Dependency[收集]),Watcher(订阅者)。

  • 首先来实现dep,dep主要负责依赖的收集,get时触发收集,set时通知watcher通信:
class Dep{
	constructor () {
		// 存放所有的监听watcher
    	this.subs = []
  	}

  	//添加一个观察者对象
  	addSub (Watcher) {
    	this.subs.push(Watcher)
  	}

  	//依赖收集
	depend () {
		//Dep.target 作用只有需要的才会收集依赖
	    if (Dep.target) {
	      Dep.target.addDep(this)
	    }
	}

	// 调用依赖收集的Watcher更新
    notify () {
	    const subs = this.subs.slice()
	    for (let i = 0, l = subs.length; i < l; i++) {
	      subs[i].update()
	    }
  	}
}
// 为Dep.target 赋值
function pushTarget (Watcher) {
  Dep.target = Watcher
}
  • 第二步实现Watcher,Watcher负责数据变更之后调用Vue的diff进行视图的更新:
class Watcher{
	constructor(vm,expOrFn,cb,options){
		//传进来的对象 例如Vue
		this.vm = vm
		//在Vue中cb是更新视图的核心,调用diff并更新视图的过程
		this.cb = cb
		//收集Deps,用于移除监听
		this.newDeps = []
		this.getter = expOrFn
		//设置Dep.target的值,依赖收集时的watcher对象
		this.value =this.get()
	}

	get(){
		//设置Dep.target值,用以依赖收集
	    pushTarget(this)
	    const vm = this.vm
	    let value = this.getter.call(vm, vm)
	    return value
	}

	//添加依赖
  	addDep (dep) {
  		// 这里简单处理,在Vue中做了重复筛选,即依赖只收集一次,不重复收集依赖
	    this.newDeps.push(dep)
	    dep.addSub(this)
  	}

  	//更新
  	update () {
	    this.run()
	}

	//更新视图
	run(){
		//这里只做简单的console.log 处理,在Vue中会调用diff过程从而更新视图
		console.log(`这里会去执行Vue的diff相关方法,进而更新数据`)
	}
}
  • 第三步实现Observer,Observer负责数据的双向绑定,并把对象属性改为getter/setter
//获得arrayMethods对象上所有属性的数组
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)

class Observer{
	constructor (value) {
	    this.value = value
	    // 增加dep属性(处理数组时可以直接调用)
	    this.dep = new Dep()
	    //将Observer实例绑定到data的__ob__属性上面去,后期如果oberve时直接使用,不需要从新Observer,
	    //处理数组是也可直接获取Observer对象
	    def(value, '__ob__', this)
	    if (Array.isArray(value)) {
	    	//处理数组
	        const augment = value.__proto__ ? protoAugment : copyAugment  
	        //此处的 arrayMethods 就是上面使用Object.defineProperty处理过
	        augment(value, arrayMethods, arrayKeys)
	        // 循环遍历数组children进行oberve
	        this.observeArray(value)
	    } else {
	    	//处理对象
	      	this.walk(value)
	    }
	}

	walk (obj) {
    	const keys = Object.keys(obj)
    	for (let i = 0; i < keys.length; i++) {
    		//此处我做了拦截处理,防止死循环,Vue中在oberve函数中进行的处理
    		if(keys[i]=='__ob__') return;
      		defineReactive(obj, keys[i], obj[keys[i]])
    	}
  	}

	observeArray (items) {
    	for (let i = 0, l = items.length; i < l; i++) {
	      observe(items[i])
    	}
  	}
}
//数据重复Observer
function observe(value){
	if(typeof(value) != 'object' ) return;
	let ob = new Observer(value)
  	return ob;
}
// 把对象属性改为getter/setter,并收集依赖
function defineReactive (obj,key,val) {
  	const dep = new Dep()
  	//处理children
  	let childOb = observe(val)
  	Object.defineProperty(obj, key, {
    	enumerable: true,
    	configurable: true,
    	get: function reactiveGetter () {
    		console.log(`调用get获取值,值为${val}`)
      		const value = val
      		if (Dep.target) {
	        	dep.depend()
		        if (childOb) {
		          	childOb.dep.depend()
		        }
		        //此处是对Array数据类型的依赖收集
		        if (Array.isArray(value)) {
		          	dependArray(value)
		        }
	      	}
      		return value
	    },
	    set: function reactiveSetter (newVal) {
	    	console.log(`调用了set,值为${newVal}`)
	      	const value = val
	       	val = newVal
	       	//对新值进行observe
	      	childOb = observe(newVal)
	      	//通知dep调用,循环调用手机的Watcher依赖,进行视图的更新
	      	dep.notify()
	    }
  })
}

//辅助方法
function def (obj, key, val) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: true,
    writable: true,
    configurable: true
  })
}

//重新赋值Array的__proto__属性
function protoAugment (target,src) {
  target.__proto__ = src
}
//不支持__proto__的直接修改相关属性方法
function copyAugment (target, src, keys) {
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i]
    def(target, key, src[key])
  }
}
//收集数组的依赖
function dependArray (value) {
  for (let e, i = 0, l = value.length; i < l; i++) {
    e = value[i]
    e && e.__ob__ && e.__ob__.dep.depend()
    if (Array.isArray(e)) {
    	//循环遍历chindren进行依赖收集
        dependArray(e)
    }
  }
}