Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vue1.0 源码解析二:理解 vue 的架构 #5

Open
lihongxun945 opened this issue Jul 15, 2018 · 0 comments
Open

vue1.0 源码解析二:理解 vue 的架构 #5

lihongxun945 opened this issue Jul 15, 2018 · 0 comments

Comments

@lihongxun945
Copy link
Owner

lihongxun945 commented Jul 15, 2018

什么是vuejs

这里更多指的是 原版的vuejs的架构,当然tinyvue也是一样的。
我个人对vuejs的定义是:通过 directives 实现 data 和 DOM 的关联的一个框架。

如果用一张图来定义大概是这样

1

更复杂点是这样的:

1

当然这里的data是广义的,包括初始化组件时传入的 data, props methods, computed 等一系列的方法和属性,把这些东西和DOM做关联,做到联动,是vuejs的核心功能。

数据响应化

这里说的是data的响应化,props暂时我们不去管。
/instance/vue.js 开始看,会发现他的除了 init 之外,第一个调用的是 stateMixin

stateMixin主要做了两件事:

  1. proxy, 把对 this.xxx 的访问代理到对 this._data.xxx 上,通过 getter 和 setter 实现的
  2. observe(this._data) 把this._data 变成响应式的数据

observe(this._data) 做了什么呢,它主要是对传入的data设置了 getter 和 setter,这样对data的访问会先触发对应的getter/setter,因此当数据有读写操作的时候都可以检测到

1

可以看一下一个很有意思的细节:
observer/index.js

Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      var value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
        //...
      }
      return value
    }

其中 Dep.target 是一个全局的属性,每当 watcher.get 调用的时候就会把自己设置上去,调用完就取消。
所以如果存在 Dep.target,那么说明当前有一个watcher是从这里取值的,也就是这个watcher依赖这个dep,于是就有了 dep.depend(),会把watcher加入deps依赖。

这是一个很巧妙的设计:在getter中收集依赖。比如 v-text=“name” 那么肯定会调用一次getter 获取name,因此就可以确定 这个指令是依赖 name 属性的。
所以如果以后你面试的时候,面试官问你Vue是怎么收集对数据的依赖的,你应该知道该怎么回答了。

Directive 指令

Directive的实现要比data更复杂一些。我画了一个图:

1

按照Directive的生命周期来说:

  1. 在compile阶段,会通过 el.attributes 来获取所有的attributes,并且通过正则匹配name的方式筛选出类似 v-xxx 的指令,生成一个指令的描述 descriptor,这个描述就包含了name, value, def 等等以后需要创建指令的时候传入的参数
  2. 在 _bindDir 阶段会根据上一步收集的指令描述符来创建指令 new Directive,并最终逐个调用指令的 ‘_bind” 方法
  3. 在指令的 _bind 方法中,会创建 Watcher 监听对应的表达式,比如 v-text=“name” 就会监听 name 表达式,当 this.name 改变时,会由 Observer 通过 dep 来通知 watcher,然后 watcher 调用 directive 的update方法。
    这里有个很有意思的事情,watcher是为了监听一个值而创建的,但是watcher本身会把这个值存起来,所以后面访问的时候不是 vm.name 而是 watcher.value 的形式访问的。

Dep 存在意义就是,他记录了 WatcherObserver 之间的依赖关系,是二者的一个桥梁。

源码中在compile的时候有很多这样的link回调:

  return function compositeLinkFn (vm, el, host, scope, frag) {
    // cache childNodes before linking parent, fix #657
    var childNodes = toArray(el.childNodes)
    // link
    var dirs = linkAndCapture(function compositeLinkCapturer () {
      if (nodeLinkFn) nodeLinkFn(vm, el, host, scope, frag)
      if (childLinkFn) childLinkFn(vm, childNodes, host, scope, frag)
    }, vm)
    return makeUnlinkFn(vm, dirs)
  }

返回的函数套着函数,很容易让人一头雾水,其实这样做主要是因为在 link 的时候依赖 scope 等参数,其实并没有传给compile,因此需要返回一个函数,在需要时候再调用这个函数并传入对应的参数。

关于 vuejs的架构先讲这么多,没有理解没关系,下面我们在动手写 tinyvue 的时候就会懂了。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant