vue 思考什么是依赖收集,为什么要依赖收集?

本贴最后更新于 1626 天前,其中的信息可能已经天翻地覆

为什么要依赖收集,有如何进行依赖收集,举一个简单的 🌰:

<template>
    <div>
	<span>test1: {{test1}}</span>
	<span>test2: {{test2}}</span>
    </div>
</template>

<script>
export default function() {
    name: 'demo',
    data() {
    	return {
		test1: 'test1',
		test2: 'test2',
		test3: 'test3'
	}
    }
}
</script>

执行这行代码,test3 显然没有使用,但是执行了,这样不好 影响性能,但是为什么会这样呢?

首先列一张 vue 执行流程图:
2.jpeg

Dep
所谓的 dep 就是创建一个类,然后通过闭包的形式把 data 的值保存起来,在使用 data 中声明的值时会调用 getter,进而调用,不会对没有使用的加载,从而加快执行效率。
Dep 源码:

export default class Dep() {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
    this.id = uid++
    this.subs = []
  }
    ...
  // 添加一个可观察的对象
   addSub(sub: Watcher) {
	this.subs.push(sub)
   }
  
  // 移除一个可观察对象
  remove() {/* 代码略 */}

   //依赖收集 
   depend() {
      if (Dep.target) {
         Dep.target.addDep(this)
      }
   }

 // 通知所有订阅者
 notifiy() {
	const subs = this.subs.slice()
	    if (process.env.NODE_ENV !== 'production' && !config.async) {
	      // subs aren't sorted in scheduler if not running async
	      // we need to sort them now to make sure they fire in correct
	      // order
	      subs.sort((a, b) => a.id - b.id)
	    }
	    for (let i = 0, l = subs.length; i < l; i++) {
	      subs[i].update()
	    }
	 }
}

// 取消依赖收集
Dep.target = null

Watcher 对象

export default class Weatcher() {
    // 省略非关键代码

// 获取getter的值并且重新进行依赖收集
  get () {
    /*将自身watcher观察者实例设置给Dep.target,用以依赖收集。*/
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    } catch (e) {
      /*
      执行了getter操作,看似执行了渲染操作,其实是执行了依赖收集。
      在将Dep.target设置为自身观察者实例以后,执行getter操作。
      譬如说现在的的data中可能有a、b、c三个数据,getter渲染需要依赖a跟c,
      那么在执行getter的时候就会触发a跟c两个数据的getter函数,
      在getter函数中即可判断Dep.target是否存在然后完成依赖收集,
      将该观察者对象放入闭包中的Dep的subs中去。
      */
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      /*如果存在deep,则触发每个深层对象的依赖,追踪其变化*/
      if (this.deep) {
        // 递归每一个数组或数组,触发他们的getter,使得对象或数组的每一个成员都被依赖收集,形成一个深的(deep)依赖关系
        traverse(value)
      }
      // 将观察者从实例target栈中取出,并设置给Dep.target
      popTarget()
      this.cleanupDeps()
    }
    return value
  }

  // 添加一个依赖收集关系到Deps集合中
   addDep (dep: Dep) {
     const id = dep.id
      if (!this.newDepIds.has(id)) {
       this.newDepIds.add(id)
       this.newDeps.push(dep)
       if (!this.depIds.has(id)) {
         dep.addSub(this)
      }
    }
  }

// 代码略...
}

进行依赖收集:


