别催了~ 正在从服务器偷取页面 . . .

vue2实例方法和全局API


实例方法

实例方法是在Vue的原型Vue.prototype上挂载方法

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

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)
}

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue

数据相关的

与数据相关的实例方法主要有3个vm.$watch、vm.$set、vm.$delete,它们是在stateMixin中挂载到Vue原型上的

import{
  set,
  del
}from '../observer/index'

export function stateMixin(Vue){
  Vue.prototype.$set=set
  Vue.prototype.$delete=del
  Vue.prototype.$watch=function(expOrFn,cb,options){}
 }

vm.$watch

用法:用于观察一个表达式或computed函数在Vue.js实例上的变化。回调函数调用时,会从参数得到新数据和旧数据。
参数:{string|Function}expOrFn
{Function|Object}callback
{Object}[options] ({boolean}deep {boolean}immediate )
返回值:{Function}unwatch
示例:

vm.$watch('a.b.c',function(newVal,oldVal){  })

var unwatch=vm.$watch('a',(newVal,oldVal){ })
unwatch()//取消观察

vm.$watch('a',callback,{
   deep:true,//观察对象内部值的变化
  immediate:true //将表达式的当前值触发回调
})

原理:
选项deep的原理主要是在Watcher的代码里实现,就是除了触发当前被监听的数据收集依赖之外,还要把这个值在内的所有子值都触发一遍收集依赖逻辑。immediate原理就是把回调函数触发一遍

 Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    const vm: Component = this
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    options.user = true
    const watcher = new Watcher(vm, expOrFn, cb, options)
    //immediate为true,cb立刻执行一次
    if (options.immediate) {
      const info = `callback for immediate watcher "${watcher.expression}"`
      pushTarget()
      invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
      popTarget()
    }
    //unwatch就是调用Watcher的teardown方法,将Watcher从它的Dep列表中移除
    return function unwatchFn () {
      watcher.teardown()
    }
  }

vm.$set

用法:在object上设置一个属性,如果object是响应式,Vue.js会保证属性被创建后也是响应式,并且触发视图更新。(这个方法主要是用来避开Vue.js不能侦测属性被添加的限制)
参数:{Object | Array} target
{string | number} key
{any} value
返回值:{Function }unwatch
原理:
原理主要就是分情况讨论,也没有很难懂~~

export function set (target: Array<any> | Object, key: any, val: any): any {
  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)) {
    //如果传递的索引值大于数组的length就让target的length为key
    target.length = Math.max(target.length, key)
    //使用splice方法把val设置到target上,这样数组拦截器会侦测到target发生了变化,会自动把新增
    //的val转换为响应式
    target.splice(key, 1, val)
    return val
  }
  //key已经存在于target中,直接修改就好了
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  const ob = (target: any).__ob__
  //target不能是Vue.js实例或Vue.js实例的根数据对象
  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将新属性
  //转换成getter/setter形式
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}

vm.$delete

