vue框架怎么做响应式_浅谈 Vue 框架中的响应式原理

浅谈 Vue 框架中的响应式原理

众所周知, 这几年前端发展的非常迅速, 涌现了好几大不错的框架体系, 其中 vue.js 以入门门槛低实用性高深得广大前端喜爱. 今天我们就谈谈 vue.js 框架的核心 -- 响应式系统

说到响应式系统, 它的原理核心来自于 Object.defineProperty, 相信大多数人都认识它, 但今天也要基本的介绍介绍一下它.

我们可以看看官方的介绍:

ab7653affab982b574eb7acc55df2e04.gif

然后我们在看看它要那些通用的属性:

ab7653affab982b574eb7acc55df2e04.gif

知道该方法怎么用的时候, 我们就来实现就最基本的响应式系统原理:

ab7653affab982b574eb7acc55df2e04.gif

这个图片在官方文档上就有, 仔细的观察一下, 我们不看所有的生命周期, new Vue() => init 这个阶段的时候, 数据就开始进行响应式的操作了.

我们定义一个函数, 用来表示视图更新, 调用这个函数就告诉大家视图更新啦.functioncb(val){

/* 更新视图操作 */

console.log("a=b=c=d=f=e=.....");

}

接下来我们创建一个 defineReactive , 这个方法通过 Object.defineProperty 来实现对对象的响应式操作.functiondefineReactive(obj,key,val){

Object.defineProperty(obj,key,{

enumerable:true,

configurable:true,

get:functiongetVal(){

returnval;

},

set:functionsetVal(newVal){

if(newVal===val)return;

cb(newVal);

}

});

}

写好这个后我们还需要定义另外一个方法 observer, 该方法我们用来遍历修改的对象, 对这些对象通过 defineReactive 函数进行相应的处理:functionobserver(value){

if(!value||(typeofvalue!=='object')){

return;

}

Object.keys(value).forEach((key)=>{

defineReactive(value,key,value[key]);

});

}

这个时候基本大功告成了, 当然真理是需要实践一下, 这个时候我们封装一个 Vue:classVue{

constructor(options){

this._data=options.data;

observer(this._data);

}

}

这里我们写一个 Vue 的构造函数, 对 options 里面的 data 进行处理, 当然, 这里面的 data 就相当于我们经常在 vue.js 模板里面的写的 data. 下面我们来 new 一个 Vue 的对象进行操作:letobj=newVue({

data:{

test:"毁灭全人类"

}

});

obj._data.test="我的心愿是: 时间和平!";

这个时候视图就会更新了, 这就是 vue.js 的响应式基本原理.

既然是基本原理, 它实现的也是最基本的功能, 接下来我们看另外一个例子:newVue({

template:

`

毁灭吧,{{a}}

毁灭吧,{{b}}

`,

data:{

a:'全人类',

b:'地球',

c:'太阳系'

}

});

如果我们现在改变 c 的值: this.c = "全宇宙" 这个时候我们在调用 cb 是无用的, 但 vue 里面这时候发生什么? 我们不得而知. 我们留着疑问看下面一个例子:

假如现在我们有一个全局对象, 而且很多 vue 视图都调用了它:letechats={

options:'pie'

};

leto1=newVue({

template:

`

{{options}}

`,

data:echats

});

leto2=newVue({

template:

`

{{options}}

`,

data:echats

});

这时我们做了这样一个操作: echats.options = "line", 这时会发生什么? vue.js 又是怎么操作的呢?

ab7653affab982b574eb7acc55df2e04.gif

根据上面的图我们可以知道, 在 getter 这个阶段会获取到相应 Watcher 实例对象, 然后 setter 被调用的时候, 会通过 Watcher 重新计算, 从而致使它关联的组件模板得以更新.

对比, 我们例 2 和例 3, 要实现这一系列的操作, 单单靠上面我们的基本原理的方法是不行的, 所以我们需要改变一下

订阅者 Dep

