基于Object.defineProperty之后,vue源码解析依赖收集、依赖触发【二】

前言:

上篇已经了解到了,defineProperty的定义、使用场景、解决的问题,接下来
,我们一起来看下vue源码,篇幅有点大,希望你能耐心的看下去,然后有收获,有任何问题,我们可以沟通交流
复制代码

接下来你将会了解到以下内容

1、依赖收集
2、依赖触发
3、响应式
4、观察者
5、侦听器
6、代理
7、订阅者
8、函数渲染
9、虚拟dom的使用
复制代码

源码:下载地址 github.com/vuejs/vue.g…

框架结构

  • 树状目录
.
|-- benchmarks      // 处理复杂业务、大数据
|   |--big-table    // 大数据table
|   |--dbmon        // 数据库监视器 调试器
|   |--reorder-list // 重排列表
|   |--ssr          // 服务器端渲染数据             
|   |--svg          // 渲染大量svg
|   |--uptime       // 大批量更新时间
|-- dist            // 编译后版本
|-- examples        // vue例子demo
|-- flow            // vue的flow检查,声明文件
|-- node_modules    // npm包存放的地方
|-- packages        // 插件 主要有四个vue相关的插件
|-- scripts         // 存放一些npm脚本的配置文件
|   |--git-hooks
|-- src             // 源码目录
|   |-- compiler    // 解析模板相关代码               
|       |-- codegen // 把AST转换成Render函数
|       |-- directives // 解析组件跨平台生成   
|       |-- parser     // 解析模板成AST
|   |-- core
|       |-- components   // 组件相关属性 (main: Keep-Alive)
|       |-- global-api   // 全局API for: Vue.extend, Vue.mixin等
|       |-- instance     // 实例化相关信息,生命周期、事件等【核心】
|       |-- observer     // 响应式数据核心目录【观察者-订阅者】
|       |-- util         // 工具方法
|       |-- vdom         // 虚拟dom相关内容(借鉴Snabbdom)
!      |-- config.js    // 静态配置文件
|       |-- index.js     // 静默导出Vue实例
|   |-- platforms                          
|       |-- web      
|           |--compiler // web端编译器相关代码,用来编译模板render函数
|           |--runtime  // web端运行时相关代码,用来创建vue实例等
|           |--server  //  服务器端渲染
|           |--util  //  相关工具类
|       |-- weex     // 和web端类似,可支持跨终端
|   |-- server       // 服务器端渲染(ssr)相关
|   |-- sfc          // 转换单文件组件(*.vue)
|   |-- shared       // 全局共享的一些方法和常量              
|   |-- test         // 测试用例目录 可以用 npm run dev:test启动
|   |-- types        // typescript的声明 新版本支持          
|   |-- .babelrc.js  // 转义器配置【语法转义器,补丁转义器、jsx和flow(这类转义器,是用来转换JSX语法和移除类型声明的)】 例如:es6代码转换成浏览器能够识别的代码
|   |-- package.json // 配置项目相关信息
复制代码
  • 思维导图

核心代码剖析

1、vue的核心代码,集中在src目录下
2、实例化相关属性、生命周期,以及事件等的入口在core/instance目录下
复制代码

在instance下找到,init.js文件,看看里面都有哪些东西

不难看出一堆初始化的方法,我们大概已经猜出vue所有的
方法、属性、状态、代理、渲染函数、声明周期、依赖注入、观察者、提供者等都是在这个文件里初始化的
复制代码

我们先来看下,defineReactive方法下的Object.defineProperty下,这两个对象get和set,相信你已经猜到了,就是这两个对象实现了依赖收集和依赖触发

export function defineReactive (
  obj: Object,
  key: string
) {
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      // 删减
    },
    set: function reactiveSetter (newVal) {
      // 删减
    }
  })
}
复制代码

依赖收集--get

get代码

get: function reactiveGetter () {
  const value = getter ? getter.call(obj) : val
  if (Dep.target) {
    dep.depend()
    if (childOb) {
      childOb.dep.depend()
      if (Array.isArray(value)) {
        dependArray(value)
      }
    }
  }
  return value
}
复制代码

get 函数做了两件事:

1、正确地返回属性值
2、收集依赖
复制代码

