likes
comments
collection
share

vue源码初解析二----响应式原理

作者站长头像
站长
· 阅读数 15

1.首次渲染过程总结

我们在总结一下之前 vue渲染过程

1.Vue初始化,初始化实例成员和静态成员

2.new Vue()new 创建一个vue实例

3.初始化_init()方法

4.vm.$mount()

  • platforms\web\entry-runtime-with-compiler.js里面

  • 如果没有传递render 把模版编译成render函数

  • compileToFunctions()生成rendewr渲染函数

  • options.render = render

5.vm.$mount()

  • platforms\web\runtime\index.js里面

  • mountComponent()

  • 平台下的重写mount方法 挂载el

6.mountComponent(this,el)

  • core/index/instance/lifecycle.js

  • 判断是否有render选项 如果没有但是传入模版 并且当前是开发环境会发送警告

  • 触发beforeMount

  • 定义updateComponent

    vm_update()

    把虚拟dom转换成真实dom

  • 创建Watcher实例

    传入updateComponent函数

    调用get方法

  • 触发mounted

  • return vm

7.watcher.get()

  • 创建完成watcher会调用一次get()

  • 调用updateComponent()

  • 调用vm_render()创建VNode

    调用render.call(vm_renderProxy,vm.$creatElement())

    调用实例化时的Vue传入的render()

    或者编译template生成的render()

    返回VNode

  • 调用vm_update(vnode)

    调用—patch-挂载真实dom

    记录vm.$el

响应式原理

1.寻找响应式入口

响应式的入口在哪里?通过之前的渲染过程总结得知:

src\core\instance\index.js ------> src\core\instance\init.js ------> src\core\instance\state.js

initMixin(Vue) ------->_init() ------> initState(vm) ------>initData(vm)

// 注册vm的_init方法 初始化vm ------> _init方法 -----> 初始化props,methods,data,computed,watch -----> 初始化Data

所以就是在初始化data的时候调用了observe方法 :

// observe data 调用observe函数将data中的数据转化成响应式

observe(data, true /* asRootData */)

export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

这个意思是判断用户有没有传过来data 有的话就initData(vm) 没有就走下面 observe(vm._data = {}, true /* asRootData */)

我们进去initData(vm)

2.observe(data, true /* asRootData */)

src\core\observer\index.js

export function observe (value: any, asRootData: ?boolean): Observer | void {
     /*检测value 是不是对象或者不是vNode的子类*/
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
//   在Vue.js中给响应式属性添加一个 __ob__ 的属性 证明是响应式的属性 
    /*通过 hasOwn 判断当前的value是否 "__ob__ 的属性"*/
    /*判断value.__ob__是否是Observer的子类*/
    /*条件成立, value已经是响应的式的*/
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (/*以上条件不成立, 说明还不是响应式的数据, 加入响应式系统 */
   /*shouldObserve实际上就是一个开关, 默认值为true*/
    shouldObserve &&
    /* 不是服务端渲染 */
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&  /*满足是数组或者原生Object对象*/
     /*是否可以进行对象属性的扩展*/
    Object.isExtensible(value) &&
     /*不是vm根实例*/
    !value._isVue
  ) {
    // 响应式入口 创建一个Observer对象
    ob = new Observer(value)
  }
    /* asRootData 为根实例*/
  if (asRootData && ob) {
      /*vmCount是ob上的一个属性, 用于统计有多少个属性加入到了响应式系统当中.*/
    ob.vmCount++
  }
  return ob
}

参考:响应式入口

3.响应式入口 ob = new Observer(value)

同目录下:

/**
 * Observer类会通过递归的方式把一个对象的所有属性都转化成可观测对象
 */
export class Observer {
//flow的写法 在函数前面声明
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data
  constructor (value: any) {
     /* 获取到传入过来的value值 */
    this.value = value
      // 这个dep 在 value 的属性新增 删除时候会用到
    //value 如果是数组 也是通过 这里的进行 依赖收集更新的
    this.dep = new Dep()
    /*统计被观察的数据的个数 初始化实例的vmCount*/
    this.vmCount = 0
    // 将实例挂载到观察对象的__ob__属性  目的是让__ob__变成不可枚举的属性 只是记录对象 不需要被观察
    def(value, '__ob__', this)
      /*value不仅是数组,还可以是对象 数组的响应式处理*/
    if (Array.isArray(value)) {
      //这里是对数组原型对象 拦截 处理
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      //为数组的每一个对象创建observer 实例
      this.observeArray(value)
    } else {
    //遍历对象中的每一个属性转换成getter setter  只有object类型的数据才会调用walk将每一个属性转换成getter/setter的形式来侦测变化
      this.walk(value)
    }
  }
​
  /**
   * Walk through all properties and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
    /*将对象类型的属性转为getter/setters*/
         /*defineReactive$$1函数调用多少次, 取决于数据对象的key的个数*/
        /*将数据对象obj, 对应的key当做参数传入, 只传递了两个参数*/
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
         /*defineReactive函数调用多少次, 取决于数据对象的key的个数*/
        /*将数据对象obj, 对应的key当做参数传入, 只传递了两个参数*/
      defineReactive(obj, keys[i])
    }
  }
​
  /**
   * Observe a list of Array items.
   */
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

这里主要的核心就是两个函数 walk 和 defineReactive(obj, keys[i]) 在我之前的文章 简写vue.js 中写observer 实例时可以查看

传送门;简单手写vue.js

在构造函数中,会给 value(data)增加 ob (当前 Observer实例 ) 属性。如果 value 是数组,会调用 observeArray 对数组进行遍历,在调用 observe 方法对每个元素进行观察。如果是对象,调用 walk 遍历 value 去调用 defineReactive 去修改属性的 get/set

只要我们将一个object传到observer中,那么这个object就会变成可观测的、响应式的object

根据 Observer 构造函数的注释, Observer 构造函数的主要作用是把每个要被观察的数据添加到响应式系统, 一旦添加 该数据, 将该数据的属性转为getter/setters, 收集依赖和派发更新

3.1在 Observer 构造函数里面, 调用了 def(value, 'ob', this) 函数
/* def(value, '__ob__', this) */
function def(obj, key, val, enumerable) {
    Object.defineProperty(obj, key, {
        value: val,
        enumerable: !!enumerable,
        writable: true,
        configurable: true
    });
}

def 函数主要是配置 Object.defineProperty 里的 enumerable. 在 Observer 里面调用了 def,没传递 enumerable, 那么就是undefined, !!enumerable两次取反最终为false. 通过 for in 操作不可枚举出 ob 属性

3.2walk (obj: Object)

遍历对象中的每一个属性转换成getter setter

只有object类型的数据才会调用walk将每一个属性转换成getter/setter的形式来侦测变化

  /**
   * Walk through all properties and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
    /*将对象类型的属性转为getter/setters*/
    /*defineReactive函数调用多少次, 取决于数据对象的key的个数*/
    /*将数据对象obj, 对应的key当做参数传入, 只传递了两个参数*/
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
         /*defineReactive函数调用多少次, 取决于数据对象的key的个数*/
        /*将数据对象obj, 对应的key当做参数传入, 只传递了两个参数*/
      defineReactive(obj, keys[i])
    }
  }
3.3defineReactive(obj, keys[i])

1.创建依赖对象实例

2.取属性描述符对象Object.getOwnPropertyDescriptor

3.提供预定义的存取函数

4.判断是否递归观察子对象,并将子对象属性都转换成getter/setter 返回子观察对象

  1. Object.defineProperty

    getter:

    1. 检测 getter 的钩子函数
    2. 判断, Dep.target 存在, 将调用 dep.depend() 函数收集依赖.
    3. 如果子观察目标存在 建立子对象的依赖关系
    4. 返回value

    setter:

    1. 检测 setter 的钩子函数
    2. 判断改变的值 newVal 和 value 是否相等
    3. 判断setter 赋值
    4. 判断子观察目标存在
    5. 通知watcher
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
    //创建依赖对象实例
  const dep = new Dep()
// 获取属性描述符对象Object.getOwnPropertyDescriptor 获取该属性的描述对象, 
// 判断是否有 property, 并且判断该对象是否可以进行配置. 如果不能进行配置, 也就不能为改属性的定义
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }
// 提供预定义的存取函数
  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  } 