用法:对象删除一个属性或者数组删除一个元素,它删除后会自动想依赖发送消息
原理:
原理主要就是分情况讨论,也没有很难懂~~

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)}`)
  }
  //处理数组情况,splice删除元素,数组拦截器会自动向依赖发送通知
  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不是target自身的属性,直接return
  if (!hasOwn(target, key)) {
    return
  }
  delete target[key]
  //非响应式直接返回
  if (!ob) {
    return
  }
  //响应式发通知
  ob.dep.notify()
}

事件相关的

目录src/score/instance/events.js
与事件相关的实例方法主要有4个vm.$on、vm.$off、vm.$once、vm.$emit,它们是在eventsMixin中挂载到Vue原型上的

export function eventsMixin (Vue) {
  Vue.prototype.$on = function (event, fn){
 
  }

  Vue.prototype.$once = function (event, fn) {
   
  }

  Vue.prototype.$off = function (event, fn){
 
  }

  Vue.prototype.$emit = function (event) {
   
  }
}

vm.$on(event,callback)

用法:主要用于监听当前实例上的自定义事件,事件可以由vm.$emit触发。回调函数会接收所有传入事件所触发的函数的额外参数
参数:{string|Array<string>}event
{Function}callback
示例:


vm.$on('try',function(msg){console.log(msg)}
//触发       
vm.$emit('try','hello')

源码:在注册事件的时候将回调函数收集起来,在触发事件的时候将收集起来的回调函数依次调用

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 {
      (vm._events[event] || (vm._events[event] = [])).push(fn)
    }
    return vm
  }

vm._events是一个对象,用来存储事件。当event不为数组时,直接通过vm._events中取出事件列表
vm._events在new Vue()的时候,调用this._init方法初始化创建的,用来存储事件

export function initEvents (vm: Component) {
  //这句创建vm._events
  vm._events = Object.create(null)
  vm._hasHookEvent = false
  // init parent attached events
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}

vm.$off([event,callback])

用法:移除自定义事件监听器(若无提供参数,则移除所有事件监听器;若只提供了事件,则移除该事件的监听器;若同时提供了事件和回调,则只移除这个回调的监听器)
参数:{string|Array<string>}event
{Function}callback
源码:

 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 有回调则移除对应的回调函数
    /*
     这里还有一个重要的问题是,遍历回调函数是从后向前遍历的,因为从前向后遍历的话会导致数组
     向前移动,而i并没有回退,会跳过一个元素
    */
    let cb
    let i = cbs.length
    while (i--) {
      cb = cbs[i]
      if (cb === fn || cb.fn === fn) {
        cbs.splice(i, 1)
        break
      }
    }
    return vm
  }

vm.$emit(event,[…args])

用法:用于触发当前实例上的事件,附加的参数都会传给监听器回调
参数:{string}event
[...args]
源码:实现原理就是从vm._events中取出事件监听器的回调函数列表赋值给cbs,若列表存在即调用每个监听器回调并将参数传给监听器回调

//invokeWithErrorHandling(cbs[i], vm, args, vm, info)后面补充。。。。。
Vue.prototype.$emit = function (event: string): Component {
    const vm: Component = this
    if (process.env.NODE_ENV !== 'production') {
      const lowerCaseEvent = event.toLowerCase()
      if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
        tip(
          `Event "${lowerCaseEvent}" is emitted in component ` +
          `${formatComponentName(vm)} but the handler is registered for "${event}". ` +
          `Note that HTML attributes are case-insensitive and you cannot use ` +
          `v-on to listen to camelCase events when using in-DOM templates. ` +
          `You should probably use "${hyphenate(event)}" instead of "${event}".`
        )
      }
    }
    let cbs = vm._events[event]
    if (cbs) {
      cbs = cbs.length > 1 ? toArray(cbs) : cbs
      /*
      toArray是将类似数组数据转换成真正数组,第二个参数是起始位置,即args是一个除了第一个
      参数之外的所有参数
      */
      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
  }

vm.$once(event,callback)

用法:监听一个自定义事件,但是只触发一次,在第一次触发之后移除监听器
参数:{string|Array<string>}event
{Function}callback
源码:
实现的过程是首先将函数on注册到事件中。当事件被触发时,会执行函数on,on函数里会使用vm.$off移除自定义事件,然后再通过apply执行函数fn,并将参数传递给函数fn

Vue.prototype.$once = function (event: string, fn: Function): Component {
    const vm: Component = this
    function on () {
      vm.$off(event, on)
      fn.apply(vm, arguments)
    }
    on.fn = fn
    vm.$on(event, on)
    return vm
  }

还有个问题是注册的函数是on函数,但是用户触发的函数是fn,在用vm.$off移除事件监听器时会失效。源码上通过on.fn=fn来解决,vm.$off源码上用 if (cb === fn || cb.fn === fn) { }来判断

生命周期相关的

与生命周期相关的实例方法主要有4个vm.$mount、vm.$forceUpdate、vm.$nextTick、vm.$destroy,vm.$forceUpdatevm.$destroy是在lifecycleMixin中挂载到Vue原型上的,vm.$nextTick是在renderMixin中挂载到Vue原型上的,vm.$mount是在跨平台的代码中挂载到Vue原型上的

// 目录 src/score/instance/lifecycle.js
export function lifecycleMixin (Vue: Class<Component>) {
     Vue.prototype.$forceUpdate = function (){
     }
     Vue.prototype.$destroy = function (event, fn) {
     }
}
// 目录 src/score/instance/render.js
export function renderMixin (Vue: Class<Component>) {
  Vue.prototype.$nextTick = function (fn: Function) {
    return nextTick(fn, this)
  }
}
//运行时版(以web平台为例) src/platforms/web/runtime/index.js
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {

}

//完整版(以web平台为例) src/platforms/web/entry-runtime-with-compiler.js
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  
}

vm.forceUpdate()

作用:迫使Vue.js实例重新渲染(注意它仅仅影响实例本身以及插入插槽内容的子组件,而不是所有子组件)
源码:实现该功能只需手动执行一下当前实例watcher的update()方法。因为Vue.js的每一个实例都有一个watcher(Vue.js的一个实例其实就相当于一个组件,而当状态发生变化的时候,会通知到组件级别,然后在组件内部使用虚拟DOM进行重新渲染)

  Vue.prototype.$forceUpdate = function () {
    const vm: Component = this
    if (vm._watcher) {
      vm._watcher.update()
    }
  }

vm.destroy

作用:完全销毁一个实例,它会清理改实例与其他实例的连接,并解绑其全部指令及生命周期,同时会触发beforeDestory和destroyed的钩子函数
源码:

Vue.prototype.$destroy = function () {
    const vm: Component = this
   //判断实例是否正在销毁,因为销毁只能销毁一次,不能反复
    if (vm._isBeingDestroyed) {
      return
    }
    callHook(vm, 'beforeDestroy')
    vm._isBeingDestroyed = true
   /*
     为什么只从一个父组件中移除?
     因为虽然一个组件可以被多个父组件引入,当时其实子组件在不同的父组件中是不同的Vue.js实例,
     所以一个子组件实例其实只有一个父组件
   */
    // remove self from parent  清除当前组件和父组件之间的连接
    const parent = vm.$parent
    if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
      remove(parent.$children, vm)
    }
  
    // teardown watchers 
    /*
      从watcher监听的所有状态的依赖列表中移除watcher
      vm._watcher是在执行new Vue()初始化时创建的,它是监听当前组件用到的所有状态,即这个组
      件内用到的所有状态的依赖列表。当这些状态发生变化时,就会通知vm._watcher,再调用虚拟DOM
      进行重新渲染
    */
    if (vm._watcher) {
      vm._watcher.teardown()
    }
    //这里销毁的是用户使用vm.$watch所创建的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--
    }
    // call the last hook...
    vm._isDestroyed = true
    // invoke destroy hooks on current rendered tree
    vm.__patch__(vm._vnode, null)
    // fire destroyed hook
    callHook(vm, 'destroyed')
    // turn off all instance listeners.移除事件监听器
    vm.$off()
    // remove __vue__ reference
    if (vm.$el) {
      vm.$el.__vue__ = null
    }
    // release circular reference (#6759)
    if (vm.$vnode) {
      vm.$vnode.parent = null
    }
  }
/*
每创建一个watcher实例时,都会将watcher实例添加到vm._watchers中,vm._watchers是在
new Vue()初始化时创建的,所以每当用户使用vm.$watch时,就会向vm._watchers添加一个watcher
实例。所以现在只需遍历vm._watchers,执行每一个watch实例的teardown方法即可
*/
export default class Watcher{
    constructor(vm,expOrFn,cb){
       vm._watchers.push(this)
       ...
    }
}

vm.$mount

用法:如果Vue.js实例在实例化的时候没有收到el选项,则它处于“未挂载”状态,没有关联的DOM元素。可以使用vm.$mount手动挂载一个未挂载的实例。
参数{Element|string}[elementOrSelector]
返回值:vm,即实例本身
示例

var MyComponent=Vue.extend({
      template:'<div>hello</div>'
})

//创建并挂载到#app
new MyComponent().$mount('#app')
//创建并挂载到#app
new MyComponent({el:'#app'})
//没有传入参数,模板会被渲染为文档之外的元素,可以使用DOM的API把它插入文档中
var mycomponent=new MyComponent().$mount()
document.getElementById('app').appendChild(component.$el)

源码
完整版(Vue.js)和运行时版(Vue.runtime.js)的差异是是否有编译器,而是否有编译器的差异主要就在vm.$mount方法的表现形式。

完整版
在完整版中,vm.$mount会首先检查template或el选项所提供的模板是否已经转换为渲染函数(render函数),如果没有,则立即进入编译过程,将模板编译成渲染函数,完成后再进入挂载与渲染的流程中

/*
这种先保存Vue原型的prototype方法,然后用一个新的方法覆盖,在新方法中调用原始方法的做法叫
函数劫持
*/
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
   el = el && query(el)
  ...
  return mount.call(this, el, hydrating)
}
//获取DOM元素
export function query (el: string | Element): Element {
  if (typeof el === 'string') {
    const selected = document.querySelector(el)
    if (!selected) {
      process.env.NODE_ENV !== 'production' && warn(
        'Cannot find element: ' + el
      )
      //获取不到,则创建一个空元素
      return document.createElement('div')
    }
    return selected
  } else {
    //如果不是字符串,则认为el是元素类型,直接返回
    return el
  }
}
/**
 * Get outerHTML of elements, taking care
 * of SVG elements in IE as well.
 */
function getOuterHTML (el: Element): string {
  if (el.outerHTML) {
    return el.outerHTML
  } else {
    const container = document.createElement('div')
    container.appendChild(el.cloneNode(true))
    return container.innerHTML
  }
}
//主要代码
function compileToFunctions (
    template: string,
    options?: CompilerOptions,
    vm?: Component
  ): CompiledFunctionResult {
    //将options属性混合到空对象中,目的是让options成为可选参数
    options = extend({}, options)
   
    // check cache 检查缓存,若已经编译则直接返回
    const key = options.delimiters
      ? String(options.delimiters) + template
      : template
    if (cache[key]) {
      return cache[key]
    }

    // compile  将template编译成代码字符串并储存在compiled的render属性中
    const compiled = compile(template, options)

    // turn code into functions 将代码字符串转换为函数
    const res = {}
    res.render = createFunction(compiled.render, fnGenErrors)
  
    return (cache[key] = res)
  }
}
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)

  /* istanbul ignore if */
  if (el === document.body || el === document.documentElement) {
    process.env.NODE_ENV !== 'production' && warn(
      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )
    return this
  }
 //options是初始化时创建的,它可以访问到实例化Vue.js时,用户设置的一些参数,例如template和render
  const options = this.$options
  //如果没有render选项,需要获取模板并将模板编译成render函数赋值给render选项
  if (!options.render) {
    let template = options.template
    if (template) {
      //template选项可以是字符串模板、#开头的选项符、DOM元素
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          //如果是#开头的选项符,则调用共idToTemplate获取模板
          template = idToTemplate(template)
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`,
              this
            )
          }
        }
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      //如果没有template选项,则使用getOuterHTML方法从用户提供的el选项中获取模板
      template = getOuterHTML(el)
    }
    if (template) {
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }
      //compileToFunctions可以将模板编译成渲染函数并设置在this.$options上
      const { render, staticRenderFns } = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns

      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile end')
        measure(`vue ${this._name} compile`, 'compile', 'compile end')
      }
    }
  }
  return mount.call(this, el, hydrating)
}

