理解Vue2.0响应式原理
前言:
Vue的响应式都是老生常淡的东西了,也有很多很好的文章可以去了解,在面试中也会经常问到,也能说出个一二来😆,在Vue框架中核心实现就是响应式,有必要去理解下它,走着~
Vue响应式系统简单的分为三步:
- 通过Object.defineProperty的getter进行依赖收集,实现一个订阅者(Dep)来进行监听
- 通过Object.defineProperty的setter进行通知
- 通过Watcher中的update进行视图更新
Object.defineProperty
vue就是基于Object.defineProperty它实现的
使用方法:
/*
obj: 目标对象
prop: 需要操作的目标对象的属性名
descriptor: 描述符
return value 传入对象
*/
Object.defineProperty(obj, prop, descriptor)
descriptor的主要有几个属性
- enumerable 属性是否可枚举,默认 false
- configurable 属性是否可以被修改或者删除,默认 false。
- writable 是否可以采用 数据运算符 进行赋值
- get 获取属性的方法
- set 设置属性的方法
有时configurable和writable的定义容易混淆
configurable为false时,不可以用delete等属性操作符进行更改
writable为false时,不可以用obj.a = "new" 等数据运算符进行更改
实现observer方法使对象变为可观察的
知道了Object.defineProperty基本属性后,咱们通过observer来观察对象,observer会在Vue在init阶段会进行初始化,对数据进行响应式绑定。
在源码中定义了defineReactive1这个方法,咱们就用defineReactive来申明一个函数,需要用到obj(需要绑定的对象),key(obj的某一个属性),val(具体值)。
附上简易实现代码
function defineReactive (obj, key, val) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
return val;
},
set: function reactiveSetter (newVal) {
if (newVal === val) return;
cb(newVal);
}
});
}
//用来更新视图的方法
function cb (val) {
/* 渲染视图 */
console.log("视图更新啦~");
}
我们上面提到了observe,这个当然需要实现,需要在上面再封装一层observer,这个observer函数需要传入一个value,这个value就是要观察的对象,借鉴源码简单实现:
function observer (value) {
if (!value || (typeof value !== 'object')) {
return;
}
//通过遍历所有属性的方式对该对象的每一个属性都通过defineReactive处理
Object.keys(value).forEach((key) => {
defineReactive(value, key, value[key]);
});
}
接着封装一个Vue类:
class Vue {
/* Vue构造类 */
//options 里包含了data
constructor(options) {
this._data = options.data;
observer(this._data);
}
}
// new一个Vue实例
let v = new Vue({
data: {
test: "boom sha ka la ka"
}
});
v._data.test = "biu biu biu"; /* 视图更新啦~ */
完整代码:
//用来更新视图的方法
function cb (val) {
/* 渲染视图 */
console.log("视图更新啦~");
}
function defineReactive (obj, key, val) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
return val;
},
set: function reactiveSetter (newVal) {
if (newVal === val) return;
cb(newVal);
}
});
}
function observer (value) {
if (!value || (typeof value !== 'object')) {
return;
}
//通过遍历所有属性的方式对该对象的每一个属性都通过defineReactive处理
Object.keys(value).forEach((key) => {
defineReactive(value, key, value[key]);
});
}
class Vue {
/* Vue构造类 */
//options 里包含了data
constructor(options) {
this._data = options.data;
observer(this._data);
}
}
let v = new Vue({
data: {
test: "boom sha ka la ka"
}
});
v._data.test = "biu biu biu"; /* 视图更新啦~ */
执行上面代码,最终输出 “视图更新啦~”,这几段代码可以体现出响应式原理了
依赖收集:
在Vue源码中,有Dep(订阅者)和Watcher(观察者)两大类,Dep主要作用是用来存放Watcher对象的
Dep
简单实现Dep其中部分代码
class Dep {
constructor () {
// 用来存放Watcher对象的数组
this.subs = [];
}
//在subs中添加一个Watcher对象
addSub (sub) {
this.subs.push(sub);
}
//通知所有Watcher对象更新视图
notify () {
this.subs.forEach((sub) => {
sub.update();
})
}
}
Dep主要实现了两部分
- 添加Watcher观察者对象
- 使用notify对subs中所有Watcher对象进行触发更新操作
Watcher
简单代码实现
class Watcher {
constructor () {
//new 一个Watcher后 把实例赋值给Dep.target
Dep.target = this;
}
update () {
console.log("视图更新");
}
}
Dep.target = null;
把Dep和Watcher都实现了,接下来需要把最开始那部分代码修改下来完成依赖收集
先贴完整代码:
//依赖收集
class Dep {
constructor () {
this.subs = [];
}
addSub (sub) {
this.subs.push(sub);
}
notify () {
this.subs.forEach((sub) => {
sub.update();
})
}
}
class Watcher {
constructor () {
Dep.target = this;
}
update () {
console.log("视图更新啦~");
}
}
//监听属性
function observer (value) {
if (!value || (typeof value !== 'object')) {
return;
}
Object.keys(value).forEach((key) => {
defineReactive(value, key, value[key]);
});
}
//拦截属性 动态添加get set方法
function defineReactive (obj, key, val) {
//创建一个Dep对象,用来收集Watcher对象
const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
dep.addSub(Dep.target);
return val;
},
set: function reactiveSetter (newVal) {
if (newVal === val) return;
val = newVal;
dep.notify();
}
});
}
class Vue {
constructor(options) {
this._data = options.data
observer(this._data)
new Watcher()
console.log('render~', this._data.test);
}
}
let v = new Vue({
data: {
test: '2111'
}
})
v._data.test = '55'
//
//render~ 2111
//视图更新啦~
Dep.target = null
修改的部分:
- 在defineReactive方法中,添加创建Dep对象的代码
- 在get中进行依赖收集,把Dep.target(Watcher对象)存到Dep的subs中
- 在set中去调用dep的notify方法来触发所有Watcher对象的update方法更新对应视图
- 在Vue类的构造函数中创建一个Watcher对象,这时候Dep.target会指向这个Watcher对象
执行过程:
总结:
- 首先Vue初始化的时候会在observer函数中遍历value,触发Object.defineProperty 给对象进行get set绑定
- 在get中让当前的watcher对象存放在Dep的subs中,在数据变化时,set会调用Dep对象的notify方法对subs中的watcher对象进行视图更新
完咯~ 记录下自己所理解的Vue2.0响应式原理,比较简单,细节实现还得仔细看源码,持续学习中~