// 判断是否递归观察子对象,并将子对象属性都转换成getter/setter 返回子观察对象
  let childOb = !shallow && observe(val)
  /**
 * // 其整个流程大致如下:
 *   Data通过observer转换成了getter/setter的形式来追踪变化。
 *  当外界通过Watcher读取数据时,会触发getter从而将Watcher添加到依赖中。
 *  当数据发生了变化时,会触发setter,从而向Dep中的依赖(即Watcher)发送通知。
 *  Watcher接收到通知后,会向外界发送通知,变化通知到外界后可能会触发视图更新,也有可能触发用户的某个回调函数等。
 */
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
        // 判检测 getter 的钩子函数是否存在, 如果存在就直接调用, 获取返回值. 最终返回 
        // 到此开始进行依赖的收集, 进行 if 判断, Dep.target 存在, 将调用 dep.depend() 函数收集依赖.           如果 childOb 存在, 进行深度的依赖收集.
      const value = getter ? getter.call(obj) : val
    // 收集依赖 存在当前依赖目标 就是watcher对象 就建立依赖
      if (Dep.target) {
        dep.depend()
        // 如果自观察目标存在 建立子对象的依赖关系
        if (childOb) {
          childOb.dep.depend()
        //   如果是数组 特殊处理受技术组对象依赖
          if (Array.isArray(value)) {
​
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
    //   if 判断进行了性能的优化, 如果改变的值 newVal 和 value 相等, 没有必要进行依赖的触发. newVal !== newVal 自身与自身不相等, 只有NaN
      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
      }
      //如果给当前的依赖进行设置newVal, 是一个引用类型, 继续进行 observe, 进行数据监测, 加入到响应式系统
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}
​
3.4Dep 依赖收集实例
1.Dep

先看一下Dep实例 再看看哪里首先用到的 然后是怎么和watcher对象相关的

export default class Dep {
  static target: ?Watcher;
  id: number; //dep 实例id
  subs: Array<Watcher>; //dep实例对应的watcher 就是订阅者的数组
  constructor () {
    this.id = uid++
    this.subs = [] //Watcher实例
  }
  //添加新订阅者
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }
  //移除订阅者
  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }
  //依赖收集
  depend () {
    if (Dep.target) { //存放当前Wather实例
      //将当前 Dep 存放到 Watcher(观察者) 中的依赖中
      Dep.target.addDep(this)
    }
  }
  //通知依赖数组中所有的watcher进行更新操作
  notify () {
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}
​
//Dep.target用来春芳目前正在使用的watcher 全局唯一并且一次也只能有一个watcher被使用。
Dep.target = null
const targetStack = []
export function pushTarget (target: ?Watcher) {
  targetStack.push(target)
  Dep.target = target
}
export function popTarget () {
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]
}
​
2.Dep.target 在 defineReactive(obj, keys[i]) 函数中用到
    // 收集依赖 存在当前依赖目标 就是watcher对象 就建立依赖
      if (Dep.target) {
        dep.depend()
        // 如果自观察目标存在 建立子对象的依赖关系
        if (childOb) {
          childOb.dep.depend()
        //   如果是数组 特殊处理受技术组对象依赖
          if (Array.isArray(value)) {
​
            dependArray(value)
          }
        }
      }
// 收集依赖 存在当前依赖目标 就是watcher对象 就建立依赖  if (Dep.target) {}

Dep.target:就是watcher对象 在哪里创建的

在之前渲染过程调试时 是在mountComponent函数里 new Watcher;

进到watcher类中: get方法的时候有个 pushTarget(this)

export function pushTarget (target: ?Watcher) {
  targetStack.push(target)
  Dep.target = target
}

在这里将目标对象本身赋值给Dep.target 。

在此之前 watcher对象存到栈中 vue2.x之后 每一个组件对应一个watch对象 组件嵌套的时候 就会把外组件存储到栈中

3.dep.depend()

将依赖收集

  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }
  
  /**
   * Add a dependency to this directive.
   */
   //判断是否有depId  存储id 以及添加watcher
  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)
      }
    }
  }
  
    addSub (sub: Watcher) {
    this.subs.push(sub)
  }
4. childOb.dep.depend()

childOb.dep.depend() 这里的是observe的对象里的dep 和现在这个还不一样 或者说和上面这个dep.depend()

childOb.dep.depend 含义是 子对象的属性新增 删除时候会用到 也要发送通知

dep.depend 是每个属性添加依赖

5.数据更新的时候dep发布通知给watcher dep.notify()
 //在set里 发布通知 
 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.sort((a, b) => a.id - b.id)
    }
    //调用每个订阅者的update方法
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
3.5 数组的响应式变化依赖

上述对observer实例的都是对对象的响应式依赖处理 这个是数组的响应式依赖怎么处理

对象的是对Object.definePropertygetter setter

而数组是 :数组数据改变 然后变成响应式 则是vue重写了使数组数据改变的方法 然后让依赖的 _ proto_ 指向重写了方法的对象 相当于代理了 这时候当操作了数组数据改变 就直接在这个对象上可观测到

数组的代码:在Observer类里

    /*value不仅是数组,还可以是对象   数组的响应式处理*/
    // 创建了一个对象, 让该对象的原型对象指向Array.prototype, arrayMethods也就具有了数组的常用的方法 
    // 就是改写了数组原型上的 改变数组的方法
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
        //为数组的每一个对象创建observer 实例
      this.observeArray(value)
    } else {
        //遍历对象中的每一个属性转换成getter setter 只有object类型的数据才会调用walk将每一个属性转换成getter/setter的形式来侦测变化
      this.walk(value)
    }
1.protoAugment(value, arrayMethods)

hasProto:判断有没有 _ proto _ 属性

有的话就执行这个函数 将watcher的proto 指向传过来的 arrayMethods

function protoAugment (target, src: Object) {
  /* eslint-disable no-proto */
  target.__proto__ = src
  /* eslint-enable no-proto */
}
2.arrayMethods

observer/array.js

