Vue源码分析-1-(代码结构和响应式系统)

本文深入剖析Vue.js的响应式系统原理,详细介绍了Observer、Watcher和Dep类的作用及交互过程,通过实例说明如何实现数据监听与更新。

本系列文章基于githubVue代码库dev分支最新代码, package.json中描述的版本号为2.5.17-beta.0

目录结构

我们先看看vue的git库中的目录结构:

-scripts
-dist
-flow
-packages
-test
-src
--compiler
--core
--platforms
--server
--sfc
--shared
复制代码
  • scripts构建相关脚本和配置文件

  • dist构建结果

  • flow Flow 需要用到的类型声明(Flow可以为JavaScript做静态类型检查), 不过vue正在往TypeScript上转,估计Vue3里面就没有这个目录了。

  • packages 包含vue-server-renderervue-template-compiler, 会单独发布成npm里的package

  • src 自然就是Vue的源码了,代码是基于ES6Flow的。

    • shared目录下有两个文件, 分别为constantsutil, 分别是一些常量和工具方法,可以先忽略。
    • src 目录中是用来编译但文件(*.vue)组件的逻辑.在 vue-template-compiler中会用到.
    • server目录中是用于服务端渲染的代码
    • platforms中是跟平台有关的代码,每个平台分别有compiler,runtime,server 三部分, 目前有webweex两种
    • core包含通用的跟平台无关的代码,里面又分为
      • observer 包含响应式系统的代码
      • vdom 包含虚拟dom相关的代码
      • instance 包含Vue实例的构造函数和原型方法
      • global-api 全局api
      • components 通用抽象组件,目前只有keep-alive
    • compiler 包含将template编译成render函数的代码

响应式系统

估计vue最深入人心的就是它的响应式系统了吧,那我们就先来看看这一部分。

src/observer中的目录结构如下:

-array.js
-dep.js
-index.js
-scheduler.js
-traverse.js
-watcher.js
复制代码

index.js中有个Observer类,watcher.sDep.js分别提供了Watcher类和Dep类,这三个类就是Vue响应式系统的核心。

Wacher,Dep,Observer的类图如下:

Observer用于表示一个响应式数据对象,比如一个Vue里面的data值,就会为其创建一个Observer对象。

Watcher代表一个监听,会解析表达式收集依赖,以及在表达式中的值发生变化时触发回调。例如,每一个watch属性,都会有一个watcher对象。而wacher对象收集到的依赖就是这个watch监听的属性。 这个依赖我们用Dep对象表示。

举例:

export default {
    data(){
        return {
            a: 1,
            b: 2,
            c: 3,
        }
    },
    watch:{
        a(value, oldValue){
            this.c = this.a + this.b;
        }
    }
}
复制代码

例如我们定义了如上的组件,那么Vue就会使用data的返回值创建一个Observer, 然后为a创建一个watcher, 这个wacher依赖了a这个属性对应的Dep对象。

Observerwatcher可以直接new出来, 那么Dep是如何收集到的呢?

分为这几步:

  1. new Observer(data)的时候,在Observer的构造函数里会为data的每个属性调用Object.defineProperty设置他们的gettersetter,并且创建一个Dep对象。
  • getter中会调用这个属性对应Dep对象的depend方法,将当前这个Dep对象加到当前的Watcher对象中的依赖列表中。
  • setter中会调用这个属性对应Dep对象的notify方法,通知所有订阅了这个DepWacher, Wacher就会重新计算一遍值并调用对应的函数。
  1. new Wacher()的构造函数里,会先获取一下当前的值,在真正去get之前,会通过修改Dep.target这个静态属性值的方式,先把直接设置成当前的Wacher,然后就会触发之前observer通过Object.defineProperty为各个属性设置的那些getter了, 在getter中将对应属性的Dep对象加到当前的Watcher对象中的依赖列表中。
  2. 现在只要修改对应属性,就会触发Dep对象的notify, 然后通知到对应wacher重新获取值,并执行回调。

到这里,Vue响应式系统的原理就基本讲清楚了。不知道大家能看明白不。本着talk is cheap, show you the code的道理,我讲Vue响应系统的源码精简之后,写了最简版的实现如下, 另外源码也可以在我的git库中找到:


class Dep {
    constructor() {
        this.subs = []
    }
    addSub (sub) {
        this.subs.push(sub)
    }
    depend(){
        if (Dep.target) {
            Dep.target.addDep(this)
        }
    }
    notify () {
        for (let i = 0, l = this.subs.length; i < l; i++) {
          this.subs[i].run()
        }
    }
}

class Watcher {
    constructor(vm, key, cb) {
        this.vm = vm
        this.deps = []
        this.key = key
        this.cb = cb
        this.value = this.get()
    }

    addDep(dep){
        this.deps.push(dep);
        dep.addSub(this);
    }
    get(){
        Dep.target = this;
        var value = this.vm[this.key];
        Dep.target = null;
        return value;
    }
    run () {
        const value = this.get()
        if (value !== this.value) {
            const oldValue = this.value
            this.value = value
            this.cb.call(this.vm, value, oldValue);
        }
    }
}


class Observer {
    constructor(obj) {
        const keys = Object.keys(obj)
        for (let i = 0; i < keys.length; i++) {
            defineReactive(obj, keys[i])
        }
    }
}

function defineReactive(obj, key) {
    const dep = new Dep()
    var value = obj[key]

    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
            
            if (Dep.target) {
                dep.depend()
            }
            return value
        },
        set: function reactiveSetter(newVal) {
            value =  newVal
            dep.notify()
        }
    })

}

//使用
var data = {
    a: 1,
    b: 2,
    c: 3,
}

new Observer(data)
new Watcher(data, 'a', function(value, oldValue){
    this.c = value + this.b;
})
data.a = 3
console.log(data.c == 5)

复制代码

预告:

下一篇中我们再看看响应式系统中的一些细节, 并尝试回答如下几个问题:

  1. 为什么对数组数据直接使用this.array[0] = 1的方式赋值不会触发响应? 那怎样才能触发呢?为什么?

  2. 在watch里面直接从this上取对应属性的值,拿到的是新值还是旧值?

  3. 为什么修改了某个属性的值之后,对应的watch不是立马触发?

欢迎关注我的公众号:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值