Javascript

Vue(v2.0)全局API的实现原理

本文主要是介绍Vue(v2.0)全局API的实现原理,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

Vue.js最独特的特性之一是看起来并不显眼的响应式系统。数据模型仅仅是普通的javascript对象。而当你修改它时,视图会进行更新。这使得状态管理非常简单、直接。不过理解其工作原理同样重要,这样你可以回避一些常见的问题。      -----官方文档

vue.js无疑是前端目前最火的MVVM框架之一,就像曾经的jQuery,已经成为前端工程师必备的技能。而要在工作中用好它,如官方文档所言,理解其工作原理很重要。笔者于清明小长假期间认真阅读了一下刘博文老师的深入剖析Vue.js源码,在这里与大家一起学习分享一下vue(2.0版本)全局API的实现原理。

进入正文之前,我们先来看下vue.js内部的一段代码:

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

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)
renderMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)

export default Vue
复制代码

其中定义了Vue构造函数,然后分别调用了initMixin、stateMixin、eventsMixin、lifecycleMixin和renderMixin这5个函数,并将Vue构造函数当作参数传给了这5个函数。

这5个函数的作用就是向Vue的原型中挂载方法。以函数initMixin为例,它的实现方式是这样的:

export function initMixin (Vue) {
    Vue.prototype._init = function (options) {
        // 初始化操作--生命周期的流程、响应式系统流程的启动
    }
}
复制代码

其他4个函数也是如此,只是它们在Vue构造函数的prototype属性上挂载不同的方法而已

下面进入这次的正题,一起来学习Vue全局API的实现源码:

1. 数据相关的实例方法

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

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

/**
 * target不能是Vue.js实例或Vue.js实例的根数据对象
 *
 * @param {object | array} target
 * @param {String | Number} key
 * @param {*} value
 */
export function stateMixin (Vue) {
    Vue.prototype.$set  = set
    Vue.prototype.$delete = del
    Vue.prototype.$watch = function (expOrFn, cb, options) {}
}
复制代码

(1)vm.$set

如果了解vue.js的变化侦测原理,我们就知道vue只能追踪到已经存在的属性的变化,新增的属性无法被追踪到。因为在ES6之前,Javascript并没有提供元编程的能力,无法侦测Object什么时候被添加了一个新属性。而用vm.$set可以解决这个问题,使用它可以为Object新增属性,然后vue可以将这个新增属性转换成响应式的。

set实现代码:

// oberver/index
function set (target, key, val) {
    // target是数组且key是一个有效的索引值 先设置length属性
    if (Array.isArray(target) && isValidArrayIndex(key)) {
        target.length = Math.max(target.length, key)
        // 当我们使用splice方法把val设置到target中的时候
        // 数组拦截器会侦测到target发生了变化
        // 并且会自动把这个新增的val转换成响应式的
        target.splice(key, 1, val)
        return val
    }
    // 如果key已经存在于target中,这种情况属于修改数据
    // 修改数据的动作会被Vue.js侦测到
    // 数据发生变化后 会自动向依赖发送通知
    if (key in target && (!key in Object.prototype)) {
        target[key] = val
        return val
    }
    // 处理在target上新增的key
    const ob = target.__ob__
    // target._isVue判断target是否为Vue实例
    // ob.vmCount判断target是否为根数据(this.$data)
    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
    }
    // 如果target没有__ob__属性 说明他不是响应式的 不需要做特殊处理
    // 只需要通过key和val在target上设置
    if (!ob) {
        target[key] = val
        return val
    }
    // target是响应式的 使用defineReactive将新增属性转换成getter/setter的形式
    defineReactive(ob.value, key, val)
    // 向target的依赖触发变化通知
    ob.dep.notify()
    return val
}
export.set = set
复制代码

(2) vm.$delete

从字面意思我们可以知道这个API的作用就是用来删除数据的某个属性的。我们知道vue的变化侦测是使用Object.defineProperty实现的,如果数据是使用delete关键字删除的,那么无法发现数据发生了变化。vm.$delete就是用来解决这个问题的。

