带你看懂vue2的数据响应式原理

前言
篇幅过长,基本都是文字,配图(可以看看官网相应式原理的配图)啥的,代码啥的都很少,大家可以耐着性子,边思考边看,慢慢来。也可以结合源码阅读。

正文
我们都知道,vue是一个很明显的使用数据驱动视图的框架。vue2内部的核心原理是使用的es5中的Object.definePropertyAPI来实现数据相应式的,这个api提供了gettersetter方法来实现数据劫持,使得vue可以来监听访问对象的属性或者对对象的属性进行赋值。

在new Vue之后,vue会将我们在data中定义好的数据使用 Observer 变成相应式数据,并给每一个数据都分配一个 Dep 实例,该实例就是用来在运行getter函数时进行依赖收集的(dep.depend()),当我们的数据发生变化时,便会运行我们的setter函数,在该函数里面便会进行派发更新(dep.notify()),至于到底收集的是什么,我们之后再说
此外,我们都知道,vue会将我们写的模板编译成为render函数,或者我们直接自己书写render函数也是可以的,之后会运行render函数,在运行render函数的过程中,会用到我们在data中定义好的数据。这个时候自然会调用我们定义好的getter函数,然后,边进行依赖收集,也就是说,告诉data里面被使用到的数据,是render函数依赖了你,你把这个函数给收集进去,到时候,你变了,就可以通知这个render函数,你变了,需要再次运行这个render函数了,因为,运行完render函数后,会返回一个vnode,vue拿到vnode之后,会进行新旧vnode的对比,然后找到真正需要改变的地方,对真实的dom进行相关操作,试图也就自然更新了。话回到之前,我们收集的依赖就是render函数吗?那么有一个疑问,就是说,我运行了render函数,然后里面使用到了data中的相应式数据,然后运行了数据的getter方法,在这个方法里面要进行依赖收集,把render函数收集到dep中去,那么在这个方法里面,我们怎么就知道要收集的是这个render函数呢,换句话说,我怎么知道是哪个函数在用我这个数据呢,我们仔细想想,会发现,这个好像是有点难,不知道从何下手。那么,接下来,我们看看vue中是如何解决这个有趣的难题的。

其实,vue中,还使用了一个 Watcher 类,我们的render函数,其实是new Watcher的时候当成参数传进去的,然后便会运行我们的render函数,也就是说,render函数并不是说直接调用,而是在watcher里面调用的,我们收集的依赖,也并不是render函数,而是watcher实例。可是这样有什么用呢,解决了上面的那个问题了吗?别急,我们在看看vue接下来是怎么做的。在watcher里面,运行render函数之前,vue做了一个非常简单的操作,就是给Dep类上设置了一个静态属性target,然后并将当前的watcher实例赋值给了这个target,然后才运行的render函数,这个时候,当使用到了data中的数据,在运行getter方法的时候,dep.depend(),会先看看Dep上的静态属性target是否有值,有的话,就会把这个值,也就是watcher实例添加到dep.subs里面去,这是一个数组,因为可能不止一个函数依赖这个数据,还可能有我们的计算属性,watch监听都会依赖这个data数据。当我们的render函数运行完之后,依赖也就收集完了,之后,vue会将Dep上的target置空。之后,当我们数据发生变化的时候,运行了setter方法,进行依赖派发更新,dep.notify(),其实也就是把subs里面的watcher实例遍历一次,然后一次运行watcher实例上的update函数,这个函数里面,会再次运行我们的render函数或者其他的依赖函数(watch监听写的函数等)。然后,如果是render函数的话,我们的视图自然就会更新啦!

接下来,我们考虑一件事,这样的设计有没有什么问题。当我们某个函数在运行的时候,连续改变了好几次,好多个data中的数据,会发生什么?一改变data,便会执行setter,便会派发更新,便会循环收集的依赖,便会执行watcher中的依赖函数,如果这个依赖是render函数,便会执行render函数,然后便会拿到vnode,再进行patch,diff更改视图。想想会进行多少次diff,多少次操作真实dom,仔细想想,真的有必要执行这么多次吗,这会有多浪费效率,我们只需要在这些值都改变完了之后,在一次性去改变视图不好吗?那怎么办呢,别急,vue已经考虑到了这一点,来看看vue是怎么来解决这个问题的。

其实watcher的update方法,里面并不是直接执行render函数的,而是将我们的watcher交给了
schedule 调度器,然后调度器会把watcher放进一个数组里面去,然后,调度器会将一个函数交给 nextTick ,nextTick会把这个函数放进微任务队列等待同步任务执行完,在执行这个函数,而这个函数执行过程中,会把刚刚说的那个数组遍历一次,拿到watcher之后,调用watcher的run方法,而run方法会真正的去执行render函数。在将watcher放进数组的时候,会先检查这个数组中是不是已经放进过当前这个watcher,没有的话就放进去,已经有了的话,就不放了。后面的watcher放进去这个数组之后,调度器就不会在放一个函数到微任务队列了,有一个就足够了。之后运行这个函数,就会把数组中的所有watcher的run运行一遍了。render函数也知识运行一次,因为刚刚讲了,去重了。

我们再举一个例子,来理解上面的话,如下:

{
    data(){
        return {
            a:1,
            b:2,
            c:3
        }
    },
    watch:{
        a(newVal){
            // 做某些事
        }
    },
    methods:{
        click(){
            this.a = 2;
            this.b = 3;
            this.c = 4
        }
    }
}

当执行this.a = 2的时候,会执行a的setter函数,然后进行dep.notify(),然后循环subs进行watcher.update(),a的依赖一共有两个,一个是render函数的watcher,一个是watch的watcher,因为我们的a变了,我们需要执行那个监听函数。update中,把render函数的watcher交给schedule调度器,调度器查看数组里面有没有push进去过render函数的watcher,一看没有,把他加进去,然后将一个函数交给nextTick,nextTick把这个函数放进微任务队列,然后会来看a的watch的watcher,交给调度器之后,再看数组里面有没有这个watcher,一看,没有,那么继续放进去,这个时候就不会再交给nextTIck啥东西了。然后,代码回到了this.b = 3;又把刚刚的顺序来一遍,只不过到调度器在看要不要把当前这个render函数的watcher加到数组中去的时候,因为之前render函数的watcher已经加进去了,所以不会再把相同的watcher加进去了,然后,this.c = 4;同理。这个时候同步代码就执行完了,然后开始执行异步任务,这个时候,我们之前放进微任务的那个函数执行了,它把数组里面的watcher一个个拿出来,其实也就两个wathcer,一个是render函数的watcher,一个是a的监听函数watch的watcher。把他们依次拿出来执行watcher.run()就完事了。
其实,nextTick里面还不完全是我上面讲的这样,还有一些地方为了方便理解简化了一些细节,具体可以去看看源码了。经过我上面的一番话,如果大家懂了个大概的话,可以去看看源码的具体实现了。跟着我说的思路,大家多去看看源码的具体实现,一定会有所收获!

留一个悬念,大家想想计算属性的watcher是怎么一个流程,大家也可以去看看源码是如何实现的,之后有时间我会跟大家一起讲讲vue中关于computed的源码,也是蛮有意思的。

最后,再告诉大家一个细节,其实,不仅仅是dep中会收集依赖,我们的watcher实例中,也会收集是哪些dep收集到了自己!这个大家也可以看看源码是如何写的。


vue2的数据响应式原理jpg.jpg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容