美文网首页
vue 源码学习

vue 源码学习

作者: jluemmmm | 来源:发表于2020-12-02 13:27 被阅读0次

主要针对 vue 源码的核心流程进行梳理:

各个流程图生效,晚点再补上,这里是完整的 pdf 链接 文件

整体流程

[图片上传失败...(image-b5c28-1606886798297)]

目录分析

|---- benchmarks 性能测试文件
|---- dist
|---- examples
|---- flow vue 使用 Flow 进行静态类型检查, 这里是静态类型检查的类型声明文件
|---- packages 独立的 vue 相关 npm 包
|---- scripts 构建相关文件和配置
|---- src 源码
|  |---- compile 模板编译代码
|  |---- core 核心代码
|    |---- components 内置组件 keep-alive
|    |---- global-api 全局 API
|    |---- instance 实例化与渲染相关
|    |---- observer observer/dep/watcher
|    |---- util
|    |---- vdom 虚拟dom 相关
|  |---- platforms 平台相关的
|    |---- web
|    |---- weex 
|  |---- server 服务端渲染
|---- test 
|---- types ts 类型声明文件

模板编译与渲染

vm.options.template经过parse生成AST抽象语法树,AST经过编译生成字符串形式的render函数,调用render方法, 生成virtual dom

  • 解析模板字符串,生成 AST

  • 优化 AST

  • 基于 AST 生成字符串形式的 render / staticRenderFns

  • web目录下定义的mount钩子函数中,通过 new Function(code)方法,将字符串形式的渲染函数变为匿名函数形式

  • Vue.prototype._render方法中,调用 render 函数

  • render Watcher中,将Vue.prototype._update方法(传入的第一个参数为vnode,本质上调用vm.__patch__方法[createPatchFunction]),包装成一个匿名函数,vm._update方法传入的第一个参数为vm._render()

    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
    

以一个例子来说明这个流程

<div id="app"></div>
<script src="../dist/vue.js"></script>
<script>
  var app = new Vue({
    el: '#app',
    data: {
      message: 'Hello Vue!',
      lists: [
        {
          name: 'sy',
          age: '88'
        },
        {
          name: 'jane',
          age: '99'
        }
      ]
    },
    methods: {
      func: function(){
        console.log('点击成功')
      }
    },
    template: 
      `<div @click.stop=func style="background-color: red; width: 100px; height: 100px;">
        <p>{{ message }}</p>
        <div v-for="(item, index) in lists" v-bind:class="item.name">
          <span>{{ item.name }}</span> 
          <span>{{ item.age }}</span>   
        </div>
      </div>`
  })
</script> 

解析

调用parseHTML方法,传入templateoptionsparseHTML 中通过正则匹配各标签和各动态及静态属性(如v-bindv-for等)并对其进行处理,将起始标签入栈,匹配到结束标签后将其出栈,最终生成 AST对象。

// 创建 AST 元素
export function createASTElement (
  tag: string,
  attrs: Array<ASTAttr>,
  parent: ASTElement | void
): ASTElement {
  return {
    type: 1,
    tag, // 标签名
    attrsList: attrs, // 属性list
    attrsMap: makeAttrsMap(attrs), // 属性 name/value map
    rawAttrsMap: {},  // 基于attrsList展开形式的原始 attrsList
    parent,
    children: []
  }
}

主要流程:

  • template字符串递归进行正则校验,匹配起始标签
  • 处理起始标签中的属性, 将标签信息以对象形式入栈
  • 基于起始标签创建AST对象,处理pre,v-if,v-for,v-once等属性并对其增强,删除template已经处理过的部分
  • 解析到结束标签后,从栈中寻找最近的结束标签,将匹配结果从栈中弹出,对元素的 key,ref,slot,component,attrsList设置与事件绑定等进行处理, 对AST进行增强
  • 直至 template 为空,返回 AST对象

stack中的存储示例