const arrayProto = Array.prototype
//使用数组的原型 创建一个新的对象
export const arrayMethods = Object.create(arrayProto)
//修改数组元素的方法
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
​
/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function (method) {
  // cache original method
  //保存数组原方法
  const original = arrayProto[method]
  //调用Object.defineProperty() 重新定义修改数组的方法
  def(arrayMethods, method, function mutator (...args) {
  //执行数组的原始方法
    const result = original.apply(this, args)
    //获取数组对象的ob 对象
    const ob = this.__ob__
    //存储数组中新增的元素
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    //对插入的新元素 重新遍历数组元素 设置为响应式数据
    if (inserted) ob.observeArray(inserted)
    // notify change 调用了修改数组的方法  调用数组的ob对象 发送通知
    ob.dep.notify()
    return result
  })
})
​

def:

/**
 * Define a property.
 */
export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  })
}

observeArray

  /**
   * Observe a list of Array items.
   */
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
3.copyAugment(value, arrayMethods, arrayKeys)

arrayKeys:就是那些重写的数组的方法的名字

const arrayKeys = Object.getOwnPropertyNames(arrayMethods)

def函数 给当前的数组对象重新定义我们修改的方法

/**
 * Augment a target Object or Array by defining
 * hidden properties.
 */
/* istanbul ignore next */
function copyAugment (target: Object, src: Object, keys: Array<string>) {
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i]
    //调用Object.defineProperty() 重新定义修改数组的方法
    def(target, key, src[key])
  }
}

4.watcher类

watcher分为三种: Computed Watcher(计算属性)、用户Watcher(侦听器)、渲染Watcher

1.前两种都是在init初始化的时候实现的

2.渲染Watcher的创建时机: vue首次渲染的时候 在 src/core/instance/lifecycle.js mountComponent函数

mountComponent 函数里

首次渲染 立即执行的watcher

function mountComponent (

vm: Component,

el: ?Element,

hydrating?: boolean

): Component{}

// we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher 标识是渲染watcher */)
​
​
​
​

参数:

vm:vue实例

updateComponent 对比两个dom差异 并把差异更新到dom上

noop: 在这里是空函数 对渲染watcher无用 对另外两个有用

第四个参数对象:有个before函数 触发生命周期函数

第五个 true 标识是渲染watcher

watcher类
 * 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;
  sync: boolean;
  dirty: boolean;
  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 //是否是渲染watcher
  ) {
    this.vm = vm
    //是渲染watcher 把当前实例记录到实例的_watcher里保存
    if (isRenderWatcher) {
      vm._watcher = this
    }
    //把所以watcher实例都记录到_watchers数组里
    vm._watchers.push(this)
    // options 是否有参数options 与渲染无关
    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 //callback
    this.id = ++uid // uid for batching 唯一标识watcher
    this.active = true
    this.dirty = this.lazy // for lazy watchers
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    // parse expression for getter 判断是否是function
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
    //创建侦听器函数 是字符串  parsePath 返回一个函数获取这个字符串的值  this.getter 记录值
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        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
        )
      }
    }
    //lazy 渲染watcher是false 其他是true 
    this.value = this.lazy
      ? undefined
      : this.get()
  }
​
  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  get () {
  //把当前的watcher保存入栈 赋值给Dep.target 
  //父子组件嵌套的时候先把父组件对应的watcher入栈
  //再去处理子组件的watcher 子组件处理完毕后再把父组件对应的watcher出栈 继续操作
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm) //渲染watcher 执行updateComponent 
    } 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
        //深度监听
      if (this.deep) {
        traverse(value)
      }
      //执行updateComponent完毕后 清理watcher
      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)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        dep.addSub(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.
   */
   // 在dep.notify的时候 通知订阅者更新的方法
   //渲染watcher 会把lazy  sync 默认为false  所以渲染watcher执行queueWatcher方法
  update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
    //把当前watcher放到一个队列里面
      queueWatcher(this)
    }
  }