/**
 * Define a reactive property on an Object.
 */
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // 在闭包中定义一个dep对象
  const dep = new Dep()

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  /*
   * 如果之前该对象已经预设了getter以及setter函数则将其取出来,新定义的getter/setter中会将其执行,
   * 保证不会覆盖之前已经定义的getter/setter。
   */
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }

  /*对象的子对象递归进行observe并返回子节点的Observer对象*/
  let childOb = !shallow && observe(val)
  // 生成一个对象的新属性,将对象的子对象递归进行observe
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      // 如果原有对象上有getter方法 则执行
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        // 进行依赖收集
        dep.depend()
        if (childOb) {
          /*子对象进行依赖收集,其实就是将同一个watcher观察者实例放进了两个depend中,
           * 一个是正在本身闭包中的depend,另一个是子元素的depend
          */
          childOb.dep.depend()
          if (Array.isArray(value)) {
            /*是数组则需要对每一个成员都进行依赖收集,如果数组的成员还是数组,则递归。*/
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      // 通过 getter 方法获取当前值,与新值进行比较,一致则不需要执行下面的操作
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      if (setter) {
        // 如果原本对象setter拥有则直接执行setter
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      // 新的值重新进行observe,保证数据相应
      childOb = !shallow && observe(newVal)
      // dep对象通知所有观察者
      dep.notify()
    }
  })
}