return stack = [{
  attrsList: [
    {name: "@click.stop", value: "func", start: 5, end: 21},
    {name: "style", value: "background-color: red; width: 100px; height: 100px;", start: 22, end: 81}
  ],
  attrsMap: {@click.stop: "func", style: "background-color: red; width: 100px; height: 100px;"}
  children: []
  end: 82
  parent: undefined
  rawAttrsMap: {@click.stop: {…}, style: {…}}
  start: 0
  tag: "div"
  type: 1
}, {
  attrsList: []
  attrsMap: {}
  children: []
  end: 94
  parent: {type: 1, tag: "div", attrsList: Array(2), attrsMap: {…}, rawAttrsMap: {…}, …}
  rawAttrsMap: {}
  start: 91
  tag: "p"
  type: 1
}]

最终生成的 AST 对象

return ast = {
  attrsList: [{
    end: 21,
    name: "@click.stop",
    start: 5,
    value: "func"
  }],
  attrsMap: { @click.stop: "func", style: "background-color: red; width: 100px; height: 100px;" },
  children: (3) [{…}, {…}, {…}]
  end: 300
  events: {
    click: {
      dynamic: false,
      end: 21,
      modifiers: {stop: true},
      start: 5,
      value: "func",
    }
  }
  hasBindings: true,
  parent: undefined,
  plain: false,
  rawAttrsMap: {
    @click.stop: { name: "@click.stop", value: "func", start: 5, end: 21 },
    style: { name: "style", value: "background-color: red; width: 100px; height: 100px;", start: 22, end: 81 }
  },
  start: 0,
  staticStyle: `{"background-color":"red","width":"100px","height":"100px"}`,
  tag: "div",
  type: 1
}

优化

优化 AST,标记静态节点。遍历生成的AST并检测纯静态节点的子树,对其进行处理

  • 将其提升为常量, 这样不需要在每次重新渲染的时候为它们创建新的节点
  • patch 过程中跳过
function isStatic (node: ASTNode): boolean {
  if (node.type === 2) { // expression 表达式
    return false
  }
  if (node.type === 3) { // text 文本
    return true
  }
  return !!(node.pre || ( //有 pre 标记
    !node.hasBindings && // no dynamic bindings
    !node.if && !node.for && // not v-if or v-for or v-else
    !isBuiltInTag(node.tag) && // not a built-in slot component
    isPlatformReservedTag(node.tag) && // not a component 
    !isDirectChildOfTemplateFor(node) && // 不是template的直接子节点
    Object.keys(node).every(isStaticKey) // 每个key 都是静态的
  ))
}

如果一个元素存在子节点并且所有子节点为静态节点的情况下,标记该节点为静态节点。

编译

基于 AST对象创建渲染函数,最终生成的render函数是一个由with包裹的字符串,在 beforeMount阶段和update 阶段在vm实例上执行。基于 AST创建生成render的函数为:

export function generate (
  ast: ASTElement | void,
  options: CompilerOptions
): CodegenResult {
  const state = new CodegenState(options)
  const code = ast ? genElement(ast, state) : '_c("div")' /* _c 为挂载在 vm 实例上的 createElement 方法, 用于根据 ast 创建 vnode*/
  return {
    render: `with(this){return ${code}}`, /* 将表达式添加到指定的作用域链上 https://www.zhihu.com/question/49929356 */
    staticRenderFns: state.staticRenderFns
  }
}

在这之前需要先了解一些挂载在Vue.prototype上的方法

export function installRenderHelpers (target: any) {
  target._o = markOnce // 用唯一的key标记静态节点
  target._l = renderList // renderList(val, render)  渲染列表
  target._t = renderSlot //  renderSlot(string, fallback, props, bindObject) 渲染插槽
  target._m = renderStatic // 静态树渲染
  target._b = bindObjectProps // 绑定对象属性
  target._v = createTextVNode // 创建文本vnode
  target._e = createEmptyVNode // 创建空vnode
  target._u = resolveScopedSlots // 加载局部插槽
  target._d = bindDynamicKeys // 绑定动态 key
}

主要的执行流程为,基于AST调用 genElement(ast, state),所有已处理过的节点会被不同的操作做上标记。 genElement(ast, state)的主要逻辑为:

