zoukankan      html  css  js  c++  java
  • 学习 vue 源码 -- 响应式原理

    概述

    由于刚开始学习 vue 源码,而且水平有限,有理解或表述的不对的地方,还请不吝指教。

    vue 主要通过 Watcher、Dep 和 Observer 三个类来实现响应式视图。另外还有一个 scheduler 来进行调度,本次暂时不做讨论。

    Watcher 和 Dep 是订阅者和发布者的关系,每个 Watcher 可以订阅多个 Dep,而每个 Dep 也可以被多个 Watcher 订阅。当 Observer 监听的数据发生改变时,相应的 Dep 就会触发其订阅者 Watcher 更新视图。

    下面通过一个简单的例子来说明其实现流程和原理:

      <div id="app">
          {{someVar}}
      </div>
    
      <script type="text/javascript">
          new Vue({
              el: '#app',
    
              data: {
                  someVar: 'init'
              },
    
              mounted(){
                  setTimeout(() => this.someVar = 'changed', 3000)
              }
    
          })
      </script>
    

    页面初始会显示 "init" 字符串,3秒钟之后,会更新为 "changed" 字符串。

    为了便于理解,将整个流程分为三个阶段:

    1. 初始化data,这个阶段 vue 通过 Observer 监听 data 对象,并将普通的 someVar 属性代理为 getset 属性
    2. 初次挂载el,这个阶段 vue 使用默认的 someVar 数据渲染视图,并将 watcher 添加到 dep 的订阅者列表
    3. someVar 更改触发视图更新,这个阶段 someVar 被赋予了新值,vue 根据 watcher 和 dep 的订阅关系触发视图的更新

    下面我们来逐步分析这三个阶段的流程。

    第一阶段

    主要流程

    new Vue(options) => vm._init(options) => initState(vm) => initData(vm) => observe(data) => new Observer(data) => defineReactive(data, key, value)

    说明

    初始化操作会监听 data 对象,对其每一个属性调用 defineReactive() 方法,将其改造为响应式属性,代码如下(去掉了不影响表述主流程的代码,以便能更清晰的抓住重点):

    /**
     * Define a reactive property on an Object.
     */
    export function defineReactive (
      obj: Object,
      key: string,
      val: any
    ) {
      const dep = new Dep()
    
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter () {
        
          dep.depend()
          return val
        },
        set: function reactiveSetter (newVal) {
          if (newVal === val || (newVal !== newVal && val !== val)) {
            return
          }
          
          val = newVal
          dep.notify()
        }
      })
    }
    

    可以看到,当 get() 方法执行的时候,会调用 dep.depend() ;当 set() 方法执行时,会调用 dep.notify()。后面我们会看到这两个方法的作用。

    第二阶段

    主要流程

    vm.$mount(el) => mountComponent(vm, el) => new Watcher(vm, updateComponent) => watcher.get() => updateComponent() => vm._update(vm._render())

    说明

    vm._render() 调用 vm.$options.render() 方法生成 vnode,vm._update() 方法根据 vnode 对视图做更新。

    vm.$options.render() 方法是在 $mount() 方法中生成的,生成后的代码如下:

    (function() {
        with (this) {
            return _c('div', {
                attrs: {
                    "id": "app"
                }
            }, [_v("
                " + _s(someVar) + "
            ")])
        }
    })
    

    可以看到,代码中会使用到 vm.someVar 属性,而该属性最终会代理到之前定义的响应式属性上,从而调用其 get() 方法,进而调用 dep.depend() 方法将 watcher 添加到订阅者列表。

    dep.depend() => Dep.target.addDep(dep) => dep.addSub(watcher) => dep.subs.push(watcher)

    dep.subs 就是 dep 的订阅者列表,通过这个流程,就建立起了 dep 和 watcher 之间的订阅关系。

    其中,Dep.target 就是当前的 watcher,因为在 watcher.get() 方法执行时,有如下流程:

    watcher.get() => pushTarget(watcher) => Dep.target = watcher

    第三阶段

    3秒之后,vm.someVar 被赋予了新的值,从而最终会调用到响应式属性的 set() 方法,进而调用 dep.notify(),触发 watcher 更新视图。

    主要流程

    dep.notify() => watcher.update() => watcher.run() => watcher.get() => watcher.getter.call(vm, vm) == updateComponent() => vm._update(vm._render())

    说明

    watcher 的 getter() 方法就是第二阶段 new Watcher(vm, updateComponent) 中的 updateComponent 方法,可以看到,通过这个流程,视图得到了更新。

    以上就构成了一个响应式视图模型,其核心是利用 defineProperty() 方法将普通属性转换为带有钩子的 setget 属性,从而实现了数据监听。

  • 相关阅读:
    consul服务注册于发现
    zookeeper注册服务中心
    Eureka自我保护机制
    发现服务
    修改主机名
    Eureka集群原理与搭建
    Eureka服务注册中心
    微服务简单规划
    开启多服务一键启动 run DashBoad,重启idea
    手撕代码:判断二进制串除以3的余数
  • 原文地址:https://www.cnblogs.com/zhaoran/p/7428833.html
Copyright © 2011-2022 走看看