本文是Vue双向绑定的前置篇。我们都知道Vue是通过数据劫持来实现双向绑定的,那么什么是数据劫持?数据劫持是如何进行的?要弄明白这两个问题,我们首先要知道我们一般情况是怎么定义对象,怎么修改对象的,先看下简单的对象定义与修改;
1 | const obj = {name: '张三'}; |
上面的代码中我们 console.log
打印 obj.name
的时候,是不是相当于先获取 obj.name
的值,然后再打印,那么获取的时候,你知道吗?你不知道;我们修改 obj.name
的值为 李四
的时候,你知道吗?你不知道;那么数据劫持是什么,数据劫持就是当你要取 obj.name
的值的时候通知我一下,你修改 obj.name
的时候也通知我一下,最好可以,你获取设置值的时候由我来返给你,而不是你直接操作;
所以想要实现这个目的,我们就需要用到一个API Object.defineProperty
或者 Proxy
,也是我们本文的主旨;
Object.defineProperty
Object.defineProperty
是 Vue 2.x 实现数据劫持的主要API,它也是ES5原生的一个API;语法 Object.defineProperty(obj, prop, descriptor)
,它接收三个参数,obj
原始对象,即你要修改或者获取的值属于那个对象; prop
要修改或获取的obj对象的key; descriptor
属性描述符,即可以设置这个对象的这个值,是否可枚举,是否只读等描述信息;
如此,那我们就把上方的代码,再加几个属性,然后用 Object.defineProperty
改写一下
1 | const defineReactive = (obj,key,value) => { |
如此我们就用 Object.defineProperty
简单实现了数据劫持(上述代码可以粘贴到浏览器控制台运行),甚至你可以把 get
的返回写个其他值, set
的修改写成其他值,那么你再获取和修改,就是你写的值了,玩的时候可以用,真的做工具或者业务用,根据需求写就行。
但是上方还有点不完美,因为属性的值还可以是对象,而 Object.defineProperty
只能代理一层,对于属性的属性就不能劫持了,所以我们可以把上方代码再修改完善下,然后加个递归,如果属性的值还是对象我们再代理一下。
1 | const observe = obj => { |
至此,使用 Object.defineProperty
做数据劫持基本完成了,但有四点需要注意;
Object.defineProperty
只能代理对象,不能代理字符串,数组,number等其他对象;Object.defineProperty
只能代理你设置了的key,你没设置代理的不会被代理,也就是说,你代理之后,又给元对象加了新属性,不会被代理;Object.defineProperty
需要在对象定义完成后就去代理,如果定义后先获取修改,再代理,只会对代理之后的获取修改起作用,代理之前的数据获取修改拦截不到- 不使用
Object.defineProperty
代理对象,不表示你获取修改对象的属性时不通过get
和set
拦截器,只是不代理对象属性的修改获取是隐式的。
Proxy
上文我们已经表述了什么是数据劫持,以及怎样进行数据劫持,所以接下来我们所要做的只是把 Object.defineProperty
方法换成 Proxy
就可以。
首先我们看下 Proxy
的语法 new Proxy(target, handler)
;可以看到 Proxy
是一个构造函数,使用它的时候我们需要对其进行实例化,然后它接收两个参数 target
要被代理的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理),handler
一个对象,其属性是当执行一个操作时定义代理的行为的函数。
然后我们把上方的代码用 Proxy
改写一下
1 | let obj = {name: '霸宋', age: 18, sex: '男', other: {father: '宋爸', mother: '宋妈'}}; |
至此使用 Proxy
进行数据劫持基本改写完成,递归的地方感觉不完美,但目的是实现了,不过需要关注 Proxy
与 Object.defineProperty
的性能消耗,以及 Proxy
的兼容性问题,但不是本文关注点,也就不研究了。