el 属性 操作逻辑
static节点 genElement方法推入staticRenderFns,调用_m执行静态树渲染,打上staticProcessed标记
v-once节点 打上onceProcessed标记
①同时为v-if节点,进入v-if处理逻辑;
②为v-for里面的静态节点,genElement(el, state) 返回 _c, 执行后生成 vnode,在调用_o用唯一的key进行标记 ;
③ 都不是,进入静态节点渲染逻辑
v-for节点 调用列表渲染方法_l,将genElement 传入,打上 forProcessed标记
v-if节点 根据el.ifConditions判断正则校验是否通过,通过的情况下调用genElement,打上ifProcessed标记
template节点 调用 genChildren
slot节点 调用插槽渲染方法_t
else 调用getData方法生成字符串形式的 data,包括directives,key,ref,pre,component,attributes,props,event handlers,v-model等,然后调用_c createElement方法创建虚拟 dom

最终生成的code形式为

return code = {
  render: `with(this){return _c('div',{staticStyle:{"background-color":"red","width":"100px","height":"100px"},on:{"click":function($event){$event.stopPropagation();return func($event)}}},[_c('p',[_v(_s(message))]),_v(" "),_l((lists),function(item,index){return _c('div',{class:item.name},[_c('span',[_v(_s(item.name))]),_v(" "),_c('span',[_v(_s(item.age))])])})],2)}`,
  staticRenderFns: []
}

DOM更新机制

更新流程

path方法由 createPatchFunction生成,挂载在 Vue.prototype.__patch__方法上,vm.__patch__方法在vm._update()时机被调用,最终生成的是 dom节点return vnode.elmcreatePatchFunction函数中的逻辑为

[图片上传失败...(image-4c4450-1606886798297)].svg)

createElm逻辑,根据vnode生成dom,并进行挂载

  • 创建 dom 节点 (如 html 标签, 注释节点, 文本节点)
  • 递归创建子元素 dom 节点
  • 调用 created 钩子, 生成标签, 属性, 样式, 事件等
  • 将生成的节点插入父节点
function sameVnode (a, b) { // 判断两个节点是否是同一个节点
  return (
    a.key === b.key && (
      (
        a.tag === b.tag &&
        a.isComment === b.isComment &&
        isDef(a.data) === isDef(b.data) &&
        sameInputType(a, b)
      ) || (
        isTrue(a.isAsyncPlaceholder) &&
        a.asyncFactory === b.asyncFactory &&
        isUndef(b.asyncFactory.error)
      )
    )
  )
}

虚拟dom --vNode结构

export default class VNode {
  tag: string | void;  // 标签名
  data: VNodeData | void; // 当前节点的数据对象
  children: ?Array<VNode>; // 子节点
  text: string | void; // 当前节点的文本, 文本节点或注释节点会有该属性
  elm: Node | void; // 当前虚拟 dom 对应的真实 dom 节点
  ns: string | void; // 当前节点的名字空间
  context: Component | void; // rendered in this component's scope 在这个组件范围内渲染
  key: string | number | void; // 节点标识
  componentOptions: VNodeComponentOptions | void; // 组件的 option 选项
  componentInstance: Component | void; // component instance 组件实例
  parent: VNode | void; // component placeholder node 节点的父节点
}

diff算法

[图片上传失败...(image-10cf7c-1606886798297)].svg)

流程:

  • 前提:const elm = vnode.elm = oldVnode.elmoldCh = oldVnode.childrench = vnode.children

  • vnode是文本节点且 oldVnode.text !== vnode.text,为elm更新 textContent

  • vnode不是文本节点

    • oldChch都存在且不相等,进入updateChildren逻辑
  • ch存在, oldCh不存在

  • checkDuplicateKeys(ch) 检查ch 上是否有重复的key,有的话抛出异常

    • oldVnode 是文本节点,清空elmtextContent
    • elm 添加虚拟节点
  • ch 不存在,oldch存在

  • 移除虚拟节点,触发 destroy 钩子

  • oldVnode 是文本节点,清空elmtextContent