del实现代码:

// observer/index
function del (target, key) {
    // target为数组
    if (Array.isArray(target) && isValidArrayIndex(key)) {
        target.splice(key, 1)
        return
    }
    const ob = target.__ob__
    if (target._isVue || (ob && ob.vmCount)) {
      process.env.NODE_ENV !== 'production' && warn(
          'Avoid deleting reactive properties to a Vue instance or its root $data ' + 
          '- just set it to null.'
      )
      return
    }
    // 如果key不是target自身属性 直接return
    if (!hasOwn(target, key)) {
        return
    }
    // 从target中删除key属性
    delete target[key]
    // 如果target不是响应式的 直接return
    if (!ob) {
        return
    }
    // 向依赖发送消息
    ob.dep.notify()
}
复制代码

(3)vm.$watch

vm.$watch其实是对Watcher的一种封装,Watcher的原理实现这里就不多多了,如果想了解有兴趣的话,可以去查找下相关资料。
(推荐:blog.csdn.net/wangweiange…)

实现代码:

Vue.prototype.$watch = function (expOrFn, cb, options) {
  const vm = this
  options = options || {}
  // watch依赖收集的Watcher
  const watcher = new Watcher(vm, expOrFn, cb, options)
  // immediate=true时 会调用一次 watcher.run 方法,因此会调用一次watch中相关key的函数
  if (options.immediate) {
      cb.call(vm, watcher.value)
  }
  // 返回一个取消监听的函数
  return function unwatchFn () {
      watcher.teardown()
  }
}
复制代码

2. 与事件相关的实例方法

与事件相关的实例方法有4个,分别是:vm.on、vm.once、vm.off、vm.emit。这4个方法是在eventsMixin中挂载到vue构造函数的prototype属性中的。

export function eventsMixin (Vue) {
    //  监听当前实例上的自定义事件 事件由vm.$emit触发
    Vue.prototype.$on = function (event, fn) {
        const vm = this
        // event是一个数组时 遍历数组 递归调用vm.$on
        if (Array.isArray(event)) {
            for (let i = 0, l = event.length; i < l; i++) {
                this.$on(event[i], fn)
            }
        } else {
            // vm._events是一个对象 用来存储事件
            (vm._events[event] || vm._events[event] = []).push(fn)
        }
        return vm
    }
    // 移除自定义事件监听器
    // 如果没有提供参数 则移除所有的事件监听器
    // 如果只提供了事件 则移除该事件所有的监听器
    // 如果同时提供了事件与回调 则只移除这个回调的监听器
    Vue.prototype.$off = function (event, fn) {
        const vm = this
        // 移除所有事件的监听器
        if (!arguments.length) {
            vm._events = Object.create(null)
            return vm
        }
        // event是数组
        if (Array.isArray(event)) {
            for (let i = 0, l = event.length;i < l; i++) {
                this.$off(event[i], fn)
            }
            return vm
        }
        const cbs = vm._events[event]
        // 如果这个事件没有被监听 直接return
        if (!cbs) {
            return vm
        }
        // 移除该事件的所有监听器
        if (arguments.length === 1) {
            vm_events[event] = null
            return vm
        }
        // 如果同时提供了事件与回调 只移除这个回调的监听器
        if (fn) {
            const cbs = vm._events[event]
            let cb
            let i = cbs.length
            while (i--) {
                cb = cbs[i]
                if (cb === fn || cb.fn === fn) {
                    cbs.splice(i, 1)
                }
            }
        }
        return vm
    }
    // 监听一个自定义事件 但是只触发一次 第一次触发之后移除监听器
    Vue.prototype.$once = function (event, fn) {
        const vm = this
        function on () {
            vm.$off(event, fn)
            fn.apply(vm, arguments)
        }
        on.fn = fn
        vm.$on(event, on)
    }
    // 触发当前实例上的事件
    Vue.prototype.$emit = function (event) {
        const vm = this
        let cbs = vm._events[event]
        if (cbs) {
            const args = toArray(arguments, 1)
            for (let i = 0, l = cbs.length; i < l; i++) {
                try {
                    cbs[i].apply(vm, args)
                } catch (e) {
                    handleError(e, vm, `event handler for "${event}"`)
                }
            }
        }
        return vm
    }
}  
复制代码

