likes
comments
collection
share

vue源码解析之实例方法

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

上一篇分析了vue的整个生命周期在initMixin函数中;下面继续分析下数据,事件和生命周期相关的方法;

// 源码位置 src/core/instance/index.js

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}
// 整个vue的生命周期
initMixin(Vue)
// 数据相关的方法
stateMixin(Vue)
// 事件相关的方法
eventsMixin(Vue)
// 生命周期相关的方法
lifecycleMixin(Vue)

export default Vue

数据相关的方法

数据相关的方法有三个,分别是set、set、setdelete、$watch;

$watch
// 源码位置 src/core/instance/state.js

Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    const vm: Component = this
    // 是否是一个对象
    if (isPlainObject(cb)) {
      // 是对象通过createWatcher创建watcher
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    // 标注为用户添加
    options.user = true
    // 不是对象,就创建watcher
    const watcher = new Watcher(vm, expOrFn, cb, options)
    // 如果是立即执行
    if (options.immediate) {
      const info = `callback for immediate watcher "${watcher.expression}"`
      // 收集依赖
      pushTarget()
      // 执行cb
      invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
      popTarget()
    }
    // 返回一个取消监听的函数
    return function unwatchFn () {
      // 取消监听
      watcher.teardown()
    }
  }

如果回调函数是对象就通过createWatcher进行创建;

/*
    处理这种形式
    this.$watch('a.b', {
        handler: () {},
        deep: false,
    })
*/
// 是否是一个对象
if (isPlainObject(cb)) {
  // 是对象通过createWatcher创建watcher
  return createWatcher(vm, expOrFn, cb, options)
}

createWatcher内部,获取到对象的handler属性,再次调用$watch进行监听

function createWatcher (
  vm: Component,
  expOrFn: string | Function,
  handler: any,
  options?: Object
) {
  // 是对象
  if (isPlainObject(handler)) {
    // 把值作为options配置项
    options = handler
    // 获取到值中的handler属性
    handler = handler.handler
  }
  // 如果是字符串
  if (typeof handler === 'string') {
    // 直接获取到实例上的这个属性
    handler = vm[handler]
  }
  // 进行监听
  return vm.$watch(expOrFn, handler, options)
}

$watch是提供给用户调用的,因此需要标注下,方便区分是内部调用还是外部调用;接着创建Watcher实例进行监听

options = options || {}
// 标注为用户添加
options.user = true
// 不是对象,就创建watcher
const watcher = new Watcher(vm, expOrFn, cb, options)

如果用户传递了立即执行,通过invokeWithErrorHandling函数执行cb函数;

// 如果是立即执行
if (options.immediate) {
  const info = `callback for immediate watcher "${watcher.expression}"`
  // 收集依赖
  pushTarget()
  // 执行cb
  invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
  popTarget()
}

最后返回一个取消监听的函数,函数内部调用watch的取消函数

// 返回一个取消监听的函数
return function unwatchFn () {
  // 取消监听
  watcher.teardown()
}

发现这个$watch中并没有处理deep属性,是因为deep是在Watcher类内部通过traverse处理的;

// 源码位置 src/core/observer/watcher.js

export default class Watcher {
    ...
    get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    } catch (e) {
      
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      // 处理deep属性
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    return value
  }
}

深度监听的原理其实就是遍历这个对象或数组,读取对象或数组上的每个属性,这样就会触发它的getter,这样就会进行依赖收集和监听,从而达到深度监听;

export function traverse (val: any) {
  _traverse(val, seenObjects)
  seenObjects.clear()
}

function _traverse (val: any, seen: SimpleSet) {
  let i, keys
  // 是否是数组
  const isA = Array.isArray(val)
  if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
    return
  }
  // 防止重复操作
  if (val.__ob__) {
    const depId = val.__ob__.dep.id
    if (seen.has(depId)) {
      return
    }
    seen.add(depId)
  }
  // 是数组进行递归 是对象就遍历对象进行递归
  if (isA) {
    i = val.length
    while (i--) _traverse(val[i], seen)
  } else {
    keys = Object.keys(val)
    i = keys.length
    while (i--) _traverse(val[keys[i]], seen)
  }
}

