observer | 每天读一点Vue源码

前言

面试的时候经常被问一些Vue源码相关的问题,通常情况下, 我会在面试前恶补掘金上的面筋来对付面试,什么双向绑定的原理呀,什么虚拟dom树呀,实际上我压根儿就没仔细研究过,其一是自己真的比较菜,其二工作上也用不上,别自己给自己添堵。但后面想一下,很多事情,为之则易,不为则难,给自己设立困难(负重)才能进步,决定每天多一点Vue的源码,在Vue的源码选择上,我选择了最老的版本(0.1)😬(真的怕自己看起来吃力), 阅读的模式为通读,从易到难一个文件一个文件的看,看完一个文件后再看它的单元测试,等完全吃透后复制粘贴代码到本地运行测试用例为代码块写一些中文注释,打上tag推到自己的仓库,开始梳理写文章总结(之前有犹豫过是否应该在掘金上写文章,因为这类Vue源码解析的文章已经很多了,而且还写的很好,我再写一遍是否还存在意义,后面想还是写吧,流水总结也不错💧)。

正文

简介

这是我发的第四篇关于Vue源码的文章, 本文介绍observer(发布者), 顾名思义observer用于发布消息, 在Object或者Array发生变化时,被observe的Object或Array通过其原型链上的Emitter.emit触发特定的事件。若Object或Array有层级关系,事件会继续冒泡到上层的Object或Array。(第一篇关于Vue源码的文章介绍过Emitter传送门)

下文主要分二部分:

  • 源码中数组与对象observe的调用顺序和实现原理, 本部分主要帮助大家了解阅读的整个顺序,节约阅读时间,同时知道Vue里面获得数据变化的原理。
  • 发布事件的基础, 事件是如何冒泡的帮助大家了解,信息是如何传输通信的。

源码中数组与对象observe的内部调用顺序

observe对象的内部函数调用顺序: observe->watch->(watchObject/watchArray) -> (convent & conventKey) -> observe

observe数组的内部函数调用顺序: watch -> (watchObject/watchArray) -> (convent & conventKey) -> observe -> watch

下面大致说下这些函数的作用:

  1. observe和watch为入口函数,observe相对与watch需要传Emitter对象,这个Emitter做完跟节点管理’set’事件的接受, 可以看到上面都是 observe -> observe, watch -> watch想表达的意思是对象和数组层层递归observe的。
  2. (watchObject/watchArray)添加__emitter__属性,使每层对象或数组可以发布信息(触发事件)
  3. (convent & conventKey) conventKey用于set/get的拦截,当set的值为数组或者对象时重复上面的操作,调用watch/observe
  4. 其实还有个方法没有列出来,watchMutation该方法在代码打包的时候就直接执行了,该方法作用在ArrayProxy里重写覆盖pop, push..., 拦截Array.prototype上操作数组元素的方法, 让pop, push, unshift, shift ...这些方法在调用时触发mutate事件。
  5. 这版里面为数组元素直接赋值是不会被监听的,那个时候es6的proxy语法还没出,数组只拦截了prototype上的方法,那怎么办了了,其实可以调用在ArrayProxy里面定义的$set进行赋值, $set在内部又调用了splice

实现原理

observe的实现基础是通过拦截对象的set/get方法,拦截数组的pop, push, shift, unshift, splice方法获得对象和数组发生了变化,通过每层挂载的__emitter__进行信息发布, 顺便说下: 对象与对象,对象与数组之间存在依赖关系,这么处理他们之间的依赖关系了,在我的第二篇文章传输门里的Binding里面有subs, deps,其中的deps为这个Binding的依赖,要这么来收集这些依赖呢?可以通过上面的拦截对象get方法来收集依赖, 可以自行移步看下deps-parser.js的代码。😄😄

发布事件的基础