运行时版
运行时版的vm.$mount没有编译步骤,它会默认实例上已存在渲染函数,如果不存在会设置一个。并且这个渲染函数在执行时会返会一个空节点VNode,以保证执行时不会因为函数不存在而报错

Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

//将Vue.js实例挂载到DOM元素上,当数据(状态)发生变化时依然可以渲染到指定DOM元素中
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  if (!vm.$options.render) {
     //实例不存在渲染函数,设置默认渲染函数createEmptyVNode,该函数执行后会返回一个注释类型的VNode节点
     vm.$options.render = createEmptyVNode
    if (process.env.NODE_ENV !== 'production') {
        //开发环境下发出警告
    }
  }
  //触发生命周期钩子
  callHook(vm, 'beforeMount')
  
  let updateComponent
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    updateComponent = () => {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } else {
    updateComponent = () => {
      /*
       vm._render 执行渲染函数,得到一份最新的VNode节点树
       vm._update 调用虚拟DOM的patch方法来执行节点的对比与渲染操作
       这一句其实就是一次渲染操作
      */
      vm._update(vm._render(), hydrating)
    }
  }

  /*
    在介绍Watcher那一节中,如果Watcher的第二个参数是一个函数,则会同时观察函数中所读取的Vue.js
    实例上的实例数据
    
  */
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