_traverse内部就通过递归的形式处理对象或数组,以便读取它身上的每个属性;在递归的时候会获取到当前属性(_traverse(val[i], seen))会去触发getter;

$set

vue中给对象添加新的属性或者通过数组的下标修改数组的某个数据,修改之后视图是不会更新;因此vue提供了vm.set或this.$set方法来处理这个问题;

/*
作用:
向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。
它必须用于向响应式对象上添加新属性,因为 `Vue` 无法探测普通的新增属性
注意:
目标对象不能是vue的实例,或vue实例的根数据对象
*/
this.$set(目标对象,属性/索引,值)
// 源码位置 ./src/core/observer/index.js
export function set (target: Array<any> | Object, key: any, val: any): any {
  // 如果是数组并且key是个数组中的有效数字
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    // 重新定义长度
    target.length = Math.max(target.length, key)
    // 修改这个属性
    target.splice(key, 1, val)
    return val
  }
  // 是对象并且这个属性已经在对象上存在了并且不是Object原始属性就直接赋值
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  // 获取到当前实例
  const ob = (target: any).__ob__
  // 如果当前对象是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
  }
  // 如果没有实例 表示不是响应式的直接添加
  if (!ob) {
    target[key] = val
    return val
  }
  // 给这个对象添加属性并且设置为响应式的
  defineReactive(ob.value, key, val)
  // 进行监听
  ob.dep.notify()
  return val
}

首先判断当前对象是不是一个数组,如果是一个数组,就判断key值是否是一个有效数字(大于等于0并且为整数),如果是就比较数组的长度和key的大小,重新定义数组的长度,通过splice进行修改数据;因为splice是重写之后的方法,因此添加的属性具有响应式;

// 如果是数组并且key是个数组中的有效数字
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    // 重新定义长度
    target.length = Math.max(target.length, key)
    // 修改这个属性
    target.splice(key, 1, val)
    return val
  }

不是数组就是对象,并且这个属性已经在这个对象上面了,并且不是Object原始属性,因此直接修改;

// 是对象并且这个属性已经在对象上存在了并且不是Object原始属性就直接赋值
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }

获取到对象的_ob_属性,判断当前对象是否是vue实例或者是否是vue实例上的根数据对象,如果是就直接返回;

// 获取到当前实例
  const ob = (target: any).__ob__
  // 如果当前对象是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_不存在表示是一个普通对象,普通对象不需要添加响应式,因此直接添加属性;

// 如果没有实例 表示不是响应式的直接添加
  if (!ob) {
    target[key] = val
    return val
  }

以上都不是表示对象是一个响应式对象,通过defineReactive给这个对象添加响应式属性,再进行数据监听

// 给这个对象添加属性并且设置为响应式的
defineReactive(ob.value, key, val)
// 进行监听
ob.dep.notify()
return val
$delete

vue中无法监听到删除对象或数组上的一个属性,删除之后视图是不会更新,因此需要vm.delete或this.$delete方法进行删除;

/*
作用:
    删除对象的属性。如果对象是响应式的,确保删除能触发更新视图。
这个方法主要用于避开 `Vue` 不能检测到属性被删除的限制,但是你应该很少会使用它。
注意:
    目标对象不能是vue实例,或实例的根数据对象
*/
vm.$delete(目标对象, 属性或索引 )
// 源码位置 ./src/core/observer/index.js
export function del (target: Array<any> | Object, key: any) {
  // 如果是数组并且是有效的索引就通过splice进行删除
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.splice(key, 1)
    return
  }
  // 获取到对象的实例
  const ob = (target: any).__ob__
  // 如果不是vue实例或者不是vue实例上的根数据对象
  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
  }
  // 如果这个属性不是这个对象的自有属性(原型上属性)直接返回
  if (!hasOwn(target, key)) {
    return
  }
  // 直接删除这个属性
  delete target[key]
  // 如果不是响应式的直接返回
  if (!ob) {
    return
  }
  ob.dep.notify()
}

首先判断是不是数组,并且属性是不是有效数字,是的话就直接通过splice进行删除,因为splice方法,vue中已经进行了重写;通过splice操作的属性具有响应式;