3. 与生命周期相关的实例方法

与生命周期相关的实例方法有4个:vm.mount,  vm.forceUpdate, vm.nextTick,  vm.destory
vm.mount是在跨平台的代码中挂载到Vue构造函数的prototype属性上的
vm.forceUpdate和vm.destory是从lifecycleMixin中挂载的
vm.$nextTick是从renderMixin中挂载的

(1)vm.$forceUpdate

vm.$forceUpdate的作用是迫使vue.js实例重新渲染,它只影响实例本身以及插入插槽内容的子组件,而不是所有子组件。

代码实现:

Vue.prototype.$forceUpdate = function () {
    const vm = this
    if (vm._watcher) {
        vm._watcher.update()
    }
}
复制代码

(2)vm.$destory

vm.$destory的作用是完全销毁一个实例,它会清理该实例与其他实例的连接,并解绑其全部指令及监听器,同时会触发 beforeDestory和destoryed的钩子函数

代码实现:

Vue.prototype.$destory = function () {
    const vm = this
    // 对属性_isBeingDestory进行判断 如果为 true vuejs实例正在被销毁 直接return 防止反复销毁
    if (vm._isBeingDestory) {
        return 
    }
    callHook(vm, beforeDestory)
    vm._isBeingDestoryed = true
    // 删除自己与父级之间的连接
    const parent = vm.$parent
    // 如果当前实例有父级 同时父级没有被销毁且不是抽象组件
    if (parent && !parent._isBeingDestoryed && vm.$options.abstract) {
        remove(parent.$children, vm)
    }
    // 销毁实例上的所有watcher
    // 从watcher监听的所有状态
    if (vm._watcher) {
        vm._watcher.teardown()
    }
    // 每当创建watchers实例时 都会将watcher实例添加到 vm._watchers中
    let i = vm._watchers.length
    while (i--) {
        vm._watchers[i].teardown()
    }
    vm._isDestoryed = true
    // 在 vnode树上触发destory钩子函数解绑指令
    vm.__patch__(vm._vnode, null)
    // 触发destoryed钩子函数
    callHook(vm, 'destoryed')
    // 移除所有的事件监听器
    vm.$off()
}
复制代码

(3) vm.$nextTick()

nextTick接收一个回调函数作为参数 它的作用是将回调延迟到下次DOM更新周期之后执行。

我们在开发项目时会遇到一种场景:当更新了状态(数据)后,需要对新DOM做一些操作,但是这是我们其实获取不到更新后的DOM,因为还没有重新渲染。这个时候我们需要使用nextTick方法

示例如下:

new Vue({
    // ......
    methods: {
        // ......
        example: function () {
            // 修改数据
            this.message = 'changed'
            // DOM还没有更新
            this.$nextTick(function () {
                // DOM 现在更新了
                // this绑定到当前实例
                this.doSomethindElse()
            })
        }
    }
})
复制代码

要理解nextTick,我们需要知道,在Vue.js中,当状态发生变化时,watcher会得到通知,然后触发虚拟DOM的渲染流程。而watcher触发渲染是异步的。Vue.js中有一个队列,每当需要渲染时,会将watcher推送到这个队列中,在下一次事件循环中再让watcher触发渲染的流程。

为什么vue.js使用异步更新队列

我们知道vue.js的变化侦测的通知只发送到组件,组件内用到的所有状态的变化都会通知到同一个watcher,所以为了避免多个状态变化,多次渲染,虚拟DOM会等所有状态都修改完毕之后,一次性将整个组件的DOM渲染到最新。

Vue.js实现的方式是将收到通知的watcher实例添加到队列中缓存起来,并且在添加到队列之前检查其中是否已经存在相同的watcher,只有不存在时,才将 watcher实例添加到队列中。然后在下一次事件循环中,Vue.js会让队列中的 watcher触发渲染流程并清空队列。

