这是我参与更文挑战的第3天,活动详情查看: 更文挑战vue
本文是关于数据响应式原理的学习笔记,目的在于更好的理解 Vue 的底层原理,篇幅较长,故而拆分为几篇,从此会陆续更新~react
得力于 Object.defineProperty() 的特性,vue 的数据变化有别于 react 和小程序,是非侵入式的。详细介绍能够看 MDN 文档,这里特别说明几点:c++
value 或 writable
和 get 或 set
是不能同时出现的,不然报错Object.defineProperty()
在使用 getter 和 setter 的时候,要想实现属性的修改,须要借助一个变量周转,以下面的 value,这就很麻烦。小程序
const obj = {}
let value
Object.defineProperty(obj, 'a', {
enumerable: true,
configurable: true,
get() {
console.log('getter')
return value
},
set(newValue) {
value = newValue
console.log('setter', newValue)
}
})
复制代码
因此定义了 defineReactive 函数,方便去给对象增长一个响应式属性。这里建立一个闭包的环境:闭包必定要有内外两个函数,外面这个函数 defineReactive 的 value 就造成了闭包。数组
const obj = {}
function defineReactive(data, key, value) {
// 若是只传了两个参数,则让 value 直接等于 data[key]
if (arguments.length === 2) value = data[key]
Object.defineProperty(data, key, {
enumerable: true, // 可被枚举(for...in 或 Object.keys 方法)
configurable: true, // 可被配置,好比删除
get() {
console.log('查看了' + key + '属性')
return value
},
set(newValue) {
console.log('修改了' + key + '属性')
value = newValue
}
})
}
defineReactive(obj, 'a', 10)
console.log(obj.a)
obj.a = 11
console.log(obj.a)
复制代码
获得的结果以下图markdown
咱们本身写一个可以侦测对象所有属性的库
新建 index.js 做为主入口文件,用于测试效果,咱们 let 一个对象 obj,目标是经过把 obj 做为参数传给 observe 函数,便可实现对 obj 对象全部属性的查看与修改的监测。闭包
// index.js
import observe from './observe.js'
let obj = {
a: {
m: {
n: 1
}
},
b: 2
}
observe(obj)
复制代码
observe 函数用于观察一个对象(value)的属性是否已被监测的(是否有 __ob__
属性),若是不是则让其属性成为响应式的(经过 new Observer(value)
)。
注意:之因此起了 __ob__
这么奇怪的变量名,是为了保证不会与对象的原有属性同名。函数
// observe.js
import Observer from './Observer.js'
export default (value) => {
if (typeof value !== 'object') return
if (value.__ob__ !== undefined) {
// 暂时留空
} else {
new Observer(value)
}
}
复制代码
Observer 是一个类,一旦 new 了一个实例,则作 2 件事:oop
__ob__
属性,值为此次 new 的实例(也就是构造函数中的 this),由于但愿 __ob__
属性是不可被枚举的,因此用 def 函数处理。// Observer.js
import { def } from './utils.js'
import defineReactive from './defineReactive.js'
export default class Observer {
constructor(value) {
def(value, '__ob__', this, false)
this.walk(value)
}
// 处理对象,让对象的属性变为响应式
walk(value) {
for (let key in value) {
defineReactive(value, key)
}
}
}
复制代码
def 函数定义以下post
export const def = (obj, key, value, enumerable) => {
Object.defineProperty(obj, key, {
value,
enumerable,
writable: true,
configurable: true
})
}
复制代码
相较于前面定义的时候,在两个地方添加了 observe(value),从而实现了递归侦测对象的所有属性。这里的参数 value,就是已经被变为响应式的属性的值,这个值若是是个对象,也须要被侦测,因此也要被 observe。
// defineReactive.js
import observe from './observe.js'
export default function defineReactive(data, key, value) {
if (arguments.length === 2) value = data[key]
// 注意这里不是传 key 而是传 value,由于 key 只是一个字符串,value 才是 key 指向的对象
observe(value)
// 让 data 的 key 属性变为响应式属性
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
console.log('查看了' + key + '属性')
return value
},
set(newValue) {
console.log('修改了' + key + '属性')
value = newValue
// 修改的属性也须要被观察,若是是对象须要被侦测
observe(newValue)
}
})
}
复制代码
至此,在 index.js 传入 observe 的 obj 的每一个属性都是响应式的了
// index.js
...省略前面的代码
obj.a.m = {
y: 8
}
console.log(obj.a.m.y)
复制代码
测试结果以下
接下去就是关于数组的响应式处理,但为了避免让每篇文章的篇幅过长以致于读起来昏昏欲睡,将在下篇继续分享~
普通对象也是有 getter 和 setter 的:
var num= {
a: 2,
get b(){
return 2
}
}
复制代码
存取器属性定义为一个或两个与属性同名的函数,这个函数定义不使用 function 关键字,而是使用 get 或 set,也没有使用冒号将属性名和函数体分开。