// 如果是数组并且是有效的索引就通过splice进行删除
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.splice(key, 1)
    return
  }

获取到当前对象的vue实例_ob_,判断当前对象是否是vue实例,或者是否是vue实例上的根数据的对象,如果是直接返回;

// 获取到对象的实例
  const ob = (target: any).__ob__
  // 如果不是vue实例或者不是vue实例上的根数据对象
  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
  }

判断这个属性是否是对象上的自有属性,不是直接返回

// 如果这个属性不是这个对象的自有属性(原型上属性)直接返回
if (!hasOwn(target, key)) {
return
}

剩下表示这个对象是一个Object对象,并且属性是自有属性,直接进行删除

// 直接删除这个属性
delete target[key]

如果当前对象不是vue实例表示是一个普通对象(_ob_为true表示vue实例,数据为响应式的),直接返回,否则进行依赖的更新;

// 如果不是响应式的直接返回
if (!ob) {
    return
}
ob.dep.notify()

事件相关的方法

事件相关的方法有四个:$on$once$off$emit

vue源码解析之实例方法

$on
/*
    作用:
    监听实例上的自定义事件,事件可以由$emit触发;回调函数会接收所有传入事件触发函数的额外参数。
    事件名可以是一个字符串,或者是一个数组
*/
this.$on(事件名,回调函数)

eg:
    vm.$on('test', function (msg) {
      console.log(msg) // '8888'
    })
    vm.$emit('test', '8888')
// 源码位置 src/core/instance/event.js
const hookRE = /^hook:/
  Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
    const vm: Component = this
    // 如果传递的事件名是一个数组
    if (Array.isArray(event)) {
      // 进行遍历
      for (let i = 0, l = event.length; i < l; i++) {
        // 通过递归处理每一项
        vm.$on(event[i], fn)
      }
    } else {
      // 是字符串,通过key来判断是否在_events事件中心对象中,如果不存在就初始化为数组,添加到数组中
      (vm._events[event] || (vm._events[event] = [])).push(fn)
      // optimize hook:event cost by using a boolean flag marked at registration
      // instead of a hash lookup
      // 如果是hook开头的事件名,给当前实例添加标记
      if (hookRE.test(event)) {
        vm._hasHookEvent = true
      }
    }
    return vm
  }

首先判断事件名是不是一个数组,如果是数组就遍历这个数据,通过递归的形式处理每一项;

if (Array.isArray(event)) {
  // 进行遍历
  for (let i = 0, l = event.length; i < l; i++) {
    // 通过递归处理每一项
    vm.$on(event[i], fn)
  }
} 

如果不是数组表示就是字符串,通过事件名判断是否在_events上存在,不存在就初始化为一个数组,再把回调添加到数组中;

// 是字符串,通过key来判断是否在_events事件中心对象中,如果不存在就初始化为数组,添加到数组中
(vm._events[event] || (vm._events[event] = [])).push(fn)
// optimize hook:event cost by using a boolean flag marked at registration
// instead of a hash lookup
// 如果是hook开头的事件名,给当前实例添加标记
if (hookRE.test(event)) {
    vm._hasHookEvent = true
}

_events是在生命周期初始化的时候定义的

export function initEvents (vm: Component) {
    vm._events = Object.create(null)
    // ...
}
$emit
/*
  作用: 触发当前实例上的事件。附加参数都会传给监听器回调
*/
this.$emit(事件名,参数)

源码解析

// 源码位置 src/core/instance/event.js
Vue.prototype.$emit = function (event: string): Component {
    const vm: Component = this
    // 从事件中心对象中获取到当前事件名的回调函数
    let cbs = vm._events[event]
    // 如果存在
    if (cbs) {
      // 如果回调函数有多个就复制一份
      cbs = cbs.length > 1 ? toArray(cbs) : cbs
      // 获取到参数并且转成数组
      const args = toArray(arguments, 1)
      const info = `event handler for "${event}"`
      // 遍历回调数组
      for (let i = 0, l = cbs.length; i < l; i++) {
        // 执行回调函数并且传递参数
        invokeWithErrorHandling(cbs[i], vm, args, vm, info)
      }
    }
    return vm
  }

首先从当前实例上的事件中心对象上获取到这个事件名的回调函数;