事件循环

我们都知道Javascript是一门单线程且非阻塞的脚本语言,这意味着Javascript代码在执行的任何时候只有一个主线程来处理所有任务。而非阻塞是指当代码需要处理异步任务时,主线程会挂起这个任务,当异步任务处理完毕后,主线程再根据一定规则去执行相应回调。

其实,当任务处理完毕后,Javascript会将这个事件加入一个队列中,即事件队列。被放入事件队列中的事件不会立刻执行其回调,而是等待当前执行栈中的所有任务执行完毕后,主线程会去查找事件队列中是否有任务。

异步任务有两种类型:微任务(microtask)和宏任务(macrotask)。不同类型的任务被分配到不同的任务队列中。
当执行栈中的所有任务都执行完毕后,会去检查微任务队列中是否有事件存在,如果存在,则会依次执行微任务队列中事件对应的回调,直到为空。然后去宏任务队列中取出一个事件,把对应的回调加入当前执行栈,当执行栈中的所有任务都执行完毕后,检查微任务队列中是否有事件存在。无限重复此过程,就形成了一个循环,即事件循环。

常见微任务的事件
Promise.then  MutationObserver  Object.observe  process.nextTick  
复制代码
常见宏任务的事件
setTimeout  setInterval  setImmediate MessageChannel requestAnimationFrame
复制代码

讲了这么多相关知识,我们回到nextTick的实现原理上来。vm.nextTick和全局方法Vue.nextTick是相同的,所以nextTick的具体实现并不是在vue原型上的nextTick方法中,而是抽象成了nextTick方法供两个方法共用。

代码如下:

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

Vue.prototype.$nextTick = function (fn) {
    return nextTick(fn, this)
}
复制代码

由于vm.$nextTick会将回调添加到任务队列中延迟执行,所以在回调执行前,如果反复调用nextTick,Vue.js并不会反复将回调添加到任务队列中,只会向任务队列中添加一个任务,多次使用nextTick只会将回调添加到回调列表中缓存起来。当任务触发时,依次执行列表中的所有回调并清空列表。

const callbacks = []
let pending = false

// 注册的任务
function flushCallbacks () {
    pending = false
    const copies = callbacks.slice(0)
    callbacks.length = 0
    for (let i = 0; i < copies.length; i++) {
        copies[i]()
    }
}

let microTimerFunc
const p = Promise.resolve()
// 将flushCallbacks添加到微任务队列中
microTimerFunc = () => {
    p.then(flushCallbacks)
}

export function nextTick (cb, ctx) {
    callbacks.push(() => {
        if (cb) {
            cb.call(ctx)
        }
    })
    // 通过pending判断是否需要向任务队列中添加任务
    if (!pending) {
        pending = true
        microTimerFunc()
    }
}
复制代码

下图给出nextTic的内部注册流程和执行流程:

在vue.js2.4版本之前,nextTick在任何地方都使用微任务,但是微任务的优先级太高,在某些场景下可能会出现问题,所以Vue.js提供了在特殊场合下可以强制使用宏任务的方法。具体实现如下:

//  在上面代码基础上新增

// ......
let useMacroTask = false
// ......
export function withMacroTask (fn) {
    return fn._withTask || (fn._withTask = function () {
        useMacroTask = true
        const res = fn.apply(null, arguments)
        useMacroTask = false
        return false
    })
}

export function nextTick(cb, ctx) {
    // ......
    if (!pending) {
        pending = true
        if (useMacroTask) {
            macroTimerFunc()
        } else {
            microTimerFunc()
        }
    }
}
复制代码

withMacroTask函数的作用是给回调函数做一层包装,保证在整个回调函数执行过程中,如果修改可状态(数据),那么更新DOM的操作会被推到宏任务队列中。