vm.$nextTick

前置:在 Vue 之中,当状态发生变化,会通知依赖这个状态的所有 watcher,然后触发虚拟 DOM 渲染流程。在 watcher 触发渲染这个操作并不是同步的,它是异步的。Vue 在内部有一个队列,每当需要渲染时,就将要渲染的 watcher 推送到这个队列,下一次事件循环再统一清空队列。
用法:将回调延迟到下次DOM更新周期之后执行。(主要就是开发中有一种场景,就是更新了数据之后,需要对新的DOM做一些操作,但是这时获取不到更新后的DOM)

注意,这个DOM更新周期之后的意思是指下次微任务执行时更新DOM,vm.$nextTick其实就是将回调添加到微任务中(只有在特殊情况下才会降级成宏任务,默认是添加到微任务),所以如果使用vm.$nextTick来获取更新后的DOM,要注意顺序问题,无论是DOM更新后的回调还是是用vm.$nextTick注册的回调,都是向微任务中添加任务,所以是先来先服务。

事实上,更新DOM的回调也是使用vm.$nextTick来注册到微任务里的

示例:

new Vue({
   ...
   methods:{
    example:function(){
    //修改数据
      this.message='change'
      //DOM还没有更新,要在更改数据后才能使用vm.$nextTick注册回调
      //如果是先使用vm.$nextTick注册回调,再修改数据,是获取不到最新的DOM的
      this.$nextTick(function(){
        //DOM更新了
        //this绑定到当前实例
        this.doSomething()
      })
    }
   }
})
//宏任务执行比微任务晚,所以setTimeout里可以获取到最新的DOM
new Vue({
   ...
   methods:{
    example:function(){
      //使用setTimeout向宏任务注册回调
      setTimeout(_ =>{
       //DOM更新了
      },0)
      //修改数据
      this.message='change'
    }
   }
})