// 从事件中心对象中获取到当前事件名的回调函数
let cbs = vm._events[event]

获取到传递的参数,并且转成数组

// 如果回调函数有多个就复制一份
cbs = cbs.length > 1 ? toArray(cbs) : cbs
// 获取到参数并且转成数组
const args = toArray(arguments, 1)

遍历回调数组,挨个进行执行

// 遍历回调数组
for (let i = 0, l = cbs.length; i < l; i++) {
    // 执行回调函数并且传递参数
    invokeWithErrorHandling(cbs[i], vm, args, vm, info)
}

invokeWithErrorHandling之前分析过,就是用来执行函数的;

export function invokeWithErrorHandling (
  handler: Function,
  context: any,
  args: null | any[],
  vm: any,
  info: string
) {
  let res
  try {
    res = args ? handler.apply(context, args) : handler.call(context)
    if (res && !res._isVue && isPromise(res) && !res._handled) {
      res.catch(e => handleError(e, vm, info + ` (Promise/async)`))
      // issue #9511
      // avoid catch triggering multiple times when nested calls
      res._handled = true
    }
  } catch (e) {
    handleError(e, vm, info)
  }
  return res
}
$off
/*
    作用:移除自定义事件
    如果没有传递参数则移除该实例上的全部自定义事件
    如果传递了事件名,则移除该事件名的所有回调
    如果传递了事件名和回调函数,则移除该事件名下的该回调函数
*/
this.$off(事件名,回调)

源码解析

// 源码位置 src/core/instance/event.js 
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
    const vm: Component = this
    // all
    // 如果没有传递参数,直接给事件中心重新赋值
    if (!arguments.length) {
      vm._events = Object.create(null)
      return vm
    }
    // array of events
    // 如果事件名为数组
    if (Array.isArray(event)) {
      // 遍历事件名,递归进行处理
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$off(event[i], fn)
      }
      return vm
    }
    // specific event
    // 从事件中心获取到该事件的回调
    const cbs = vm._events[event]
    // 不存在直接返回
    if (!cbs) {
      return vm
    }
    // 如果没有传递回调函数,则直接把事件中心的当前事件全部清空
    if (!fn) {
      vm._events[event] = null
      return vm
    }
    // specific handler
    let cb
    let i = cbs.length
    // 遍历回调数组
    while (i--) {
      cb = cbs[i]
      // 找到当前回调和传递进来的回调是否相同,相同进行删除
      if (cb === fn || cb.fn === fn) {
        cbs.splice(i, 1)
        break
      }
    }
    return vm
  }

首先判断没有传递参数,则直接把当前实例上的事件中心对象初始为空对象(全部删除)

// 如果没有传递参数,直接给事件中心重新赋值
if (!arguments.length) {
  vm._events = Object.create(null)
  return vm
}

判断传递的事件名是否是一个数组,是数组就遍历进行递归处理

// 如果事件名为数组
if (Array.isArray(event)) {
  // 遍历事件名,递归进行处理
  for (let i = 0, l = event.length; i < l; i++) {
    vm.$off(event[i], fn)
  }
  return vm
}

从事件中心对象上获取到对应的回调,如果回调不存在直接返回

// 从事件中心获取到该事件的回调
const cbs = vm._events[event]
// 不存在直接返回
if (!cbs) {
  return vm
}

如果没传递回调函数,则直接把事件中心的对应的事件名的属性值清空

// 如果没有传递回调函数,则直接把事件中心的当前事件全部清空
if (!fn) {
  vm._events[event] = null
  return vm
}

剩下就表示传递了事件名和回调,则遍历事件中心获取对应事件名的回调,从中找出和传递进来的回调一样的回调进行删除

let cb
let i = cbs.length
// 遍历回调数组
while (i--) {
  cb = cbs[i]
  // 找到当前回调和传递进来的回调是否相同,相同进行删除
  if (cb === fn || cb.fn === fn) {
    cbs.splice(i, 1)
    break
  }
}
$once
/*
    作用: 监听一个自定义事件,只触发一次,一旦触发之后就进行移除
*/
this.$once(事件名,回调函数)

源码解析