​
  /**
   * Scheduler job interface.
   * Will be called by the scheduler.
   */
  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.
        isObject(value) ||
        this.deep
      ) {
        // set new value
        const oldValue = this.value
        this.value = value
        if (this.user) {
          const info = `callback for watcher "${this.expression}"`
          invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }
​
  /**
   * Evaluate the value of the watcher.
   * This only gets called for lazy watchers.
   */
  evaluate () {
    this.value = this.get()
    this.dirty = false
  }
​
  /**
   * Depend on all deps collected by this watcher.
   */
  depend () {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }
​
  /**
   * 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.
      if (!this.vm._isBeingDestroyed) {
        remove(this.vm._watchers, this)
      }
      let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this)
      }
      this.active = false
    }
  }
}
queueWatcher (watcher: Watcher) {}
export function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  //判断id  防止watcher被重复处理
  if (has[id] == null) {
    has[id] = true
    //true 表示队列正在被处理
    if (!flushing) {
      queue.push(watcher)
    } else {
      // if already flushing, splice the watcher based on its id
      // if already past its id, it will be run next immediately.
      //根据被处理的时候的id放到合适的位置
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1, 0, watcher)
    }
    // queue the flush 当前队列是否正在被执行
    if (!waiting) {
      waiting = true
        //开发环境直接调用这个函数 遍历所有watcher 调用run方法
      if (process.env.NODE_ENV !== 'production' && !config.async) {
        flushSchedulerQueue()
        return
      }
      nextTick(flushSchedulerQueue)
    }
  }
}
​
flushSchedulerQueue
function flushSchedulerQueue () {
  currentFlushTimestamp = getNow()
    //表示当前正在被处理
  flushing = true
  let watcher, id
​
  // Sort queue before flush.
  // This ensures that:
  // 1. Components are updated from parent to child. (because parent is always 父组件到子组件
  //    created before the child)
  // 2. A component's user watchers are run before its render watcher (because  用户watcher要在渲染watcher之前
  //    user watchers are created before the render watcher)
  // 3. If a component is destroyed during a parent component's watcher run, 组件在父组件之前销毁 就跳过
  //    its watchers can be skipped.
//   排序 根据id 从小到大 watcher创建顺序 父组件到子组件
  queue.sort((a, b) => a.id - b.id)
​
  // do not cache length because more watchers might be pushed 不缓存length
  // as we run existing watchers
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    // 判断是否有before函数 这个函数是渲染watcher才有
    if (watcher.before) {
      watcher.before()
    }
    id = watcher.id
    // 数据处理过后设置为null
    has[id] = null
    watcher.run()
    // in dev build, check and stop circular updates.
    if (process.env.NODE_ENV !== 'production' && has[id] != null) {
      circular[id] = (circular[id] || 0) + 1
      if (circular[id] > MAX_UPDATE_COUNT) {
        warn(
          'You may have an infinite update loop ' + (
            watcher.user
              ? `in watcher with expression "${watcher.expression}"`
              : `in a component render function.`
          ),
          watcher.vm
        )
        break
      }
    }
  }
​
  // keep copies of post queues before resetting state
  const activatedQueue = activatedChildren.slice()
  const updatedQueue = queue.slice()
​
  resetSchedulerState()
​
  // call component updated and activated hooks
  callActivatedHooks(activatedQueue)
  callUpdatedHooks(updatedQueue)
​
  // devtool hook
  /* istanbul ignore if */
  if (devtools && config.devtools) {
    devtools.emit('flush')
  }
}
run()

