Vue 响应式原理的实现

256 阅读1分钟

知识点:

1. Vue2的数据响应式原理(defineProperty)

2. Vue3的数据响应式原理(Proxy)

Vue2的响应式主要是基于defineProperty实现的
defineProperty其实并不是核心的为一个对象做数据的双向绑定,而是去给对象的属性做描述符,只不过属性描述符里的get和set实现了响应式
属性描述符如图所示: 标签图 defineProperty简单的使用示例1:

 var obj1 = {
     a:1,
     b:2
 }
 Object.defineProperty(obj1,'a',{
    writable:false
 })
 console.log(Object.getOwnPropertyDescriptor(obj1,'a'))//方法返回指定对象上一个自有属性对应的属性描述符

以上代码在控制台打印结果如图所示: 打印结果图1
简单的使用示例2:

 var obj1 = {
     a:1,
     b:2
 }
 var _value = obj1.a;
 Object.defineProperty(obj1,'a',{
    get:function(){
        console.log('a is be get')
        return _value
    },set:function(newVal){
      console.log('a is be set')
        _value = newVal
        return _value
    }
 })

以上代码在控制台打印结果如图所示: 打印结果图2 由此可见,从obj1中取a的时候会触发get方法,给obj1.a重新设值的时候会触发set方法,且get和set里必须要有返回值,返回什么值取到的和设置的就是什么值,这种方式需要借助设值外部变量来进行中转,不友好

简单的使用示例3(手写一个简约版的vue):

test.js中:

function vue(){
	 this.$data = {
        a:1
    }
    this.el = document.getElementById('app')
    this.virturaldom = ''
    this.observer(this.$data)
    this.render()
}

vue.prototype.observer = function(obj){//在vue源码中是通过defineReactive?1方法实现的监听get和set
	var value;
	var self = this;
	for(var key in obj){
		value = obj[key]
		if(typeof value=='object'){
			self.observer()
		}else{
			Object.defineProperty(obj,key,{
				get:function(){//进行数据依赖,优点是:避免更新不相干组件的视图
					return value
				},
				set:function(newVal){
					value = newVal
					self.render()
				}
			})
		}
	}
}
vue.prototype.render = function(){
    this.virturaldom = 'i am'+this.$data.a
    this.el.innerHTML =  this.virturaldom
}

index.html中

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
		
	</head>
	<body>
		<div id='app'></div>
		<script src="./test.js"></script>
		<script>
			var vm = new vue()
			setTimeout(function(){
				console.log(vm.$data.a)
				vm.$data.a = '新值'
			},2000)
		</script>
		
	</body>
</html>

页面结果: 一开始是 i am1,2s后变为i am新值

问题来了:defineProperty定义的get和set是对象的属性,那么数组怎么办?

对数组的处理其实就是做了一个装饰者模式,原理如下:
var arrayPro = Array.prototype;//先取数组的原型
var arrCopyPro = Object.create(arrayPro)//把取到的原型拷贝一份给arrCopyPro,防止污染原型
var arr = ['push','pop','shift'];//存储要被装饰的方法
arr.forEach((method,index)=>{
    arrCopyPro[method] = function(){//在拷贝的原型链对象上重写方法
        var ret = arrayPro[method].apply(this,arguments)//调用原本的方法
        dep.notyfy();//触发视图更新
    }
}
最后要把数组的prototype替换成新的prototype(arrCopyPro)

Vue3的响应式主要是基于Proxy实现的
Proxy对象用于定义基本操作的自定义行为 (和defineProperty类似,功能几乎一样,用法上有不同)
使用proxy的好处:
1.defineProperty只能监听某个属性,不能监听全对象
2.不会污染原对象
3.不用去做for循环,节省性能,提高效率
4.不用借助变量中转
5.可以监听数组,不用单独的去对数组进行特异性操作

Proxy简单的使用示例1:

var person = {// 源对象
       	name:'张三',
       	age:12
       }
var p = new Proxy(person,{
   get(target,property,receiver){//查的时候触发
           //target就是person,property就是要获取的属性
           //receiver就是代理对象p=Proxy{name:'张三',age:12} 
           //console.log("读取了!")
           //console.log(target,property,receiver)
           return Reflect.get(target,property)//等价于target[property]
   },
   set(target,property,newVal){//新增、修改的时候触发
          //console.log("设置了!")
          //console.log(target,property,newVal)
           return Reflect.set(target,property,newVal)//等价于 target[property] = newVal;
   },
   deleteProperty(target,property){//删除的时候触发
           //console.log("删除了!!!")
           //console.log(target,property)
           return Reflect.deleteProperty(target,property)//等价于 delete target[property]
   }
})

接着在控制台中输入:person.name 打印出来张三, 但是get方法却没被触发,这是为什么呢?
这是因为proxy代理了源对象,给出一个新的代理对象 操作新对象才会触发get和set方法。
如下图所示:

11.8.png
使用proxy改写上面简约版的vue

vue.prototype.observer = function(obj){
    var self = this;
    this.$data = new Proxy(obj,{
        get(target,key,receiver){
            return target[key]
        },
        set(target,key,newVal,receiver){
            target[key] = newVal
           self.render()
        }
    })
}