Vue.prototype.$once = function (event: string, fn: Function): Component {
    const vm: Component = this
    // 定义一个on函数
    function on () {
      // 移除on事件
      vm.$off(event, on)
      // 执行fn回调
      fn.apply(vm, arguments)
    }
    // 给on上添加fn回调,目的就是用户在手动off的时候能够移除掉这个fn
    on.fn = fn
    // 通过$on监听event的on事件
    vm.$on(event, on)
    return vm
}

首先定义了一个on函数,此函数的作用就是做一层代理,当前这个事件触发之后,就会执行这个函数,函数内部首先移除了这个事件的on回调,再执行用户传递进来的fn回调,这样再此调用就没有这个事件的回调了;

// 定义一个on函数
function on () {
  // 移除on事件
  vm.$off(event, on)
  // 执行fn回调
  fn.apply(vm, arguments)
}

把用户传递进来的fn回调放在on的fn上面,目的就是用户手动调用$off的时候能够找到这个事件,因为通过on进行代理了,不这样做在$off中就找不到fn这个函数(使用$off移除事件的时候,$off内部会判断如果回调函数列表中某一项的fn属性与fn相同时,就可以成功移除事件了。)

// 给on上添加fn回调,目的就是用户在手动off的时候能够移除掉这个fn
on.fn = fn

通过$on添加自定义事件,回调为on函数

// 通过$on监听event的on事件
vm.$on(event, on)

生命周期相关的方法

生命周期相关的方法有四个:$mount$forceUpdate$nextTick$destory;
$mount
/*
   作用:
   如果vue实例在实例化的时候没有接收到el选项,它就处于未挂载状态,
   没有关联DOM元素,因此可以手动调用$mount进行挂载;
*/
vm.$mount( [elementOrSelector] )

$mount在生命周期中的模板编译阶段解析过;

$forceUpdate
/*
   作用:
     强制渲染vue实例,只会渲染当前实例和其插槽内的子组件,
     不会渲染全部的子组件
*/
this.$forceUpdate()

源码解析:

// 源码位置 src/core/instance/lifecucle.js
 Vue.prototype.$forceUpdate = function () {
    const vm: Component = this
    if (vm._watcher) {
      vm._watcher.update()
    }
  }

源码很简单,就是调用了_watcher中的update方法进行更新;因为watcher就是数据的依赖,当数据变化的时候就会通知依赖进行更新,也就是执行watcher中的update进行更新;

$nextTick
/*
    作用:
    将回调延迟到下一次DOM更新循环结束执行;在修改数据之后立即使用它,
    然后等待DOM更新;
    注意:
        没有提供回调的时候,在支持Promise的环境中会返回一个Promise
*/
this.$nextTick(回调函数,执行上下文)

vue中对数据的操作是这样描述的:

可能你还没有注意到,Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。

源码解析

// 源码位置 /src/core/util/next-tick.js
export let isUsingMicroTask = false

const callbacks = []
let pending = false