执行updateComponment

  run () {
    // 标记是否存活
    if (this.active) {
      const value = this.get() //渲染watcher无返回值 执行执行updateComponment
      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.
        isObject(value) ||
        this.deep
      ) {
        // set new value
        const oldValue = this.value
        this.value = value
        if (this.user) {
          const info = `callback for watcher "${this.expression}"`
          invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }

5.数据响应式原理总结

1.响应式入口:initData(vm) -->observe(value)

在初始化data的时候调用了observe方法 :将data中的数据转化成响应式

src\core\instance\index.js ------> src\core\instance\init.js ------> src\core\instance\state.js

initMixin(Vue) ------->_init() ------> initState(vm) ------>initData(vm) ------>observe(value)

2.observe(value)

observe 函数功能:

判断value是否是对象,不是对象直接返回

判断value对象是都有--ob-- 如果有 直接返回 如果没有创建observe对象

返回observer对象

3.Observer 类

src/core/observer/index.js

功能:

给value对象定义不可枚举的--ob--属性,记录当前的observer对象

数组的响应式处理

对象的响应式处理 调用walk方法

4.数组的响应式处理

新建一个对象 arrayMethods,重写会改变原数组的方法:push, pop ,shift ,unshift,splice,sort,reverse 让依赖的--proto--指向这个对象 当操作了数组数据改变 就直接在这个对象上可观测到

5.对象的响应式处理 walk-->defineReactive(obj, keys[i])

src/core/observer/index.js

功能:

为每一个属性创建dep对象,如果当前属性的值是对象,调用observe 递归继续处理响应式

定义getter 收集依赖 dep.depend() 返回属性的值

定义setter 保存新值,新值是对象,调用observe;派发更新 调用的dep.notify()

6.收集依赖

1,在watcher对象的get方法中调用pushTarget记录Dep.target属性

2,访问data中的成员的时候收集依赖,defineReactive的getter中收集依赖

3,把属性队形的watcher对象添加到dep的subs数组中

4,给childOb收集依赖,目的是子对象添加和删除成员时发送通知

7.watcher

dep.notify()在调用watcher 对象的update()方法

queueWatcher()判断watcher是否被处理 如果没有的话添加到queue队列中,并调用flushScheduleQueue()

7.1flushScheduleQueue

触发beforeUpdate钩子函数

调用watcher.run() 就是调用updateComponent run()->get()->getter()->updateComponent

清空上一次依赖

触发actived钩子

触发updated钩子函数

6.Vue.set, vm.$set

静态方法 Vue.set和实例方法 vm.$set 是同一个方法

set方法的定义位于源码的src/core/observer/index.js中。

对于object型数据,当我们向object数据里添加一对新的key/value或删除一对已有的key/value时,Vue是无法观测到的;而对于Array型数据,当我们通过数组下标修改数组中的数据时,Vue也是是无法观测到的。

export function set (target: Array<any> | Object, key: any, val: any): any {
    // 先判断在非生产环境下如果传入的target是否为undefined、null或是原始类型
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
//   判断target是数组 key是否是合法的索引
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    // 取当前数组长度与key这两者的最大值作为数组的新长度
    target.length = Math.max(target.length, key)
    // 数组的splice方法将传入的索引key对应的val值添加进数组。这里注意一点,为什么要用splice方法呢?
    // 还记得我们在介绍Array类型数据的变化侦测方式时说过,
    // 数组的splice方法已经被我们创建的拦截器重写了,也就是说,当使用splice方法向数组内添加元素时,该元素会自动被变成响应式的
    target.splice(key, 1, val)
    return val
  }
//   如果传入的target不是数组,那就当做对象来处理
// 首先判断传入的key是否已经存在于target中,如果存在,表明这次操作不是新增属性,而是对已有的属性进行简单的修改值,那么就只修改属性值即可
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
//   获取到traget的__ob__属性
  const ob = (target: any).__ob__
//   属性是否为true标志着target是否为响应式对象,接着判断如果tragte是 Vue 实例,或者是 Vue 实例的根数据对象,则抛出警告并退出程序,
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
//   判断如果ob属性为false,那么表明target不是一个响应式对象,那么我们只需简单给它添加上新的属性,不用将新属性转化成响应式
  if (!ob) {
    target[key] = val
    return val
  }
//   如果target是对象,并且是响应式,那么就调用defineReactive方法将新属性值添加到target上,
// defineReactive方会将新属性添加完之后并将其转化成响应式,最后通知依赖更新
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}

7.Vue.delete vm.$delete

删除对象的属性,如果对象是响应式的 确保删除能触发更新视图,这个方法主要用于避开Vue不能检测到属性被删除的限制

目标对象不能是一个vue实例或者vue实例的根数据对象

vm.$delete(vm.obj,'msg')

export function del (target: Array<any> | Object, key: any) {
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  //   判断target是数组 key是否是合法的索引
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.splice(key, 1)
    return
  }
  const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid deleting properties on a Vue instance or its root $data ' +
      '- just set it to null.'
    )
    return
  }
//   判断是否有key这个属性 没有直接退出
  if (!hasOwn(target, key)) {
    return
  }
//   有就删除  再判断有没有ob  因为有没有ob是判断是否是响应式的标识
  delete target[key]
  if (!ob) {
    return
  }
//   通过ob发送通知
  ob.dep.notify()
}

8.vm.$watch

vm.$watch(expOrFn,calllback,[options])

src\core\instance\state.js

功能:观察Vue实例变化的一个表达式或者计算属性函数,回调函数得到的参数为新值和旧值表达式只接受监督的键路径 对于更复杂的表达式 用一个函数取代。

expOrFn:要见识的$data中的属性,可以是表达式或者函数

callback:数据变化后执行的函数

函数:就是回调函数

对象 就是具有handler属性(字符串或者函数) 如果该属性为字符串则methods中相应的定义

options 可选选项

deep 布尔类型 深度监听

immediate 布尔类型 是否立即执行一次回调函数

三种Watcher: 计算属性Watcher 、用户Watcher(侦听器、渲染Watcher

创建顺序: 计算属性Watcher 、用户Watcher(侦听器、渲染Watcher

调试watcher 创建顺序 在src/core/observer/watcher.js 构造函数中 this.vm = vm 打断点 然后执行 查看右边断点的调用堆栈 可以看到顺序是 计算属性Watcher 、用户Watcher(侦听器、渲染Watcher

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>watcher</title>
</head>
<body>
  <div id="app">
    {{ reversedMessage }}
    <hr>
    {{ user.fullName }}
  </div><script src="../../dist/vue.js"></script>
  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        message: 'Hello Vue',
        user: {
          firstName: '诸葛',
          lastName: '亮',
          fullName: ''
        }
      },
      //计算属性
      computed: {
        reversedMessage: function () {
          return this.message.split('').reverse().join('')
        }
      },
      //监听器
      watch: {
        // 'user.firstName': function (newValue, oldValue) {
        //   this.user.fullName = this.user.firstName + ' ' + this.user.lastName
        // },
        // 'user.lastName': function (newValue, oldValue) {
        //   this.user.fullName = this.user.firstName + ' ' + this.user.lastName
        // },
        'user': {
          handler: function (newValue, oldValue) {
            this.user.fullName = this.user.firstName + ' ' + this.user.lastName
          },
          deep: true,
          immediate: true
        }
      }
    })
  </script>