接下来我们来实现一个订阅者, 用来存放 Watcher 实例对象.classDep{

constructor(){

/* 存放 Watcher 对象的数组 */

this.subs=[];

}

/* 在 subs 中添加一个 Watcher 对象 */

addSub(sub){

this.subs.push(sub);

}

/* 通知所有 Watcher 对象更新视图 */

notify(){

this.subs.forEach((sub)=>{

sub.update();

})

}

}

这时我们在来实现一个观察者:

观察者 WatcherclassWatcher{

constructor(){

Dep.target=this;

}

/* 视图更新 */

update(){

console.log("毁灭全人类");

}

}

Dep.target=null;

下面我们在修改一下一开始列子的代码:functiondefineReactive(obj,key,val){

constdep=newDep();

Object.defineProperty(obj,key,{

enumerable:true,

configurable:true,

get:functionreactiveGetter(){

dep.addSub(Dep.target);

returnval;

},

set:functionreactiveSetter(newVal){

if(newVal===val)return;

dep.notify();

}

});

}

classVue{

constructor(options){

this._data=options.data;

observer(this._data);

newWatcher();

console.log('render...',this._data.a);

}

}

通过上面的代码, 这个时候我们在回头看例 2 和例 3, 当我 data.c 改变的时候, 这里会发生什么呢? 现在我们知道每一个 data 的属性都对应一个 dep, 而每一个 dep 就对应一个或者多个 Watcher(多个视图调用的情况), 例 2 中我们改变了 data.c, 但视图上并没有对象的 Watcher, 那么它就没法调用 addSub 方法, 所以视图不会更新. 当然例 3 也从中得以理解. 最后贴一下我整理后的所有代码:constObserver=function(data){

for(letkeyindata){

defineReactive(data,key);

}

}

constdefineReactive=function(obj,key){

constdep=newDep();

letval=obj[key];

Object.defineProperty(obj,key,{

enumerable:true,

configurable:true,

get(){

console.log('in get');

dep.depend();

returnval;

},

set(newVal){

if(newVal===val){

return;

}

val=newVal;

dep.notify();

}

});

}

constobserve=function(data){

returnnewObserver(data);

}

constVue=function(options){

constself=this;

if(options&&typeofoptions.data==='function'){

this._data=options.data.apply(this);

}

this.mount=function(){

newWatcher(self,self.render);

}

this.render=()=>{

if(self){

returnthis._data.text;

}

}

observe(this._data);

}

constWatcher=function(vm,fn){

constself=this;

this.vm=vm;

Dep.target=this;

this.addDep=function(dep){

dep.addSub(self);

}

this.update=function(){

console.log('is watcher update');

}

this.value=fn();

Dep.target=null;

}

constDep=function(){

constself=this;

this.target=null;

this.subs=[];

this.depend=function(){

if(Dep.target){

Dep.target.addDep(self);

}

}

this.addSub=function(watcher){

self.subs.push(watcher);

}

this.notify=function(){

for(leti=0;i

self.subs[i].update();

}

}

}

constvue=newVue({

data(){

return{

text:'hahah'

}

}

})

vue.mount();

vue._data.text='123';

总结:

ab7653affab982b574eb7acc55df2e04.gif

在 observer 的过程中会注册 get 方法, 该方法用来进行依赖收集. 在它的闭包中会有一个 Dep 对象, 这个对象用来存放 Watcher 对象的实例. 其实依赖收集的过程就是把 Watcher 实例存放到对应的 Dep 对象中去. get 方法可以让当前的 Watcher 对象 (Dep.target) 存放到它的 subs 中 (addSub) 方法, 在数据变化时, set 会调用 Dep 对象的 notify 方法通知它内部所有的 Watcher 对象进行视图更新.

后话: 参考了很多大牛的文章, 如果描述的有错, 请多多包涵~~

来源: http://www.qdfuns.com/article/16817/00a5f91f18fa5216135d6c14c81de3b9.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值