updateChildren 逻辑

const elm = vnode.elm = oldVnode.elm

const oldCh = oldVnode.children // 旧的虚拟dom的子节点
let oldStartIdx = 0 
let oldEndIdx = oldCh.length - 1
let oldStartVnode = oldCh[0] 
let oldEndVnode = oldCh[oldEndIdx] 

const ch = vnode.children // 新的虚拟dom的子节点
let newStartIdx = 0
let newEndIdx = newCh.length - 1
let newStartVnode = newCh[0] 
let newEndVnode = newCh[newEndIdx] 

递归得以进行的条件:oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx

oldStartVnode oldEndVnode
newStartVnode sameVnode --> patchVnode; newStartIdx++; oldStartIdx++ sameVnode --> patchVnode; newStartIdx++; oldEndIdx--; insertBefore(oldStartVnode.elm)
newEndVnode sameVnode --> patchVnode; newEndIdx--; oldStartIdx++; insertBefore(oldEndVnode.elm.next) sameVnode --> patchVnode; newEndIdx++; oldEndIdx++

如果以上逻辑均没有匹配

  • oldCholdStartIdxnewStartIdx之间元素的keyindex为键和值建立map
  • 如果newStartVnodekey,用newStartVnodekeymap中进行寻找
  • 如果newStartVnodekey,在oldCh中逐个进行比较寻找
    • 如果找到,继续进行sameVnode --> patchVnode的逻辑
      • 如果isSameNode,将oldch中的元素置空,insertBefore(oldStartVnode)
      • 如果不是同一个元素,createElm 创建一个新元素并插入到合适的位置。
    • 如果没有找到,createElm 创建一个新元素并插入到合适的位置。

[图片上传失败...(image-672396-1606886798297)]

数据更新

数据更新流程图

[图片上传失败...(image-a6dc9d-1606886798297)]

Class 功能
Observer 数据变动的观察者
Watcher 数据变动的订阅者, 数据的变化会通知到 watcher, 由watcher 进行相应的操作, 如视图更新
Dep 进行依赖收集, 数据变化时, 会被 Observer 观察到, 然后由 Dep 通知到 Watcher

整体流程概述:

  • initData时,为传入的options.data直接添加__ob__属性(Observe的实例),对数组和对象的逻辑不同。

    • 数组,为数组中每个元素添加__ob__属性,重写数组实例__proto__ 指向的原型Array.prototype,只处理push, pop, shift, unshift, splice, sort被调用时的更新,对通过 push, unshift, splice新增的数组元素增加__ob__,通过ob.dep.notify()通知相应的Watcher,因此无法检测通过数组或者索引修改数组元素引起的变化。
    • 对象,对对象上的所有属性,通过 Object.defineProperty对值的 getset操作添加相应回调。get中将栈顶的 Watcher订阅到这个数据持有的depwatcher中,后续数据变化时通知到哪些 watcher做更新;set中当检测到值变化时,调用dep.notify通知到 watcher执行相应的update方法。
  • mount时机调用mountComponent方法,内部初始化一个 render watcher,在 Watcher内部的逻辑中将其入栈,当通过set改变通知到 render watcher,触发vm._update(vm._render(), hydrating) 进行重新渲染

  • 对于计算属性值的更新,再initComputed时调用计算属性上用户定义的get方法,会触发所依赖的响应式变量内部的get方法,将 dep.target 添加到自身的订阅者列表中, 响应式变量 变化时, 会触发 set 函数, 其中的逻辑遍历执行订阅者, 计算属性的值在此时被更新

  • watch的更新使用调用Vue.prototype.$watch方法,方法内部创建一个user watcher,初始化时通过Watcherthis.get进行依赖收集,但不会执行其中的回调,数据变化时触发update方法,同步或者异步地执行其中的回调。

[图片上传失败...(image-88c060-1606886798297)]