看下代码第一行

const value = getter ? getter.call(obj) : val
复制代码

上面代码做了下面的事情:

1、用三元运算,首先判断是否存在 getter,
2、如果 getter 存在那么直接调用该函数,并以该函数的返回值作为属性的值,保证属性的原有读取操作正常运作。【getter 常量中保存的是属性原有的 get 函数】
3、如果 getter 不存在则使用 val 作为属性的值,返回属性值。
复制代码

核心的代码来了

if (Dep.target) { // 有需要被收集的依赖
  dep.depend() // 此方法执行,依赖被收集
  if (childOb) { // 是否存在__ob__【新创建的属性】
    childOb.dep.depend() // 触发新属性的依赖收集
    if (Array.isArray(value)) {  // 是数组
      dependArray(value) // 逐个触发数组每个元素的依赖收集
    }
  }
}
复制代码

上面代码主要做了下面的事情:

1、判断是否有需要被收集的依赖,如果有走依赖收集逻辑
2、判断是否存在__ob__属性【介绍相应的时候会细说】如果存在,触发新属性的依赖收集
3、如果value是数组,则逐个触发数组每个元素的依赖收集
复制代码
  • 依赖触发--set
在 get 函数中收集了依赖之后,接下来我们就要看一下在 set 函数中是如何触发依赖的,即当属性被修改的时候如何触发依赖。
复制代码

set代码

set: function reactiveSetter (newVal) {
      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.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
复制代码

以上代码我进行六部分说明:

one

const value = getter ? getter.call(obj) : val // 与get函数类似,得到原值
复制代码

two

if (newVal === value || (newVal !== newVal && value !== value)) { // 新值与原值相等 或者 新值与新值不等 老值与老值不等 (NaN === NaN // false 的情况) 满足直接返回
return
}
复制代码

three

if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter() // 非生产环境,打印辅助信息
}
复制代码

four

if (getter && !setter) return // 无需要修改的setter属性,直接返回
复制代码

five

if (setter) { // 有setter直接触发setter方法,并把新值以参数的形式传入该方法体,改变该属性的值
setter.call(obj, newVal)
} else { // 如果没有原始set则直接进行赋改原值
val = newVal
}
// 修改的属性值 setter.call(obj, newVal)
// 修改的setter【PropertyDescriptor】中value
interface PropertyDescriptor {
    configurable?: boolean;
    enumerable?: boolean;
    value?: any;
    writable?: boolean;
    get?(): any;
    set?(v: any): void;
}
复制代码

six

childOb = !shallow && observe(newVal) // 深度观测的前提下,对新值进行观测
dep.notify() // 触发收集的依赖
复制代码

上面主要做下面的事:

1、与get函数类似,得到get中的原始值
2、拿到的新值原始值进行对比,如果相当直接返回 或者新值与新值 原始值原始值进行比对(NaN === NaN // false 的情况)
3、非生产环境下,可以通过customSetter方法打印辅助信息
4、判断属性中是否有原始get方法,如果有并且没有原始的set方法,此时直接返回
5、判断属性中是否有原始set方法,如果有直接触发原始set方法,并把新值以参数的形式传入该方法体,改变该属性的值。保证属性原有的设置操作不受影响,如果没有原始set则直接进行覆盖原始数据
6、假如我们为属性设置的新值是一个数组或者纯对象,那么该数组或纯对象是未被观测的,所以需要对新值进行观测【在深度观测的前提下,新的观测对象重写childOb】
7、最后触发收集的依赖
复制代码

至此,依赖收集和依赖触发就讲完了,具体的依赖收集、触发里面的代码细节,以及

3、响应式
4、观察者
5、侦听器
6、代理
7、订阅者
8、函数渲染
9、虚拟dom的使用
复制代码

待下一篇介绍响应式的时候细说,以上如有不对的地方欢迎批评指正

待续。。。

vue源码解析响应式的流程实现细节(干货)【三】

参考:

1、vue官网

2、vue项目中加入flow类型校验

3、Vue框架构造

转载于:https://juejin.im/post/5c8ce1005188257ade024ed6

你可能感兴趣的:(基于Object.defineProperty之后,vue源码解析依赖收集、依赖触发【二】)