原理:
当调用nextTick时,回调函数被添加到callbacks,然后判断任务队列中是否已经添加任务,没添加就需要执行timerFunc向任务队列里添加任务,如果已经添加就无需再添加,无需添加重复任务(被添加到任务队列的任务只需执行一次),然后当任务被执行时,就会依次执行callbacks中的所有回调

//isUsingMicroTask来判断是否使用微任务方法
export let isUsingMicroTask = false
//callbacks存储用户注册的回调
const callbacks = []
//pending来保证每次只向任务队列添加一个任务
let pending = false
//清空callbacks
function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}
//异步执行函数
let timerFunc

 /*
    有Promise、MutationObserver以及setImmediate、setTimeout尝试得到timerFunc的方法,
    优先使用Promise,在Promise不存在的情况下使用MutationObserver,这两个方法都会在
    microtask中执行,会比宏任务更早执行,所以优先使用。如果上述两种方法都不支持的环境
    则会使用setImmediate、setTimeout,这两种属于宏任务,比微任务慢执行
  */
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  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
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  // Fallback to setTimeout.
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}
//ctx是上下文
export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  //cb回调函数push进callbacks函数
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  //pending为false,说明本轮事件循环还没执行过timerFunc()
  if (!pending) {
    pending = true
    timerFunc()
  }
  // $flow-disable-line 没传入回调函数
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

全局API

全局API是直接在Vue上挂载方法

 Vue.extend = function (extendOptions: Object): Function {
   ...
 }

Vue.extend(options)

用法:使用基础Vue构建一个“子类”,参数是一个包含组件选项”的对象
参数:{Object}options
示例:

<div id="test"></div>
//创建构造器
var Profile=Vue.extend({
     template:'<p>{{firstName}}-{{lastName}}</p>',
     data:function(){
        return {
           firstName:'chen',
           lastName:'yiwei'
        }
     }     
})
//创建Profile实例,并挂载在一个元素上
new Profile().$mount('#test')


//结果
<p>chen--yiwei</p>