</body>
</html>

vue源码初解析二----响应式原理

vue源码初解析二----响应式原理

vue源码初解析二----响应式原理

$watch 源代码:

用户Watcher入口

initState

export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  //计算属性 Watcher入口 
  if (opts.computed) initComputed(vm, opts.computed)
  //这里侦听器的初始调用位置  用户Watcher入口  
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}
initWatch
 
function initWatch (vm: Component, watch: Object) {
  for (const key in watch) {
    const handler = watch[key]
    //判断传过来的参数是不是数组 就是我们自己写的watch可以是数组
    if (Array.isArray(handler)) {
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i])
      }
    } else {
    //createWatcher函数
      createWatcher(vm, key, handler)
    }
  }
}
//
function createWatcher (
  vm: Component,
  expOrFn: string | Function,
  handler: any,
  options?: Object
) {
//是否是原生的对象 回调函数cb和参数options剥离出来
  if (isPlainObject(handler)) {
    options = handler
    handler = handler.handler
  }
  // 回调函数 是不是字符串
  if (typeof handler === 'string') {
    handler = vm[handler]
  }
  
  return vm.$watch(expOrFn, handler, options)
}
 
 
 
 
 
$watch
Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
  //获取Vue实例 this
    const vm: Component = this
    if (isPlainObject(cb)) {
    //判断cb是对象执行createWatcher
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    //标记为用户的watcher 
    options.user = true
    //创建用户的watcher对象
    const watcher = new Watcher(vm, expOrFn, cb, options)
    //判断immediate
    if (options.immediate) {
      const info = `callback for immediate watcher "${watcher.expression}"`
      pushTarget()
      invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
      popTarget()
    }
    // 返回取消监听的方法
    return function unwatchFn () {
      watcher.teardown()
    }
  }

2.计算属性 Watcher

计算属性的时候const computedWatcherOptions = { lazy: true } 把lazy设置成true 就是不立即调用get

//lazy 渲染wacher是false 其他是true 
this.value = this.lazy
  ? undefined
  : this.get()
const computedWatcherOptions = { lazy: true }
​
function initComputed (vm: Component, computed: Object) {
  // $flow-disable-line
  const watchers = vm._computedWatchers = Object.create(null)
  // computed properties are just getters during SSR
  const isSSR = isServerRendering()
​
  for (const key in computed) {
    const userDef = computed[key]
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    if (process.env.NODE_ENV !== 'production' && getter == null) {
      warn(
        `Getter is missing for computed property "${key}".`,
        vm
      )
    }
​
    if (!isSSR) {
      // create internal watcher for the computed property.
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
    }
​
    // component-defined computed properties are already defined on the
    // component prototype. We only need to define computed properties defined
    // at instantiation here.
    if (!(key in vm)) {
      defineComputed(vm, key, userDef)
    } else if (process.env.NODE_ENV !== 'production') {
      if (key in vm.$data) {
        warn(`The computed property "${key}" is already defined in data.`, vm)
      } else if (vm.$options.props && key in vm.$options.props) {
        warn(`The computed property "${key}" is already defined as a prop.`, vm)
      } else if (vm.$options.methods && key in vm.$options.methods) {
        warn(`The computed property "${key}" is already defined as a method.`, vm)
      }
    }
  }
}

9.nextTick

异步更新队列 :在下次DOM更新循环结束之后执行延迟回调,在修改数据之后立即使用这个方法,获取更新后的DOM 异步执行的 批量的

vm.$nextTick()

1.src\core\instance\render.js

初始化的时候 renderMixin--> Vue.prototype.$nextTick

2.src\core\global-api\index.js 静态方法 Vue.nextTick = nextTick

3.在Watcher中 queueWatcher函数里 调用这个nextTick src\core\observer\scheduler.js

都是最后指向这个js :src\core\util\next-tick.js

vm.$nextTick( [callback] )

当我们更新完数据后,此时又想基于更新后的 DOM 状态来做点什么,此时我们就需要使用Vue.nextTick(callback),把基于更新后的DOM 状态所需要的操作放入回调函数callback中,这样回调函数将在 DOM 更新完成后被调用。