Observer

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; 

  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this) /* 为对象定义属性  (obj, key, val, enumerable) __ob__ 指向Observer 实例, 对象和数组都有一个__ob__ 属性 */ 
    if (Array.isArray(value)) {
      if (hasProto) { /* 重写 Array.prototype */
        protoAugment(value, arrayMethods) /* value.__proto__ = arrayMethods ( Object.create(Array.prototype) ) */ 
      } else {
        copyAugment(value, arrayMethods, arrayKeys) 
      }
      this.observeArray(value) /* 为数组中的每一项执行 observe 方法, 添加 Observer 实例 */
      /**
       * 对数组的元素没有进行依赖收集, 是对每一个值添加了一个观察者实例, 如果对数组进行依赖收集, 
       * 每次更新的情况下(不管是通过索引, 长度, 或是数组方法)修改数组, 会大量频繁触发 get 和 set 中定义的方法, 
       * 像forEach 这种调用 触发量更大, 因此指针对数组中的 push, pop, shift, unshift, splice, sort, reverse 方法,
       * 触发后对新增数据新增观察者实例, 调用 ob.dep.notify() 方法通知更新
       */
    } else {
      this.walk(value) /* 对对象的处理, 遍历对象上的自身可枚举属性并将其转化为 getter 和 setter, 对数组中的属性值调用 defineReactive 方法 */
    }
  }
}
 /**
  * Define a reactive property on an Object.
  * 在对象上定义响应式属性 defineReactive(obj, key, val, customSetter, shallow)
  */
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean /* 用于判断是否将子属性转换成响应式的, 默认为 false */
) {
  const dep = new Dep()

  const property = Object.getOwnPropertyDescriptor(obj, key) /* 返回对象上一个自有属性对应的属性描述符 */
  if (property && property.configurable === false) {
    return
  }
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }

  let childOb = !shallow && observe(val) /* 为值创建一个观察者实例, 深度监听 */
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      /* get 中处理的操作是依赖收集 */
      const value = getter ? getter.call(obj) : val
      if (Dep.target) { /* Dep.target 实际上是一个 Watcher */
        dep.depend() 
        /* 把当前的 watcher 订阅到这个数据持有的 dep 的 watchers (subs) 中, 后续数据变化时能通知到哪些 watcher 做准备 */
        if (childOb) { 
          childOb.dep.depend()
          /**
           * 劫持对象属性的变化, 在 getter 的时候, 拿到 Observer 实例中的 dep 实例, 执行 dep.depend 方法
           * 将 watcher 实例添加到 Dep 实例的 subs 属性中
           */
          if (Array.isArray(value)) { /* 值是数组的情况下 */
            dependArray(value) /* 无法向 getter 一样拦截对数组元素的访问, 收集对数组元素的依赖关系, value[i].__ob__.dep.depend()*/
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      if (newVal === value || (newVal !== newVal && value !== value)) { /* 值没有变化, 不触发 */
        return
      }
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal) 
      dep.notify()
      /**
       * 1. notify, 将 subs 中的元素按照 id 排序, 执行 watcher 中的 update 方法
       * 2. update --> run --> get --> 执行用户定义的 getter 函数
       */
    }
  })
}
/**
 *  原生方法上直接根据索引修改数组会触发 set
 *  vue 不能检测两种数组变动: 1. 利用索引设置数组项 2. 直接修改数组长度
 */

Watcher

/**
 * watcher 有三种
 * 1. render watcher 更新视图, $mount 里创建一个render watcher, 调用 mountComponent方法,  expOrFn 回调里传入 vm._update(vm._render(), hydrating)
 * 2. computed watcher 更新计算属性的值与更新计算属性对应的视图 
 * 3. user watcher 开发者注册的 watch 回调函数执行
 * 
 * A watcher parses an expression, collects dependencies, and fires callback when the expression value changes.
 * This is used for both the $watch() api and directives.
 * 观察值解析表达式, 收集依赖, 在表达式更改时触发回调. 
 */