实现原理:
其实原理并不复杂,就是创建一个Sub函数继承父类,利用了父类的cid缓存子类,依次继承父类的原型、父类的option选项,若有props或者computed就将它们初始化

 export function initExtend (Vue: GlobalAPI) {
/**
   * Each instance constructor, including Vue, has a unique
   * cid. This enables us to create wrapped "child
   * constructors" for prototypal inheritance and cache them.
   */
  Vue.cid = 0
  let cid = 1

  /**
   * Class inheritance
   */
  Vue.extend = function (extendOptions: Object): Function {
    extendOptions = extendOptions || {}
    const Super = this
    const SuperId = Super.cid
    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
    //如果已经缓存过了,就直接返回
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }

    const name = extendOptions.name || Super.options.name
    if (process.env.NODE_ENV !== 'production' && name) {
      validateComponentName(name)
    }

    const Sub = function VueComponent (options) {
      //到时回过头看看 这一句,this._init是new Vue()的时候初始化用的
      this._init(options)
    }
    
    //这三句是实现子类继承父类的原型
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub
    Sub.cid = cid++
    
   
    /*
     * 这两句是实现父类的options选项继承到子类中
     * mergeOptions方法是将两个选项合成一个
     */
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    Sub['super'] = Super

    // For props and computed properties, we define the proxy getters on
    // the Vue instances at extension time, on the extended prototype. This
    // avoids Object.defineProperty calls for each instance created.
    if (Sub.options.props) {
      initProps(Sub)
    }
    if (Sub.options.computed) {
      initComputed(Sub)
    }

    // allow further extension/mixin/plugin usage
    Sub.extend = Super.extend
    Sub.mixin = Super.mixin
    Sub.use = Super.use

    // create asset registers, so extended classes
    // can have their private assets too.
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type]
    })
    // enable recursive self-lookup
    if (name) {
      Sub.options.components[name] = Sub
    }

    // keep a reference to the super options at extension time.
    // later at instantiation we can check if Super's options have
    // been updated.
    Sub.superOptions = Super.options
    Sub.extendOptions = extendOptions
    Sub.sealedOptions = extend({}, Sub.options)

    // cache constructor 缓存Sub,因为反复调用Vue.extend应该返回同一个结果
    cachedCtors[SuperId] = Sub
    return Sub
  }
 }
//初始化props的作用是将key代理到_props
//比如vm.name实际上访问的是Sub.prototype._props.name
function initProps (Comp) {
  const props = Comp.options.props
  for (const key in props) {
    proxy(Comp.prototype, `_props`, key)
  }
}

export function proxy (target: Object, sourceKey: string, key: string) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

//初始化computed就是遍历computed对象,将对象里每一项都定义一遍
function initComputed (Comp) {
  const computed = Comp.options.computed
  for (const key in computed) {
    defineComputed(Comp.prototype, key, computed[key])
  }
}



这个 defineComputed后面补充。。。。。。。

Vue.nextTick([callback,context])

用法:在下次DOM更新循环结束之后执行延迟回调,修改数据之后立即使用这个方法获取更新后的DOM
参数:{Function}[callback]
{Object}[context]
示例:

//修改数据
vm.msg='hello'

//DOM还没有更新
Vue.nextTick(function(){
  //DOM更新后的操作~~
})

