vue的响应式与源码解读

导读

vue面试中经常会问到响应式以及计算属性和watch侦测的区别等这些知识点,下面就可以从简单到难来讲一下vue的响应式是怎么工作的?本文参考Observer、Dep、Watcher 傻傻搞不清楚

一般回答vue响应式肯定会说在vue2.x中使用Object.defineProperty来进行数据劫持,当数据发生改变的时候,会调用setter,然后通知相依赖的watcher,最后更新视图?那这里就会有几个问题延伸,首先,Object.defineProperty是不能监测到数组的变化的?vue2.x有没有什么办法去解决?vue3.x是怎么进行数据拦截的,为什么这样可以进行拦截?watcher是什么?让你自己去实现一个观察者模式可以实现吗?是不是能问的问题很多?而这些也是我所遇见的~~

在vue源码中实际上在new Vue的时候会初始化vue组件中的data以及其他配置

// src/core/instance/index.js
	function Vue (options) {
	    // ...
	    this._init(options)
	}
	// src/core/instance/init.js
	Vue.prototype._init = function (options?: Object) {
	    // 合并配置
	    // ...
	    // 一系列初始化
	    // 比较重要
	    initState(vm)
	    if (vm.$options.el) {
	      vm.$mount(vm.$options.el)
	    }
	}

initState(vm)

// src/core/instance/state.js
	export function initState (vm: Component) {
	    vm._watchers = []  // _watchers 
	    const opts = vm.$options // 
	    if (opts.props) initProps(vm, opts.props) // 初始化 props
	    // ...
	    if (opts.data) {
	    	// 初始化 data, 重要
	        initData(vm) 
	    } else {
	        observe(vm._data = {}, true /* asRootData */)
	    }
	    // ...
	}

这里是一些初始化操作,比如初始化data,options,computed以及其他一些属性,在initData(vm) 中实际上实现了观察者模式(发布订阅),那么实际怎么做的呢?initData(vm)实际上说白了就是初始化一个dep,这个后面讲,然后利用Object.defineProperty创建setter以及getter

// src/core/observer/index.js
// ...
	const dep = new Dep()
	// Dep类 
	Object.defineProperty(obj, key, {
	    enumerable: true,
	    configurable: true,
	    get: function reactiveGetter () {
	        // ...
	        dep.depend()
	        //...
	    },
	    set: function reactiveSetter (newVal) {
	        // ...
	        dep.notify()
	    },
	    ...
	}

dep其实里面有一个subs用来存储依赖,所以说dep是一个依赖管理者

	export default class Dep {
	    constructor () {
	        this.id = uid++
	        this.subs = []
	    }
	
	    addSub (sub: Watcher) {}
	
	    removeSub (sub: Watcher) {}
	
	    depend () {}
	    
	    notify () {}
	}

到了这里其实响应式数据和依赖管理器都已经准备好了,还需要一个什么呢?还需要一个订阅者,也就是watcher,watcher什么时候初始化呢?当执行到挂载阶段的时候初始化watcher,进行求值,求值时会调用getter,执行dep.depend(), 将相关依赖添加到dep中(这里注意 每一个Observer实例都会有一个dep) 数据发生变化,数据拦截器调用setter得通知订阅者dep.notify()

// src/core/observer/watcher.js
	export default class Watcher {
	    // 对 getter 求值,进行依赖收集
	    get () {}
	    // 触发更新
	    update() {}
	}

所以总结下来就是:

  1. 初始化data时首先创建Observer实例,为每一个实例都新建一个Dep类以及将数据属性转化成setter以及getter;
  2. 中间的Dep类实际上是存储相关依赖的,也就是watcher;
  3. watcher初始化时会进行求值,求值会触发getter;收集依赖;

但是在vue官网里,Object.defineProperty是无法监测到数组改变的,比如官网例子是这样的:

	var vm = new Vue({
	  data: {
	    items: ['a', 'b', 'c']
	  }
	})
	vm.items[1] = 'x' // 不是响应性的
	vm.items.length = 2 // 不是响应性的

在官网里是用Vue.set()以及vm.items.splice()解决的:

Vue.set(vm.items, indexOfItem, newValue) // vm.$set
vm.items.splice(indexOfItem, 1, newValue)

这两种办法可以触发响应式系统。(面试里问过)

vue3的proxy代理以及reflect反射

在vue3里,数据拦截它换成代理方法,同一时间也出现了反射方法。

	function ProxyWatcher(data,queue){
		return new Proxy(data,{
			get:(target,key)=>{
				value=target[key]
				return value
			},
			set:(target,key,value)=>{
				target[key]=value;
				queue[key].notify(value)
			}
		})
	}

调用new Proxy()可创建代替其他目标(target)对象的代理,能够拦截并改变js引擎的底层操作,拦截行为使用了一个能够响应特定操作的函数(被称为陷阱)而reflect对象所代表的反射接口,是给底层提供默认行为的方法的集合。为什么代理可以用作数据拦截呢?就是因为每个陷阱函数都允许重写js的内置行为。

后记

写一个简单的发布订阅叭(面试遇到)

	function Subject(){
		this.observers=[];
		this.attach=function(callback){
			this.observers.push(callback);
		}
		this.notify=function(value){
			this.observers.forEach((func)=>{
				func(value)
			})
		}
	}
	function Watcher(data,queue){
		for(let key in data){
			Object.defineProperty(data,key,{
				let value=data[key];
				enumerable:'',
				get:()=>{},
				set:(newValue)=>{
					value=newValue;
					queue[key].notify(value)
				}
			})
		}
	}
	function Observer(queue,key,callback){
		queue[key].attach(callback);
	}
	
	const Data={value:''}
	const messageQueue={}
	for(let key in Data){
		messageQueue[key]=new Subject();
	}
	Observer(messageQueue,"value",(value)=>{
		console.log("value",value)
	})
	
	let myData=Watcher(Data,messageQueue)
	myData.value=''
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值