vue官方对响应式原理的解释:深刻响应式原理javascript
总结下官方的描述,大概分为一下几点:html
然鹅,官方的介绍只是一个大体的流程,咱们仍是不知道vue究竟是怎样给data的每一个属性设置getter、setter方法?对象属性和数组属性的实现又有什么不一样?怎样实现依赖的收集和依赖的触发? 想要搞清楚这些,不得不看一波源码了。下面,请跟我从vue源码分析vue的响应式原理vue
--- 下面我要开始个人表演了---java
vue源码的 instance/init.js 中是初始化的入口,其中初始化分为下面几个步骤:react
//初始化生命周期
initLifecycle(vm)
//初始化事件
initEvents(vm)
//初始化render
initRender(vm)
//触发beforeCreate事件
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
//初始化状态,!!!此处划重点!!!
initState(vm)
initProvide(vm) // resolve provide after data/props
//触发created事件
callHook(vm, 'created')
复制代码
其中划重点的 initState() 方法中进行了 props、methods、data、computed以及watcher的初始化。在instance/state.js中能够看到以下代码。数组
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
//初始化props
if (opts.props) initProps(vm, opts.props)
//初始化methods
if (opts.methods) initMethods(vm, opts.methods)
//初始化data!!!再次划重点!!!
if (opts.data) {
initData(vm)
} else {
//即便没有data,也要调用observe观测_data对象
observe(vm._data = {}, true /* asRootData */)
}
//初始化computed
if (opts.computed) initComputed(vm, opts.computed)
//初始化watcher
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
复制代码
划重点的initData()方法中进行了data的初始化。代码依旧在instance/state.js中能够看到。initData()方法代码以下(删节版)。浏览器
/* 初始化data */
function initData (vm: Component) {
//判断data是不是一个对象
if (!isPlainObject(data)) {
...
}
//判断data中的属性是否和method重名
if (methods && hasOwn(methods, key)) {
...
}
//判断data中的属性是否和props重名
if (props && hasOwn(props, key)) {
...
}
//将vm中的属性转至vm._data中
proxy(vm, `_data`, key)
//调用observe观测data对象
observe(data, true /* asRootData */)
}
复制代码
initData()函数中除了前面一系列对data的判断以外就是数据的代理和observe方法的调用。其中数据代proxy(vm, `_data`, key)
做用是将vm的属性代理至vm._data上,例如:缓存
//代码以下
const per = new VUE({
data:{
name: 'summer',
age: 18,
}
})
复制代码
当咱们访问per.name
时,实际上访问的是per._data.name
而下面一句observe(data, true /* asRootData */)
才是响应式的开始。app
总结一下初始化过程大概以下图dom
observe函数的代码在observe/index.js,observe是一个工厂函数,用于为对象生成一个Observe实例。而真正将对象转化为响应式对象的是observe工厂函数返回的Observe实例。
Observe构造函数代码以下(删减版)。
export class Observer {
constructor (value: any) {
//对象自己
this.value = value
//依赖收集器
this.dep = new Dep()
this.vmCount = 0
//为对象添加__ob__属性
def(value, '__ob__', this)
//若对象是array类型
if (Array.isArray(value)) {
...
} else {
//若对象是object类型
...
}
}
复制代码
从代码分析,Observe构造函数作了三件事:
__ob__
属性,__ob__
中包含value数据对象自己、dep依赖收集器、vmCount。数据通过这个步骤之后的变化以下://原数据
const data = {
name: 'summer'
}
//变化后数据
const data = {
name: 'summer',
__ob__: {
value: data, //data数据自己
dep: new Dep(), //dep依赖收集器
vmCount: 0
}
}
复制代码
当数据是object类型时,调用了一个walk方法,在walk方法中遍历数据的全部属性,并调用defineReactive方法。defineReactive方法的代码仍然在observe/index.js中,删减版以下:
export function defineReactive (...) {
//dep存储依赖的变量,每一个属性字段都有一个属于本身的dep,用于收集属于该字段的依赖
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
//缓存原有的get、set方法
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
// 为每一个属性建立childOb,而且对每一个属性进行observe递归
let childOb = !shallow && observe(val)
//为属性加入getter/setter方法
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
...
},
set: function reactiveSetter (newVal) {
...
})
}
复制代码
defineReactive方法主要作了如下几件事:
__ob__
,其余属性的childOb为undefined)。【经过getter、setter引用】通过defineReactive处理的数据变化以下, 每一个属性都有本身的dep、childOb、getter、setter,而且每一个object类型的属性都有__ob__
//原数据
const data = {
user: {
name: 'summer'
},
other: '123'
}
//处理后数据
const data = {
user: {
name: 'summer',
[name dep,]
[name childOb: undefined]
name getter,//引用name dep和name childOb
name setter,//引用name dep和name childOb
__ob__:{data, user, vmCount}
},
[user dep,]
[user childOb: user.__ob__,]
user getter,//引用user dep和user childOb
user setter,//引用user dep和user childOb
other: '123',
[other dep,]
[other childOb: undefined,]
other getter,//引用other dep和other childOb
other setter,//引用other dep和other childOb
__ob__:{data, dep, vmCount}
}
复制代码
刚刚讲到defineReactive函数的最后一步是每个属性都加上getter、setter方法。那么getter和setter函数到底作了什么呢?
getter函数内部代码以下:
get: function reactiveGetter () {
//调用原属性的get方法返回值
const value = getter ? getter.call(obj) : val
//若是存在须要被收集的依赖
if (Dep.target) {
/* 将依赖收集到该属性的dep中 */
dep.depend()
if (childOb) {
//每一个对象的obj.__ob__.dep中也收集该依赖
childOb.dep.depend()
//若是属性是array类型,进行dependArray操做
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
复制代码
getter方法主要作了两件事:
__ob__
的依赖收集器__ob__.dep
中,这个依赖收集器在使用$set 或 Vue.set 给属性对象添加新属性时触,也就是说Vue.set 或 Vue.delete 会触发__ob__.dep
中的依赖。__ob__.dep
中。确保在使用$set 或 Vue.set时,数组中嵌套的对象能正常响应。代码以下://数据
const data = {
user: [
{
name: 'summer'
}
]
}
// 页面显示
{{user}}
<Button @click="addAge()">addAge</Button>
//addAge方法,为数组中的嵌套对象添加age属性
change2: function(){
this.$set(this.user[0], 'age', 18)
}
复制代码
//dependArray函数
function dependArray (value: Array<any>) {
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)) {
dependArray(e)
}
}
}
复制代码
//转化后数据
const data = {
user: [
{
name: 'summer',
__ob__: {user[0], dep, vmCount}
}
__ob__: {user, dep, vmCount}
]
}
复制代码
dependArray的做用就是将user的依赖收集到它内部的user[0]对象的__ob__.dep
中,使得进行addAge操做时,页面能够正常的响应变化。
setter函数内部代码以下:
set: function reactiveSetter (newVal) {
// 为属性设置正确的值
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
//因为属性的值发生了变化,则为属性建立新的childOb,从新observe
childOb = !shallow && observe(newVal)
//在set方法中执行依赖器中的全部依赖
dep.notify()
}
})
复制代码
setter方法主要作了三件事:
数据是纯对象类型的处理讲完了,下面看下数据是array类型的操做。
observer/index.js中对array处理的部分:
if (Array.isArray(value)) {
const augment = hasProto
? protoAugment
: copyAugment
//拦截修改数组方法
augment(value, arrayMethods, arrayKeys)
//递归观测数组中的每个值
this.observeArray(value)
}
复制代码
当数据类型是array类型时
__proto
为arrayMethods,出于兼容性考虑若是浏览器不支持__proto__
,则使用arrayMethods重写数组数据中的全部相关方法。arrayMethods中的定义在observe/array.js中,代码以下:
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
//修改数组的方法
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/** * Intercept mutating methods and emit events */
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
//拦截修改数组的方法,当修改数组方法被调用时触发数组中的__ob__.dep中的全部依赖
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
//对新增元素使用observeArray进行观测
if (inserted) ob.observeArray(inserted)
//触发__ob__.dep中的全部依赖
ob.dep.notify()
return result
})
})
复制代码
在arrayMethods中作了以下几件事:
__ob__.dep
的全部依赖observeArray代码以下:
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
复制代码
在observeArray方法,对数组中的全部属性进行observe递归。然而这里有一个问题就是没法观测数组中的全部非Object的基本类型。observe方法的第一句就是
if (!isObject(value) || value instanceof VNode) {
return
}
复制代码
也就是说数组中的非Object类型的值是不会被观测到的,若是有数据:
const data = {
arr: [{
test: 0
}, 1, 2],
}
复制代码
此时若是改变arr[0].test=3能够被触发响应,而改变arr[1]=4不能触发响应,由于observeArray观测数据中的每一项时,observe(arr[0])是一个观测一个对象能够被观测。observe(arr[1])时观测一个基本类型数据,不能够被观测。
响应式阶段流程图
参考文章:揭开数据响应系统的面纱