最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 【Vue.js进阶】总结我从Vue源码学到了什么(上)

    正文概述 掘金(lin1997)   2020-11-27   559

    【Vue.js进阶】总结我从Vue源码学到了什么(上)

    前言

    阅读Vue源码仅仅只是为了面试吗?我想,大概很多人都会这么觉得吧!但我并不这么想...为什么呢?(ps:后续再慢慢讲述...)当然,当你选择去阅读Vue源码也真的是需要一定的勇气,为什么会这么说?如果自己花了时间但却没有一点收获,心情肯定是失落的。好在功夫不负有心人,这段时间通过不断的思考自己学到了什么这样的课题伴随着...还算是有收获。

    初始 _init

    Vue构造函数从_init这个api下开始工作,在init.js这个文件下初始化了很多api,详细的可查看源码,在这里我只想分享我的心得...

    初始initComputedinitWatch

    平时常讲的computed具有缓存的效果以及它依赖某个数据的变化而变化;watch当开启immediate:true时,为什么会比mounted钩子执行的还早?并且它们两个有什么区别,在项目中应该如何取舍等等...

    这一切的疑问,在initComputedinitWatch函数都告诉我了。

    源码思路

    • initComputed:内部的原理是使用Watcher类实现

    当我们给computed选项写的的每一个对象,都是通过new Watcher包装构造出computedWatcher实例并且通过传入一个computedLazy:true标识来实现缓存的机制。

    • Watcher类具体做了什么事呢?(ps:Watcher类的实现,后续具体讲述)

    在Watcher类构造器内部会进行第一次的取值。那这样看起来computed的每一个对象被包装成了computedWatcher实例,再将每个computedWatcher实例通过Object.defineProperty进行再次包装,在使用时触发了get,get函数会拦截判断computedWatcher实例dirty的值,为true时则触发watcher类的evaluate函数,该函数用来获取值最后再关闭dirty,dirty是false直接返回第一次获取的值。

    整理出这样的思路,我大概写了一份伪代码来实现上面所述的逻辑: 【Vue.js进阶】总结我从Vue源码学到了什么(上)

    思考

    后来我又思考了:如果我写了一个computed对象不依赖data选项的数据或者是props的数据,然后我更改computed值时怎么会不能被watch选项监听呢?(可以动手敲一下)

    computed: {
        getC: {
          get: () => {
            return 1;
          },
          set: (newVal) => {
            return newVal;
          },
        },
      },
    mounted() {
        setTimeout(() => {
          this.getC = 8;
        }, 800);
      },
    watch: {
        getC: {
          handler: function () {
            console.log(this.getC, "c无法被监听");//无法打印
          },
        },
      },
    

    computed内部Object.defineProperty中的set没有做任何通知依赖变化的操作。initComputed最终获取的是一个值,它可以依赖某个响应式数据,也可以不依赖。如果依赖于一个响应式数据或者多个响应式数据,那么当响应式数据发生变化了,它也跟着变化这是因为响应式数据中使用了new Dep创建了dep实例,并且在set函数通知了那些依赖它的watcher。

    那为什么不被watch选项监听呢?

    watch选项监听的是响应式数据,执行watch选项的回调函数是在dep通知更新变化的时候去执行的,那不依赖于响应式数据的computed对象怎能做到被通知变化了呢!这样子说watch是用来监听一个值的变化,接收的值是一个新变化的值合情合理了。

    回到最初的疑问,immediate:true的实现原理什么?

    在开启immediate:true时,会先执行watch的回调函数,这是因为js是单线程的,在_init初始化时initWatchmounted早。initWatch的关键是vm.$watch,在实现这个函数的时候,判断了immediate是否为true,如果是true就立即执行了callback函数。

    通过这些内容,实现缓存机制的一种想法已经浮现在我的脑袋中了,合理利用好Object.defineProperty可以实现一些数据的拦截,也是相当ok的!

    初始响应式数据

    props和data选项都是响应式数据,Vue是如何将propsdata的数据变成可观察的?

    在Vue源码中,通过一个Observer类实现数据的劫持,也就是说当初始化data和props时,调用observer方法。该方法进行了数据类型的判断,决定是重新包装对象,还是重写方法。那这和响应式有什么关系?

    疑惑

    当我在data选项里定义了一个变量a之后,改变了变量a的值,为何所有用到它的地方都会自动更新值呢?

    思考

    响应式数据其关键的还是Watcher类和Dep类在中间起了关键的角色。那这两个类具体做了什么事情?

    • Watcher(被观察者)作用

      1、在自身实例化时往属性订阅器(dep)里面添加自己。

      2、自身必须有一个update()方法。

      3、待属性变动dep.notice()通知时,能调用自身的update()方法,

    • Dep(观察者)作用

      1、有个框子来收集着每一个watcher。

      2、有个收集watcher的addSub()方法。

      3、有个通知的方法,通知watcher进行更新。

    • 提炼了两者的作用之后,那它们又是如何工作的?

      Wtacher类在实例化时,通过定义Dep类身上的target属性将该watcher实例关联起来了。那么对于变量a,在data初始化使用Object.defineProperty进行包装时,通过new Dep类(ps:这个dep实例可以看出一个框子,通过框子去收集这些依赖变量a的watcher实例)实现依赖变量a的watcher实例被收集了。

      这个dep实例实际上是属于变量a的小框子,它将依赖它的watcher放进了dep定义的小框子里。等待变量a发生改变时,在set函数里触发dep的notify函数通知所有watcher调用update函数更新值。

    所以Observer类的实现并不难,关键是Watcher类和Dep类。

    • 如何实现Watcher类和Dep类

      我通过模拟Vue源码写了一份伪代码,可供参考:

    Watcher类和工具函数: 【Vue.js进阶】总结我从Vue源码学到了什么(上)

    【Vue.js进阶】总结我从Vue源码学到了什么(上)

    Dep类: 【Vue.js进阶】总结我从Vue源码学到了什么(上)

    到这里,你应该清晰的了解到响应式数据是如何实现了吧,而不是仅仅因为Object.defineProperty~

    回归到Vue的响应式数据data/props,在源码中还做了一层代理,因此我们写代码的时候就可以直接通过this.xxx来访问变量了。

    【Vue.js进阶】总结我从Vue源码学到了什么(上)

    写这段伪代码的时候,让我想起了es6的Proxy类。

    初始nextTick

    在了解nextTick之前,我们先来看一个案例吧。

    案例

    有一种情况:在mounted时,test的值会被++循环执行1000次。 每次++时,都会根据响应式触发setter->Dep->Watcher->update->patch。

    思考:Vue如何是如何更新视图的以及和nextTick有什么关系?

    Vue是异步更新视图的,Vue实现了一个queue队列。先执行主线程的代码,等下一个tick的时候会统一执行queue中Watcher的run。这里在说下一个tick那得先理解一下事件循环。可参考学习大佬写的:事件循环

    同时,拥有相同id的Watcher不会被重复加入到该queue中去,所以不会执行1000次Watcher的run。最终更新视图只会直接将test对应的DOM的0变成1000。

    而之所以会在nextTick回调函数中能获取到数据修改后的DOM变化,是因为nextTick函数中定义了timerFunc函数,这个函数使用PromiseMutationObserversetImmediatesetTimeout进行包装,其实就是微任务的存在形式。上面queue队列中Watcher的run函数执行完毕,是不是要清空一下微任务,所以nextTick回调函数中能获取到数据修改后的DOM变化。

    在nextTick函数中使用 callbacks 而不是直接在 nextTick 中执行回调函数的原因是保证在同一个 tick 内多次执行 nextTick,不会开启多个异步任务,而把这些异步任务都压成一个同步任务,在下一个 tick 执行完毕。

    似想如果这时候没有异步更新视图,那么每次++都会直接操作DOM更新视图,这是非常消耗性能的。

    初始事件机制

    Vue.js提供了四个事件API,分别是$on$once$off$emit

    分析

    • $on订阅函数,传入事件名称,以及cb。一个事件可被订阅多次,所以一个事件通过队列的形式进行cb的收集。
    • $emit发布函数,通过对应的事件名称去执行刚刚收集的cb函数。
    • $once只订阅一次,这里的思想是使用函数包装:将传入的cb进行包装,然后订阅包装函数,包装函数逻辑是先调用cb再取消订阅。那什么时候会触发包装函数?就是当$emit时候。

    还有一个小细节:利用函数的特性给包装函数绑定属性,将传入的cb绑定到包装函数的某个属性上。这个作用是什么?(ps:留个疑问,后续讲述)

    • $off是移除事件监听,源码中分了几种情况:

      1、参数lenght为0,清空所有事件。

      2、当事件传入的是数组,递归一个一个关闭事件。

      3、判断传入的事件名和fn,从事件名所对应的队列中删除。这里比较特殊的就是如果我先$once一个事件了,但我并没有触发,直接$off该事件了。这时候去删除fn的时候就利用到了上面说的利用函数的特性给包装函数绑定属性。

    思考

    订阅发布仅仅只能先订阅了才能发布吗

    显然不是的,在这个Eevnt类我又实现了先发布后订阅的情况(ps:例如离线接收消息)。主要是思想就是发步放在发布的时候,我先将发布的这个函数包裹收集起来,等到订阅者订阅了全部清空,这里要注意的是发布方的生命周期应该是一次的。(ps:订阅方只能订阅一次)等到订阅的时候再将结果给订阅方。

    具体实现的代码如下 【Vue.js进阶】总结我从Vue源码学到了什么(上) 【Vue.js进阶】总结我从Vue源码学到了什么(上)

    针对事件机制,一般可用来通信传输数据。我又思考了一个问题:

    如何在Vue中实现广播事件(ps:广播事件就是父一层一层的向子广播通知)和派发事件(子一层一层的向父派发)。

    广播事件和派发事件也有2种情况:

    针对每一层级都派发

    【Vue.js进阶】总结我从Vue源码学到了什么(上) 指定一层级派发 【Vue.js进阶】总结我从Vue源码学到了什么(上)

    利用好事件机制,会给项目的数据通信添加不一样的色彩哦~

    总结

    学习本身就是一个循进渐进的过程,每个人的学习方法都不一样,但我觉得自问自答的方式也是一份学习方法!

    这就是我觉得为什么看源码不仅仅是为了面试的原因,学习框架重在一份思想并且知道内部api的实现机制也是一份收获。这段时间的源码学习笔记,希望能帮到各位~

    另外,我学习过程中也手动写了一些代码可供参考:Vue源码学习总结

    望大家不要吝啬手中的??


    起源地下载网 » 【Vue.js进阶】总结我从Vue源码学到了什么(上)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元