// 执行回调函数的函数
function flushCallbacks () {
  pending = false
  // 复制一份回调函数
  const copies = callbacks.slice(0)
  // 清空callbacks
  callbacks.length = 0
  // 遍历回调函数并且执行它
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

let timerFunc
// 兼容性判断,取决使用哪个异步方法
// promise存在通过promise执行回调
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
  // 如果不是ie并且MutationObserver存在,就听过MutationObserver监听dom的变化,MutationObserver是异步的当dom变化完成之后才会执行它的回调,MutationObserver是微任务
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
    // 修改dom中的内容,触发MutationObserver中的回调
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
  // 如果setImmediate存在就通过setImmediate宏任务执行flushCallbacks
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {// 如果以上都不存在,就通过setTimeout执行flushCallbacks
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

首先定义了两个变量,分别是callbacks存储传递进来的回调函数和pending控制回调执行的开关;

const callbacks = []
let pending = false

定义了一个flushCallbacks函数,这个函数的作用就是用来执行callbacks回调数组的;callbacks.length=0清空数组目的就是防止在nextTick中调用nextTick;

// 执行回调函数的函数
function flushCallbacks () {
  pending = false
  // 复制一份回调函数
  const copies = callbacks.slice(0)
  // 清空callbacks
  callbacks.length = 0
  // 遍历回调函数并且执行它
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

接下来定义了timerFunc变量,这个变量最终是一个函数,函数内部通过微任务或宏任务来异步执行flushCallbacks;从浏览器的兼容性首先选择微任务再选择宏任务,从promise到MutationObserver到setImmediate到setTimeout;

// promise存在通过promise执行回调
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
  // 如果不是ie并且MutationObserver存在,就听过MutationObserver监听dom的变化,MutationObserver是异步的当dom变化完成之后才会执行它的回调,MutationObserver是微任务
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
    // 修改dom中的内容,触发MutationObserver中的回调
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
  // 如果setImmediate存在就通过setImmediate宏任务执行flushCallbacks
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {// 如果以上都不存在,就通过setTimeout执行flushCallbacks
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

再看看nextTick函数

// 源码位置 /src/core/util/next-tick.js
export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  // 把回调放在一个函数中执行,并且把这个函数存入到callbacks数组中
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  // 如果没有执行过,就执行
  if (!pending) {
    // 标记为执行过
    pending = true
    // 执行timerFunc
    timerFunc()
  }
  // $flow-disable-line
  // 如果没有传递回调函数,就返回一个Promise
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

首先通过callbacks存储传递进来的回调函数

callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
})

如果pending为false,表示处于未执行状态,那么就执行timerFunc异步任务;并且标记pending为ture,防止重复执行;

// 如果没有执行过,就执行
if (!pending) {
    // 标记为执行过
    pending = true
    // 执行timerFunc
    timerFunc()
}

最后如果没有传递回调函数,那么直接返回一个promise对象

/ 如果没有传递回调函数,就返回一个Promise
if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
}
// 对应的使用方法
this.$nextTick().then()
$destroy
/*
作用: 完全销毁一个实例,清理它与其它实例的链接,解绑它的全部指令和事件监听器
触发beforeDestroy和destroy钩子
*/
该方法的解析在生命周期篇的销毁阶段已经解析过;
1. 执行beforeDestory钩子
2. 从父级中删除当前实例
3. 从使用到当前watcher的数据中删除当前watcher
4. 从当前watcher中删除到它所依赖的其他watcher;
5. 移除响应式的引用
6. 删除当前组件的虚拟节点
7. 执行destroyed钩子
8. 删除当前组件的所有事件

Vue.prototype.$destroy = function () {
    const vm: Component = this
    // 判断是否正在被销毁,如果是就直接返回
    if (vm._isBeingDestroyed) {
      return
    }
    // 执行beforeDestroy钩子
    callHook(vm, 'beforeDestroy')
    // 设置正在被销毁
    vm._isBeingDestroyed = true
    // remove self from parent
    // 获取到当前实例的父级
    const parent = vm.$parent
    // 如果父级存在并且父级没有被销毁并且不是抽象组件,把当前实例从父级上进行删除
    if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
      remove(parent.$children, vm)
    }
    // teardown watchers
    // 其他数据所依赖的当前实例的watcher进行删除
    if (vm._watcher) {
      vm._watcher.teardown()
    }
    // 把当前实例所依赖的其他watcher进行删除
    let i = vm._watchers.length
    while (i--) {
      vm._watchers[i].teardown()
    }
    // remove reference from data ob
    // frozen object may not have observer.
    // 移除实例内响应式数据的引用
    if (vm._data.__ob__) {
      vm._data.__ob__.vmCount--
    }
    // 给当前实例上添加_isDestroyed属性来表示当前实例已经被销毁
    vm._isDestroyed = true
    // invoke destroy hooks on current rendered tree
    // 将实例的VNode树设置为null
    vm.__patch__(vm._vnode, null)
    // fire destroyed hook
    // 执行destoryed钩子
    callHook(vm, 'destroyed')
    // turn off all instance listeners.
    // 关闭事件监听
    vm.$off()
    // remove __vue__ reference
    // __vue__指向null
    if (vm.$el) {
      vm.$el.__vue__ = null
    }
    // release circular reference (#6759)
    if (vm.$vnode) {
      vm.$vnode.parent = null
    }
  }
转载自:https://juejin.cn/post/7236771018609983548
评论
请登录