export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  deep: boolean;
  user: boolean;
  lazy: boolean; /* lazy 为 true 时表示是一个 computed watcher */
  sync: boolean; /* user watcher 的 options 对象中的 sync 属性, 默认 false 异步 */
  dirty: boolean; /* 检测当前的 computed watcher 是否需要重新执行的标志, false 不会重新执行 */
  active: boolean;
  deps: Array<Dep>;
  newDeps: Array<Dep>;
  depIds: SimpleSet;
  newDepIds: SimpleSet;
  before: ?Function;
  getter: Function;
  value: any;

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this /* vm._watcher 上是 watcher 实例  */
    }    
    vm._watchers.push(this)
    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user
      this.lazy = !!options.lazy
      this.sync = !!options.sync
      this.before = options.before
    } else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid /*  uid for batching uid用于批处理 */
    this.active = true
    this.dirty = this.lazy /* dirty 的值等于 lazy 的值, for lazy watchers */
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production' /* 非生产环境, 将 expOrFn 转化为字符串, 生产环境为'' */
      ? expOrFn.toString()
      : ''
    if (typeof expOrFn === 'function') { /* 如果是函数, 直接赋给getter */
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn) /* 否则转换成方法赋给getter, 返回闭包 */
      if (!this.getter) { /* watcher 仅接受简单的点分割路径 */
        this.getter = noop
        process.env.NODE_ENV !== 'production' && warn(
          `Failed watching path: "${expOrFn}" ` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }
    this.value = this.lazy /* lazy 为 true 时, 为 computed watcher, value 为 undefined, 否则直接调用 get 方法 */
      ? undefined
      : this.get()
  }

  /**
   * Evaluate the getter, and re-collect dependencies. 评估getter, 重新收集依赖关系
   * 
   * 
   * 1. 将当前的 watcher 作为栈顶的 watcher 推入栈, 通过 dep.target 将当前的 computed watcher 推入栈中, 
   *  此时 Dep.target 就指向栈顶的 computed watcher
   * 
   * 2. 执行 getter 方法, 对于 computed watcher, getter 方法是计算属性的函数, 执行函数将返回的值赋给 value 属性, 
   * 当计算属性的函数执行时,如果内部含有其他响应式变量, 会触发它们内部的 getter, 将第一步放入作为当前栈顶的 
   * computed watcher 存入响应式变量内部的 dep 对象中
   * 
   * 在这一步中, 页面读取计算属性的值时, 会调用 Vue 上的计算属性对应的方法, 方法中如果依赖某个响应式属性的变量
   * 
   * [vue 会维护一个全局的栈用来存放 watcher, 每当触发响应式变量内部的 getter 时, 收集这个全局的栈的顶部的
   * watcher (Dep.target) 将这个 watcher 存入响应式变量内部保存的 dep 中] 
   * 
   * 
   * 3. 将这个 computed watcher 弹出全局的栈, 之所以将 computed watcher 推入又弹出, 是为了让第二步执行内部的 
   * getter 时, 能让计算属性内部依赖的响应式变量收集到这个 computed watcher 
   * 
   */
  get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm) /* 在 vm 实例上执行用户定义的 getter 函数 */
    } catch (e) {
      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: true, 监听对象内部值的变化
       */
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    return value
  }

  /* 向指令添加依赖项 Add a dependency to this directive. */
  addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) { /* 避免重复收集依赖 */
      this.newDepIds.add(id)  /* id 放入newDepIds 中 */
      this.newDeps.push(dep)  /* dep 放入 newDeps 中 */
      if (!this.depIds.has(id)) {
        dep.addSub(this)  /* 向 subs 中push this */
      }
    }
  }

  /* Clean up for dependency collection  清除依赖收集 */
  cleanupDeps () {
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)
      }
    }
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

  /**
   * Subscriber interface. Will be called when a dependency changes.
   * 订阅者 interface, 依赖项更改时将被调用
   */
  update () {
    if (this.lazy) { // 如果 lazy, dirty 为 true
      this.dirty = true 
    } else if (this.sync) { /* 同步 */
      this.run()
    } else { /* 异步 */
      queueWatcher(this)
    }
  }
 
  /* Scheduler job interface. Will be called by the scheduler. 任务调度 interface, 将被调度者使用 */
  run () {
    if (this.active) {
      const value = this.get()
      if (
        value !== this.value ||
        /**
         * Deep watchers and watchers on Object/Arrays should fire even when the value is the same, because the value may have mutated.
         * 即使值相同, 在 对象/ 数组上的 watcher 也应该触发, 值有可能突变
         */
        isObject(value) ||
        this.deep
      ) {
        /*  set new value, 设置新值 */
        const oldValue = this.value
        this.value = value
        if (this.user) {
          try {
            this.cb.call(this.vm, value, oldValue) /* vm 上执行回调, 传入旧值和新值 */
          } catch (e) {
            handleError(e, this.vm, `callback for watcher "${this.expression}"`)
          }
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }

  /* Evaluate the value of the watcher. This only gets called for lazy watchers. 计算 lazy watchers 的值 */
  evaluate () {
    this.value = this.get() /* 执行 get 方法 */
    this.dirty = false /* 将 dirty 设置为 false  */
  }

  /**
   * Depend on all deps collected by this watcher. 依赖这个watcher的所有deps
   * 
   * depend 方法会遍历当前 computed watcher 的 deps 属性, 依次执行 dep 的 depend 方法
   * dep 是每个响应式变量内部保存的一个对象, deps 是所有响应式变量内部 dep 的集合
   * 每个响应式变量内部的 dep 会保存所有的 watcher, 每个 watcher 的 deps 属性会保存所有收集到这个 watcher 的响应式变量内部的 dep 对象
   * 是一个相互依赖的关系
   */
  depend () {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
      /**
       * Vue 在 watcher 中保存 deps, 一方面需要让计算属性能够收集依赖, 一方面可以在注销这个 watcher 的时候知道哪些
       * dep 依赖了这个 watcher, 只需要调用 dep 里面对应的注销方法即可
       */
    }
  }
 
  /* Remove self from all dependencies' subscriber list. 从所有依赖的订阅者列表中删除自身 */
  teardown () {
    if (this.active) {
      /**
       * remove self from vm's watcher list this is a somewhat expensive operation so we skip it if the vm is being destroyed.
       * 从 vm 列表中删除当前 watcher
       */
      if (!this.vm._isBeingDestroyed) {
        remove(this.vm._watchers, this)
      }
      let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this)
      }
      this.active = false
    }
  }
}