//也可以作为一个Promise使用
Vue.nextTick().then(function(){  //DOM更新后的操作~~   })

原理:Vue.nextTick实现原理和vm.$nextTick是一样的

import {nextTick} from '../util/index'

Vue.nextTick=nextTick

Vue.set(target,key,value)

用法:设置对象属性。如果对象是响应式的,确保属性被创建后也是响应式的,同时触发视图更新。这个方法主要用于避开Vue不能检测到属性被添加的限制(注意这个对象不能是Vue.js实例或者Vue.js实例的根数据对象?????)
参数:{Object|Array}target
{string|number}key
{any}value
返回值:设置的值
原理:和vm.delete的实现原理相同

import { set } from '../observer/index'

Vue.set = set

Vue.delete(target,key)

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

import { delete } from '../observer/index'

Vue.delete = delete

Vue.directive/filter/component(id,[definition])

用法:注册或获取全局指令、全局过滤器、全局组件(注册组件时,还会自动使用给定的id设置组件的名称),注意比如全局指令,这里只是注册或者获取全局指令,并不是让其生效,其他两个API也是同样的道理。
参数:{string}id
{Function|Object}[definition]
示例:

//Vue.directive
//注册指令
Vue.directive('my-directive',{
  bind:function(){},
  inserted:function(){},
  update:function(){},
  componentUpdated:function(){},
  unbind:function(){}
})
//注册指令
Vue.directive('my-directive',function(){
//这里的内容会被bind和update调用
})
//getter方法,返回已注册的指令
var myDirective=Vue.directive('my-directive')



//Vue.filter
//注册
Vue.filter('my-filter',function(value){  //返回处理后的值  })
//getter方法,返回已注册的过滤器
var myFilter=Vue.filter('my-filter')
//过滤器可以用在双括号插值和v-bind表达式
{{message|capitalize}}
<div v-bind:id="rawId|formatId"></div>
  

  //Vue.component
  //注册组件,传入一个扩展过的构造器
  Vue.component('my-component',Vue.extend({  }))
  //注册组件,传入一个选项对象(自动调用Vue.extend)
  Vue.component('my-component',{  })
  //获取注册组件(返回构造器)
  var myComponent=Vue.component('my-component')

原理:
Vue.directive、Vue.filter、Vue.component的源码

//  shared/constants.js 
export const ASSET_TYPES = [
  'component',
  'directive',
  'filter'
]
// src/core/global-api/index.js
Vue.options = Object.create(null)
 ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })
Vue.options._base = Vue
initAssetRegisters(Vue)

// src/core/global-api/assets.js
export function initAssetRegisters (Vue: GlobalAPI) {
  /**
   * Create asset registration methods.
   */
  ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      //definition参数不存在,是注册操作
      if (!definition) {
        return this.options[type + 's'][id]
      } else {
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && type === 'component') {
          validateComponentName(id)
        }
        //如果是Object类型,就调用Vue.extend变成Vue的子类
        if (type === 'component' && isPlainObject(definition)) {
          definition.name = definition.name || id
          definition = this.options._base.extend(definition)
        }
        if (type === 'directive' && typeof definition === 'function') {
          definition = { bind: definition, update: definition }
        }
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })
}

Vue.use(plugin)

用法:安装Vue.js插件。
参数:{Object|Function}plugin
原理:
如果插件是一个对象则必须提供install方法,如果插件是一个函数,它会被作为install方法。调用install方法时,会将Vue作为参数传入,install方法被同一个插件多次使用,插件也只会安装一次。

import { toArray } from '../util/index'

export function initUse (Vue: GlobalAPI) {
  Vue.use = function (plugin: Function | Object) {
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    //不重复安装插件
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }
    // toArray方法就是将类数组转成真正的数组,并且得到除了第一个参数外的其他参数
    const args = toArray(arguments, 1)
    //把Vue作为args第一个参数
    args.unshift(this)
    if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
    }
    installedPlugins.push(plugin)
    return this
  }
}

Vue.mixin(mixin)

用法:全局注册一个混入,影响注册之后创建的每个Vue.js实例。插件作者可以使用混入向组件注入自定义行为。(不推荐在应用代码中使用)
参数:{Object}mixin
示例:

Vue.mixin({
   created:function(){
     var myOption=this.$option.myOption,
      if(myOption)
        console.log(myOption)
      }
   }
})

new Vue({
   myOption:'hello'
})
//hello

原理:
实现原理只是将mixinVue自身的options属性合并在一起了

import { mergeOptions } from '../util/index'

export function initMixin (Vue: GlobalAPI) {
  Vue.mixin = function (mixin: Object) {
    this.options = mergeOptions(this.options, mixin)
    return this
  }
}

Vue.compile

作用:编译模板字符串并返回包含渲染函数的对象。(只在完整版中才有效,只有完整版才包含编译器)
参数:{string}template
示例:

var res=Vue.compile('<div><span>{{msg}}</span></div>')

new Vue({
   data:{
    msg:'hello'
   },
  render:res.render
})

源码:

Vue.compile=compileToFunctions

Vue.version

作用:提供字符串形式的Vue.js安装版本号。
示例:

var version=Number(Vue.version.split('.')[0])

if(version===2){
  //2.x.x版本
}else if(version===1){
  //1.x.x版本
}else{
  //不支持的Vue.js版本
}

文章作者: John Doe
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 John Doe !
评论
  目录