将观察者 Watcher 实例赋值给全局的 Dep.target,然后触发 render 操作只有被 Dep.target 标记过的才会进行依赖收集。有 Dep.target 的对象会将 Watcher 的实例 push 到 subs 中,在对象被修改触发 setter 操作的时候 dep 会调用 subs 中的 Watcher 实例的 update 方法进行渲染。

  • Vue.js

    Vue.js(读音 /vju ː/,类似于 view)是一个构建数据驱动的 Web 界面库。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。

    261 引用 • 662 回帖 • 2 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • 开源

    Open Source, Open Mind, Open Sight, Open Future!

    393 引用 • 3380 回帖 • 1 关注
  • Ubuntu

    Ubuntu(友帮拓、优般图、乌班图)是一个以桌面应用为主的 Linux 操作系统,其名称来自非洲南部祖鲁语或豪萨语的“ubuntu”一词,意思是“人性”、“我的存在是因为大家的存在”,是非洲传统的一种价值观,类似华人社会的“仁爱”思想。Ubuntu 的目标在于为一般用户提供一个最新的、同时又相当稳定的主要由自由软件构建而成的操作系统。

    123 引用 • 168 回帖
  • 设计模式

    设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

    198 引用 • 120 回帖
  • BND

    BND(Baidu Netdisk Downloader)是一款图形界面的百度网盘不限速下载器,支持 Windows、Linux 和 Mac,详细介绍请看这里

    107 引用 • 1281 回帖 • 22 关注
  • 电影

    这是一个不能说的秘密。

    120 引用 • 597 回帖
  • Dubbo

    Dubbo 是一个分布式服务框架,致力于提供高性能和透明化的 RPC 远程服务调用方案,是 [阿里巴巴] SOA 服务化治理方案的核心框架,每天为 2,000+ 个服务提供 3,000,000,000+ 次访问量支持,并被广泛应用于阿里巴巴集团的各成员站点。

    60 引用 • 82 回帖 • 590 关注
  • Swagger

    Swagger 是一款非常流行的 API 开发工具,它遵循 OpenAPI Specification(这是一种通用的、和编程语言无关的 API 描述规范)。Swagger 贯穿整个 API 生命周期,如 API 的设计、编写文档、测试和部署。

    26 引用 • 35 回帖 • 7 关注
  • 博客

    记录并分享人生的经历。

    270 引用 • 2386 回帖
  • Maven

    Maven 是基于项目对象模型(POM)、通过一小段描述信息来管理项目的构建、报告和文档的软件项目管理工具。

    185 引用 • 318 回帖 • 352 关注
  • SQLServer

    SQL Server 是由 [微软] 开发和推广的关系数据库管理系统(DBMS),它最初是由 微软、Sybase 和 Ashton-Tate 三家公司共同开发的,并于 1988 年推出了第一个 OS/2 版本。

    19 引用 • 31 回帖
  • 心情

    心是产生任何想法的源泉,心本体会陷入到对自己本体不能理解的状态中,因为心能产生任何想法,不能分出对错,不能分出自己。

    59 引用 • 369 回帖
  • 钉钉

    钉钉,专为中国企业打造的免费沟通协同多端平台, 阿里巴巴出品。

    15 引用 • 67 回帖 • 380 关注
  • WordPress

    WordPress 是一个使用 PHP 语言开发的博客平台,用户可以在支持 PHP 和 MySQL 数据库的服务器上架设自己的博客。也可以把 WordPress 当作一个内容管理系统(CMS)来使用。WordPress 是一个免费的开源项目,在 GNU 通用公共许可证(GPLv2)下授权发布。

    45 引用 • 113 回帖 • 321 关注
  • golang

    Go 语言是 Google 推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发 Go,是因为过去 10 多年间软件开发的难度令人沮丧。Go 是谷歌 2009 发布的第二款编程语言。

    491 引用 • 1383 回帖 • 370 关注
  • 以太坊

    以太坊(Ethereum)并不是一个机构,而是一款能够在区块链上实现智能合约、开源的底层系统。以太坊是一个平台和一种编程语言 Solidity,使开发人员能够建立和发布下一代去中心化应用。 以太坊可以用来编程、分散、担保和交易任何事物:投票、域名、金融交易所、众筹、公司管理、合同和知识产权等等。

    34 引用 • 367 回帖 • 2 关注
  • iOS

    iOS 是由苹果公司开发的移动操作系统,最早于 2007 年 1 月 9 日的 Macworld 大会上公布这个系统,最初是设计给 iPhone 使用的,后来陆续套用到 iPod touch、iPad 以及 Apple TV 等产品上。iOS 与苹果的 Mac OS X 操作系统一样,属于类 Unix 的商业操作系统。

    84 引用 • 139 回帖
  • JWT

    JWT(JSON Web Token)是一种用于双方之间传递信息的简洁的、安全的表述性声明规范。JWT 作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以 JSON 的形式安全的传递信息。

    20 引用 • 15 回帖 • 18 关注
  • Sillot

    Sillot (汐洛)孵化自思源笔记,致力于服务智慧新彖乄,具有彖乄驱动、极致优雅、开发者友好的特点
    Github 地址:https://github.com/Hi-Windom/Sillot

    12 引用 • 26 关注
  • Log4j

    Log4j 是 Apache 开源的一款使用广泛的 Java 日志组件。

    20 引用 • 18 回帖 • 36 关注
  • 大数据

    大数据(big data)是指无法在一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合,是需要新处理模式才能具有更强的决策力、洞察发现力和流程优化能力的海量、高增长率和多样化的信息资产。

    89 引用 • 113 回帖
  • Scala

    Scala 是一门多范式的编程语言,集成面向对象编程和函数式编程的各种特性。

    13 引用 • 11 回帖 • 101 关注
  • 导航

    各种网址链接、内容导航。

    37 引用 • 168 回帖
  • React

    React 是 Facebook 开源的一个用于构建 UI 的 JavaScript 库。

    192 引用 • 291 回帖 • 444 关注
  • 尊园地产

    昆明尊园房地产经纪有限公司,即:Kunming Zunyuan Property Agency Company Limited(简称“尊园地产”)于 2007 年 6 月开始筹备,2007 年 8 月 18 日正式成立,注册资本 200 万元,公司性质为股份经纪有限公司,主营业务为:代租、代售、代办产权过户、办理银行按揭、担保、抵押、评估等。

    1 引用 • 22 回帖 • 674 关注
  • Gzip

    gzip (GNU zip)是 GNU 自由软件的文件压缩程序。我们在 Linux 中经常会用到后缀为 .gz 的文件,它们就是 Gzip 格式的。现今已经成为互联网上使用非常普遍的一种数据压缩格式,或者说一种文件格式。

    9 引用 • 12 回帖 • 106 关注
  • OAuth

    OAuth 协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是 oAuth 的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此 oAuth 是安全的。oAuth 是 Open Authorization 的简写。

    36 引用 • 103 回帖 • 6 关注
  • InfluxDB

    InfluxDB 是一个开源的没有外部依赖的时间序列数据库。适用于记录度量,事件及实时分析。

    2 引用 • 47 关注