/**
* By default, when a reactive property is set, the new value is
* also converted to become reactive. However when passing down props,
* we don't want to force conversion because the value may be a nested value
* under a frozen data structure. Converting it would defeat the optimization.
*/
export const observerState = {
shouldConvert: true,
isSettingProps: false
}
这一篇主要是相对
src/core/observer/
这一块进行剖析。主要作用为MVVM
框架在数据层面的观察,之后通知DOM
刷新的部分。文档里面对于这一部分的原理是这么说的:
受现代
JavaScript
的限制(以及废弃Object.observe
),Vue 不能检测到对象属性的添加或删除。由于 Vue 会在初始化实例时对属性执行getter/setter
转化过程,所以属性必须在 data 对象上存在才能让Vue
转换它,这样才能让它是响应的。所以
observer
需要做的事情是:遍历一个对象/数组
是否应该遍历它?
虽然我们希望数据都是响应式的。然而,在某些情况下(
v-for
循环中或props
传递的数据),我们并不希望人为改变其中的数据(通常Vue
会给出一个警告)。因而需要在遍历一个对象之前,设定这个状态是否应该被响应式触发:如何遍历它?
遍历对象/数组通常采用的是递归遍历,这一点众所周知。在
Observer
里有两个方法:walk
和observeArray
。他们分别用于遍历对象和数组。为何我们要遍历它?
文档里面对这一部分解释得很清楚:
把一个普通
JavaScript
对象传给Vue
实例的data
选项,Vue
将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为getter/setter
。Object.defineProperty
是仅ES5
支持,且无法 shim 的特性,这也就是为什么Vue
不支持IE8
以及更低版本浏览器的原因。变异方法
什么是变异方法
之前初学
Vue
的时候踩过一个坑,相信这个坑,所有初学Vue
读者也都经历过:更改数组的某一项,并不会触发视图更新?这是为什么?文档上边对此解释为变异方法。
其实
Vue
所做的事情是改写数组的push
、pop
等方法,让他们在执行之后通知Vue
,因而如果不使用变异方法
进行数组更新,这样的改变是不会被Vue
所监听得到的!在使用数组的变异方法时,除了触发本方法,还会触发一个回调:通知Vue
,我已更新!在
src/core/observer/index.js
最开始引入数组的变异方法:变异方法的改写
下面来分析变异方法的改写:
我们这里要做到的是:对于数组每个元素的变化,我们都要做到让它是可响应的,这一点是至关重要的。
而对于数组的变异方法
push
、pop
、shift
、unshift
、splice
、sort
、reverse
。只有push
、unshift
和splice
是有新的元素添加入了原数组。因此我们需要对其做特殊处理!特殊处理就是:对于每一项,我们都对它执行
observeArray
方法,使得Vue
能够响应它自身的变化(也就是通过Object.defineProperty
为之添加getter/setter
方法)。对于
splice
方法,其添加的参数在第二位。因而inserted = args.slice(2)
。至此,变异方法的分析大概就到这里。
如何做到响应?
Observer的constructor
我们先分析这样一段代码:
Observer
的作用是使得传入的对象/数组是相应的,这样我们才能够去实现Vue
的VM
与Model
之间的双向绑定。如何observer一个数组?
下面来看observe方法:
observe
先对传入的数组作一个判断:如果不是引用类型,则返回,如果其原型上已有__ob__
实例(即其已经被observe
过了),则返回。否则就去做递归,使得其每一子项都是可观察的:ob = new Observer(value)
。如何observer一个对象?
在
Vue
执行this.walk(value)
时,会对其对象每一项进行递归遍历,并对每一项执行defineReactive(obj, keys[i], obj[keys[i]])
,使得对象是可响应的。下面来看
defineReactive
的实现方式,由于量比较大,直接贴一段我写过注释的代码:总结
其实整个过程也正如文档里面所说的,递归,为每个引用类型添加getter/setter,对于数组的
profill
是为其添加变异方法来进行相应其数据的变化。对于对象我们只能做直接替换!(不能做某一项的改变,我们要刷一个对象的方式,文档里也有写到,只能类似于redux
改变数据的方式,使用Object.assign
,当然你也可以使用immutable
)。对于
getter/setter
,同样也需要对每一项进行递归的发布 - 订阅其主要为依赖于Dep
对象的发布 - 订阅模式,对于getter
,一旦订阅到这一个变化,还会去发布一个自身已经改变的状态给订阅其的数据。即源码中的dep.depend()
。对于setter
,一旦一个数据触发其set
方法,Vue
便会发布消息,通知订阅这个数据的元素也要发生改变。即源码中的dep.notify()