通过上面的简介,大家知道当数组或对象发生变化时会触发事件(发布消息),触发事件是通过Emitter实现的,Emitter是数组或对象上的属性, 所以在observe前, 为对象添加__emitter__属性,同时可能要为对象或数组添加一些特定的方法(方法是共享的),再者像observe数组是通过拦截push,pop,shift,unshift,splice等方法,所以不能直接修改原型链上的方法,这是我们需要数组和对象代理,数组代理arrayProxy.__proto__ === Array.prototype,对象代理objProxy.__proto__ === Object.prototype,自定义的方法(比如: $add, $delete)放入arrayProxy和objProxy

// 代码为了举例,做了修改
var ArrayProxy = Object.create(Array.prototype)
var ObjProxy = Object.create(Object.prototype)
// 这里添加__emitter__属性
function convert(obj) {
    if (obj.__emitter__) return true
    var emitter = new Emitter()
    def(obj, '__emitter__', emitter) // defineProperty
    // ...
}
function augment(target, src) {
    target.__proto__ = src // 如果有隐式原型链,src里面的key不可枚举
    convert(target)
}
function watchObject(obj) {
    augment(obj, ObjProxy) // obj.__proto__ === ObjProxy, 
    // ...
}
function watchArray(arr) {
    augment(arr, ArrayProxy) // 注释同上
    // ...
}

事件是如何冒泡的

在observer里面数组和对象的冒泡内部实现不同,数组内部子元素的__emitter__的owners里存放了父数组的引用,子元素改变时直接通知owners里存放的父元素,父元素自身触发事件。对象内部子元素改变,事件冒泡,父元素中有层事件代理,事件代理收到了子元素的事件,然后父元素自身在触发事件。下面通过为了举例来清楚说明数组与对象事件冒泡的不同之处,现在有child = {a: 'a'}, 数组A: A = [child], 对象B: B = {child:child}

var child = {a: 'a'}
var A = [child]
var B = {child:child}
var ob = new Emitter()
Observer.watch(A) // observe数组
Observer.observe(B, 'test', ob) // observe对象
// 现在改变child
// A.__emitter__收到一个set
// ob收到3个set事件, test, test.child, test.child.a
child.a = 'b'

当child.a发生改变时, child.__emitter__会触发set事件, child.__emitter__中默认有2个监听set事件的处理函数,一个用于数组,一个用于对象分别在covert和observe函数里。

  1. 如果上层是数组, child.__emitter__.owners为数组存放的为父节点,本例存放的为A
// child.__emitter__.owners 为[A]
// obj为child, child.__emitter__中监听了事件set, 
// 这个处理函数针父节点为数组,代码部分我直接写死了,
// 源码实现细节在convert的propagateChange里面
child.__emitter__.on('set', function(){
// owners 为 [A]
    var owners = child.__emitter__.owners,
    i = owners.length
    while (i--) {
    // A自身触发事件
        owners[i].__emitter__.emit('set', '', '', true)
    }
})
  1. 如果上层是对象,上层的对象的__emitter__中有“层事件代理”,child.__emitter__触发set时,触发“事件代理”里的处理函数,里面的处理函数在触发上层对象的set事件,直接看代码:
// 以B代理child做代码示例,所以我把代码写死了
// 实际源码在observe中
B.__emitter__.proxies = {}
var proxies = B.__emitter__.proxies[‘child’] = {
            set: function (key, val, propagate) {
            // 告诉父对象,这里变化了,不冒泡到上一层
            // 这里的key为 a
            if (key) ob.emit('set', 'child.'+key, val)
            if (propagate) {
                ob.emit('set', 'child', B.child, true)
            }
        },
}
child.__emitter__.on('set', proxies.set)

最后

observer.js里面大约有400多行代码, 我比较笨,反复看了几遍才完全梳理清楚,我心里认为我完全读透了,但是把它写成文章发布,感觉还是差了一些,不能完全表达清楚我想表达的😅😅如果大家有时间的话,建议自己翻阅源码,一定会有不一样的收获💪。

observer.js

observer.test.js

持续更新…❤️

https://juejin.im/post/5e81364a6fb9a03c3a087098

「点点赞赏,手留余香」

    还没有人赞赏,快来当第一个赞赏的人吧!
0 条回复 A 作者 M 管理员
    所有的伟大,都源于一个勇敢的开始!
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论