export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  // 把cb加上异常处理存入callback 数组中
  callbacks.push(() => {
    if (cb) {
      try {
      //调用cb()
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  //判断队列是否在被处理 准备等同步函数执行完后,就开始执行回调函数队列
  if (!pending) {
    pending = true
    timerFunc()
  }
  // $flow-disable-line   // 如果没有提供回调,并且支持Promise,返回一个Promise
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

timerFunc()

flushCallbacks以Promise.then 微任务的 形式处理的 不是直接调用

let timerFunc
​
//浏览器兼容问题  ios不完全支持Promise 会降低成setTimeout
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
  //微任务
    p.then(flushCallbacks)
 
    if (isIOS) setTimeout(noop)
  }
  //标记 使用的微任务
  isUsingMicroTask = true
  //MutationObserver  监听DOM改变 改变之后 会执行回调函数  这个回调函数以微任务执行的
  //isIE  判断是不是IE MutationObserver在ie10 ie11才支持   不支持的用new MutationObserver
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  // Use MutationObserver where native Promise is not available,
  // e.g. PhantomJS, iOS7, Android 4.4
  // (#6466 MutationObserver is unreliable in IE11)
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
    //标记 使用的微任务
  isUsingMicroTask = true
  //不支持promise 不支持 MutationObserver 使用 setImmediate(ie浏览器 nodejs支持) 类似一个定时器 比setTimeout 性能好
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  // Fallback to setImmediate.
  // Technically it leverages the (macro) task queue,
  // but it is still a better choice than setTimeout.
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  // Fallback to setTimeout.
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

flushCallbacks

// 执行队列中的每一个回调
function flushCallbacks () {
    pending = false     // 重置异步锁
    // 防止出现nextTick中包含nextTick时出现问题,在执行回调函数队列前,提前复制备份并清空回调函数队列
    const copies = callbacks.slice(0)
    callbacks.length = 0
    // 执行回调函数队列
    for (let i = 0; i < copies.length; i++) {
        copies[i]()
    }
}

在浏览器环境中,常见的

  • 宏任务(macro task) 有 setTimeoutMessageChannelpostMessagesetImmediate
  • 微任务(micro task)有MutationObseverPromise.then

nextTick的核心就是timerFunc的处理,优先以微任务,不支持的话就变成宏任务

nextTick触发的时机:

    同一事件循环中的代码执行完毕 -> DOM 更新 -> nextTick callback触发

Vue 中的nextTick 运用的是:Vue 在内部尝试对异步队列使用原生的 Promise.then 和 MutationObserver 方法, (MessageChannel 在2.5+之后替换掉了MutationObserver )如果执行环境不支持,会采用 setTimeout(fn, 0) 代替。

宏任务耗费的时间是大于微任务的,所以在浏览器支持的情况下,优先使用微任务。如果浏览器不支持微任务,使用宏任务;但是,各种宏任务之间也有效率的不同,需要根据浏览器的支持情况,使用不同的宏任务。

Vue中对DOM的更新策略

Vue是异步执行dom更新的,一旦观察到数据变化,Vue就会开启一个队列,然后把在同一个事件循环 (event loop) 当中观察到数据变化的 watcher 推送进这个队列。如果这个watcher被触发多次,只会被推送到队列一次。这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和DOm操作。而在下一个事件循环时,Vue会清空队列,并进行必要的DOM更新。

简单的说就是:Vue 在修改数据后,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。

//改变数据
vm.message = 'changed'//想要立即使用更新后的DOM。这样不行,因为设置message后DOM还没有更新
console.log(vm.$el.textContent) // 并不会得到'changed'//这样可以,nextTick里面的代码会在DOM更新后执行
Vue.nextTick(function(){
    console.log(vm.$el.textContent) //可以得到'changed'
})
​
JS的运行机制

JS 执行是单线程的,它是基于事件循环的。事件循环大致分为以下几个步骤:

  1. 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。

  2. 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。

  3. 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

  4. 主线程不断重复上面的第三步。

主线程的执行过程就是一个 tick,而所有的异步结果都是通过 “任务队列” 来调度。 任务队列中存放的是一个个的任务(task)。 规范中规定 task 分为两大类,分别是宏任务(macro task) 和微任务(micro task),并且每执行完一个个宏任务(macro task)后,都要去清空该宏任务所对应的微任务队列中所有的微任务(micro task),他们的执行顺序如下所示:

for (macroTask of macroTaskQueue) {
    // 1. 处理当前的宏任务
    handleMacroTask();
​
    // 2. 处理对应的所有微任务
    for (microTask of microTaskQueue) {
        handleMicroTask(microTask);
    }
}
事件循环

同步代码执行 -> 查找异步队列,推入执行栈,执行Vue.nextTick[事件循环1] ->查找异步队列,推入执行栈,执行Vue.nextTick[事件循环2]

Vue.nextTick 的原理和用途

Vue源码系列-Vue中文社区

JavaScript 运行机制详解:再谈Event Loop

下一篇 总结一下 js浏览器的进程线程事件循环等专业名词.

事件循环、js线程、进程、同步、异步、宏任务、微任务、任务队列

转载自:https://juejin.cn/post/7179540575459704891
评论
请登录