下面我们来看看macroTimerFunc是如何将回调添加到宏任务队列中的。
前面我们介绍过几种属于宏任务的事件。Vue.js优先使用setImmediate,但是它存在兼容问题,只能在IE中使用,所以使用MessageChannel作为备选方案,如果浏览器也不支持MessageChannel,那么最好会使用setTimeout将回调添加到宏任务队列中。

macroTimerFunc实现代码:

if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
    macroTimerFunc = () => {
        setImmediate(flushCallbacks)
    }
} else if (typeof MessageChannel !== 'undefined' && (
    isNative(MeaasgeChannel) || 
    MessageChannel.toString() === '[object MessageChannelConstructor]'
)) {
    const channel = new MessageChannel()
    const port = channel.port2
    channle.port1.onmessage = flushCallbacks
    macroTimerFunc = () => {
        port.postMessage(1)
    }
} else {
    macroTimerFunc = () => {
        setTimeout(flushCallbacks, 0)
    }
}
复制代码

前面提到microTimerFunc的实现原理是使用Promise.then,但并不是所有浏览器都支持Poemise,当不支持时,会降级成macroTimerFunc,其实现方式如下:

if (typeof Promise !== 'undefined' && isNative(Promise)) {
    const p = Promise.resolve()
    // 将flushCallbacks添加到微任务队列中
    microTimerFunc = () => {
        p.then(flushCallbacks)
    }
} else {
    microTimerFunc = macroTimerFunc
}
复制代码

官方文档中有这样一句话:如果没有提供回调且在支持Promise的环境中,则返回一个Promise。也就是说可以这样使用vm.$nextTick:

this.$nextTick().then(function () {
    // DOM更新了
})
复制代码

要实现这个功能,我们只需要在nextTick中进行判断,如果没有提供回调且当前环境支持Promise,那么返回Promise,并且在callbacks中添加一个函数,当这个函数执行时,执行Promise的resolve即可,代码如下:

export function nextTick (cb, ctx) {
    // ......
    let _resolve
    callbacks.push(() => {
        if (cb) {
            cb.call(ctx)
        } else if (_resolve) {
            _resolve(ctx)
        }
    })
    // ......
    if (!cb && typeof Promise !== 'undefined') {
        return new Promise(resolve => {
            _resolve = resolve
        })
    }
}
复制代码

(4) vm.$mount()

一个Vue.js实例在实例化时如果没有收到el选项,则它处于"未挂载"状态,没有关联的DOM元素。我们可以使用vm.$mount手动挂载一个未挂载的实例。如果没有提供参数,模板会被渲染为文档之外的元素,必须使用原生DOM的API把它插入到文档中。这个方法返回实例自身,因而可以链式调用其他实例方法。

4. 常见全局API的实现原理

全局API和实例方法不同,后者是在 Vue的原型上挂载方法,前者是直接在Vue上挂载方法

(1)Vue.extend

Vue.extend用来创建一个子类,让它继承Vue身上的一些功能,实现代码如下:

let cid = 1

Vue.extend = function (extendOptions) {
    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
        if (!/^[a-zA-Z][\w-]*$)/.test(name)) {
            warn(
                'Invalid component name: "' + name + '". Component names ' +
                'can only contain alphanumberic characters and the hyphen, ' + 'and must start with a letter.'
            )
        }
    }
    const Sub = function VueComponent (options) {
        this._init(options)
    }
    //  将父类的原型继承到子类中
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub
    Sub.cid = cid++
    // 将父类的options选项继承到子类中
    Sub.options = mergeOptions(
        Super.options,
        extendOptions
    )
    Sub['super'] = Super
    // 如果选项中存在props属性 则初始化
    if (Sub.options.props) {
        initProps(Sub)
    }
    // 如果选项中存在computed 则对它进行初始化
    if (Sub.options.computed) {
        initComputed(Sub)
    }
    // 将父类中存在的属性依次复制到子类中
    Sub.extend = Super.extend
    Sub.mixin = Super.mixin
    Sub.use = Super.use
    
    // ASSET_TYPES = ['component', 'directive', 'filter']
    ASSET_TYPES.forEach(function (type) {
        Sub[type] = Super[type]
    })
    if (name) {
        Sub.options.components[name] = Sub
    }
    //  缓存构造函数
    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)
    }
}
const sharedPropertyDefinition = {
    enumberable: true,
    configurable: true,
    get: noop,
    set: noop
}
function proxy (target, sourceKey, key) {
    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])
    }
}
复制代码