Dep

export default class Dep {
  static target: ?Watcher; /* 类通过 static 关键字定义静态方法, 不能在类的实例上调用静态方法, 应该通过类本身调用 */
  id: number;
  subs: Array<Watcher>;  /* 用于存储依赖 (Watcher 实例) 的数组 */

  constructor () {
    this.id = uid++
    this.subs = []
  }

  /* 添加 watcher */
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

  /* 移除 watcher */
  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }

  depend () {
    if (Dep.target) { /* target 是 watcher */
      Dep.target.addDep(this) /* 将 watcher 实例添加到 Dep 实例的 subs 属性中 */
    }
  }

  notify () {
    /* stabilize the subscriber list first 稳定订阅列表 */
    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 在 scheduler 中未排序, 进行排序保证正确的触发顺序
       */
      subs.sort((a, b) => a.id - b.id) // 按照 id 增序排列
    }
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update() /* 调用 watcher 的 update 方法 */
    }
  }
}

/**
 * 全局同时只能有一个的被观察的目标观察者
 * The current target watcher being evaluated. This is globally unique because only one watcher can be evaluated at a time.
 */
Dep.target = null
const targetStack = []

/* 全局观察者指向当前入栈的观察者 */
export function pushTarget (target: ?Watcher) {
  targetStack.push(target)
  Dep.target = target
}


/* 将目标观察者栈中的最后一个观察者弹出, 更新Dep.target */
export function popTarget () {
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]
}

参考资料:

相关文章

网友评论

      本文标题:vue 源码学习

      本文链接:https://www.haomeiwen.com/subject/iqgywktx.html