(2). Vue.nextTick

Vue.nextTick的实现原理与我们前面介绍的vm.$nextTick一样,代码如下:

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

Vue.nextTick = nextTick
复制代码

(3). Vue.set

Vue.set与vm.$set的实现原理相同,代码如下:

import { set } from '../observer/index'
Vue.set = set
复制代码

(4). Vue.delete

Vue.delete与vm.$delete的实现原理相同,代码如下:

import { del } from '../observer/index'
Vue.delete = del
复制代码

(5). Vue.directive

注册或获取全局指令,代码如下:

// 用于保存指令的位置
Vue.options = Object.create(null)
Vue.options['directives'] = Object.create(null)

Vue.directive = function (id, definition) {
    // 如果definition参数不存在 则使用id从this.options['direactives']中读出指令并返回
    if (!definition) {
        return this.options['directives'][id]
    } else {// definition参数存在
        // 如果definition是函数 则默认监听bind和update两个方法
        // 将definition分别赋值给对象中的bind和uodate两个方法
        if (typeof definition === 'function') {
            definition = { bind: definition, update: definition }
        }
        this.options['directives'][id] = definition
        return definition
    }
}
复制代码

(6) Vue.filter

注册或获取全局过滤器,代码如下:

Vue.options['filter'] = Object.create(null)

Vue.filter = function (id, definition) {
      // 如果definition参数不存在 则使用id从this.options['filter']中读出指令并返回
    if (!definition) {
        return this.options['filter'][id]
    } else {
        this.options['filter'][id] = definition
        return definition
    }
}
复制代码

(7) Vue.component

注册或获取全局组件。注册组件时,会自动使用给定的id设置组件的名称,使用方法如下:

// 注册组件 传入一个扩展过的构造器
Vue.component('my-component', Vue.extend({/* ... */}))

// 注册组件 传入一个选项对象(自动调用Vue.extend)
Vue.component('my-component', {/* ... */})

// 获取注册的组件(返回构造器)
let MyComponent = Vue.component('my-component')
复制代码

Vue.component实现代码:

Vue.options['components'] = Object.create(null)

Vue.component = function (id, definition) {
    if (!definition) {
        return this.options['components'][id]
    } else {
        if (isPlainObject(definition)) {
            definition.name = definition.name || id
            definition = Vue.extend(definition)
        }
        this.options['component'][id] = definition
        return definition
    }
}
复制代码

(7) Vue.use

Vue.use是用来安装Vue.js插件的。如果插件是一个对象,必须提供install方法。如果插件是一个函数,它会被作为install方法。调用install方法时,会将Vue作为参数传入。install方法被同一个插件多次调用时,插件也只会被安装一次。

实现代码如下:

Vue.use = function (plugin) {
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    // 判断插件是否被注册过
    if (installedPlugins.indexOf(plugin) > -1) {
        return this
    }
    // 其他参数
    const args = toArray(arguments, 1)
    args.unshift(this)
    if (typeof plugin.install === 'function') {
        plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
        plugin.apply(null, plugin)
    }
    installedPlugins.push(plugin)
    return this
}
复制代码

(8) Vue.mixin

全局注册一个混入(mixin),影响注册之后创建的每个Vue.js实例。可以使用混入向组件注入自定义行为(例如:监听声明周期钩子)。实现代码如下:

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

export function initMixin (Vue) {
    Vue.mixin = function (mixin) {
         // 将传入的对象与Vue.js自身的options属性合并在一起
        this.options = mergeOptions(this.options, mixin)
        return this
    }
}
复制代码

好了,清明小长假的学习分享就先到这了

这篇关于Vue(v2.0)全局API的实现原理的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!