热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Vue源码之实例方法

个人博客地址在Vue内部,有一段这样的代码:import{initMixin}from.initimport{stateMixin}from.stateimport{renderM

个人博客地址
Vue 内部,有一段这样的代码:

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)

上面5个函数的作用是在Vue的原型上面挂载方法。

  • initMixin 函数

    export function initMixin (Vue: Class) {
    Vue.prototype._init = function (options?: Object) {
    const vm: CompOnent= this
    // a uid
    vm._uid = uid++
    let startTag, endTag
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    startTag = `vue-perf-start:${vm._uid}`
    endTag = `vue-perf-end:${vm._uid}`
    mark(startTag)
    }
    // a flag to avoid this being observed
    vm._isVue = true
    // merge options
    if (options && options._isComponent) {
    // optimize internal component instantiation
    // since dynamic options merging is pretty slow, and none of the
    // internal component options needs special treatment.
    initInternalComponent(vm, options)
    } else {
    vm.$optiOns= mergeOptions(
    resolveConstructorOptions(vm.constructor),
    options || {},
    vm
    )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
    initProxy(vm)
    } else {
    vm._renderProxy = vm
    }
    // expose real self
    // 初始化操作
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    vm._name = formatComponentName(vm, false)
    mark(endTag)
    measure(`vue ${vm._name} init`, startTag, endTag)
    }
    if (vm.$options.el) {
    vm.$mount(vm.$options.el)
    }
    }
    }

    可以看到在 initMixin 方法中,实现了一系列的初始化操作,包括生命周期流程以及响应式系统流程的启动。

  • stateMixin 函数

    export function stateMixin (Vue: Class) {
    // flow somehow has problems with directly declared definition object
    // when using Object.defineProperty, so we have to procedurally build up
    // the object here.
    const dataDef = {}
    dataDef.get = function () { return this._data }
    const propsDef = {}
    propsDef.get = function () { return this._props }
    if (process.env.NODE_ENV !== 'production') {
    dataDef.set = function () {
    warn(
    'Avoid replacing instance root $data. ' +
    'Use nested data properties instead.',
    this
    )
    }
    propsDef.set = function () {
    warn(`$props is readonly.`, this)
    }
    }
    Object.defineProperty(Vue.prototype, '$data', dataDef)
    Object.defineProperty(Vue.prototype, '$props', propsDef)
    Vue.prototype.$set = set
    Vue.prototype.$delete = del
    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)
    if (options.immediate) {
    try {
    cb.call(vm, watcher.value)
    } catch (error) {
    handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
    }
    }
    return function unwatchFn () {
    watcher.teardown()
    }
    }
    }

    stateMixin 被调用时,往Vue的原型上了挂载了三个方法:$delete$set$watch

  • eventsMixin 函数

    export function eventsMixin (Vue: Class) {
    const hookRE = /^hook:/
    // $on的实现:在注册时把回调函数收集起来,在触发时将收集的事件依次调用
    Vue.prototype.$on = function (event: string | Array, fn: Function): Component {
    const vm: CompOnent= this
    // 当event为数组时,遍历event将其中的每一项都调用$on
    // 当event为字符串时,向事件列表中添加回调
    // vm._enevts是专门用来存储事件,在initMixin中生成:vm._events = Object.create(null)
    if (Array.isArray(event)) {
    for (let i = 0, l = event.length; i vm.$on(event[i], fn)
    }
    } else {
    (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
    if (hookRE.test(event)) {
    vm._hasHookEvent = true
    }
    }
    return vm
    } Vue.prototype.$Once= function (event: string, fn: Function): Component {
    const vm: CompOnent= this
    // 当第一次触发自定义事件时,会移除这个事件监听器,然后手动运行fn函数
    function on () {
    vm.$off(event, on)
    fn.apply(vm, arguments)
    }
    on.fn = fn
    vm.$on(event, on)

    return vm
    }
    Vue.prototype.$off = function (event?: string | Array, fn?: Function): Component {
    const vm: CompOnent= this
    // all
    // 当参数为空时,直接清空vm._events,移除所有事件监听器
    if (!arguments.length) {
    vm._events = Object.create(null)
    return vm
    }
    // array of events
    // 当event为数组时,遍历event每一项都调用$off
    if (Array.isArray(event)) {
    for (let i = 0, l = event.length; 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
    // 从vm._events中删除这个事件监听器
    let cb
    let i = cbs.length
    while (i--) {
    cb = cbs[i]
    if (cb === fn || cb.fn === fn) {
    cbs.splice(i, 1)
    break
    }
    }
    return vm
    }
    // $emit的实现:使用事件名event从vm._events中取出事件监听器的回调函数列表
    // 依次执行列表中的回调函数并且把参数传入监听器回调函数
    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
    const args = toArray(arguments, 1)
    const info = `event handler for "${event}"`
    for (let i = 0, l = cbs.length; i invokeWithErrorHandling(cbs[i], vm, args, vm, info)
    }
    }
    return vm
    }
    }
    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
    }

    eventsMixin 函数被调用时,往 Vue 的原型上挂载了4个方法:$on$once$off$emit

  • lifecycleMixin 函数

    export function lifecycleMixin (Vue: Class) {
    Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: CompOnent= this
    const prevEl = vm.$el
    const prevVnode = vm._vnode
    const restoreActiveInstance = setActiveInstance(vm)
    vm._vnode = vnode
    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.
    if (!prevVnode) {
    // initial render
    vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
    // updates
    vm.$el = vm.__patch__(prevVnode, vnode)
    }
    restoreActiveInstance()
    // update __vue__ reference
    if (prevEl) {
    prevEl.__vue__ = null
    }
    if (vm.$el) {
    vm.$el.__vue__ = vm
    }
    // if parent is an HOC, update its $el as well
    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
    vm.$parent.$el = vm.$el
    }
    // updated hook is called by the scheduler to ensure that children are
    // updated in a parent's updated hook.
    }
    // vm._watcher就是Vue.js实例的watcher,手动执行watcher的update方法
    // 然后组件内部就会重新生成vnode,和旧的vnode进行对比,更新视图
    Vue.prototype.$forceUpdate = function () {
    const vm: CompOnent= this
    if (vm._watcher) {
    vm._watcher.update()
    }
    }
    //
    Vue.prototype.$destroy = function () {
    const vm: CompOnent= this
    // 如果已经在销毁实例,则直接返回
    if (vm._isBeingDestroyed) {
    return
    }
    // 调用钩子函数:beforeDestory
    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监听的所有状态的依赖列表中移除watcher
    if (vm._watcher) {
    vm._watcher.teardown()
    }
    // 在Watcher类中有这样一行代码: vm._watchers.push(this)
    // 移除所有用户通过$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
    // 在vnode树上触发destory钩子函数解绑指令
    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
    }
    }
    }

    lifecycleMixin 被调用时,往 Vue 的原型上挂载了三个方法:_updata$forceUpdate$destory

  • renderMixin 函数

    export function renderMixin (Vue: Class) {
    // install runtime convenience helpers
    installRenderHelpers(Vue.prototype)
    Vue.prototype.$nextTick = function (fn: Function) {
    return nextTick(fn, this)
    }
    Vue.prototype._render = function (): VNode {
    const vm: CompOnent= this
    const { render, _parentVnode } = vm.$options
    if (_parentVnode) {
    vm.$scopedSlots = normalizeScopedSlots(
    _parentVnode.data.scopedSlots,
    vm.$slots,
    vm.$scopedSlots
    )
    }
    // set parent vnode. this allows render functions to have access
    // to the data on the placeholder node.
    vm.$vnode = _parentVnode
    // render self
    let vnode
    try {
    // There's no need to maintain a stack because all render fns are called
    // separately from one another. Nested component's render fns are called
    // when parent component is patched.
    currentRenderingInstance = vm
    vnode = render.call(vm._renderProxy, vm.$createElement)
    } catch (e) {
    handleError(e, vm, `render`)
    // return error render result,
    // or previous vnode to prevent render error causing blank component
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {
    try {
    vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
    } catch (e) {
    handleError(e, vm, `renderError`)
    vnode = vm._vnode
    }
    } else {
    vnode = vm._vnode
    }
    } finally {
    currentRenderingInstance = null
    }
    // if the returned array contains only a single node, allow it
    if (Array.isArray(vnode) && vnode.length === 1) {
    vnode = vnode[0]
    }
    // return empty vnode in case the render function errored out
    if (!(vnode instanceof VNode)) {
    if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
    warn(
    'Multiple root nodes returned from render function. Render function ' +
    'should return a single root node.',
    vm
    )
    }
    vnode = createEmptyVNode()
    }
    // set parent
    vnode.parent = _parentVnode
    return vnode
    }
    }
    // 简化版的nextTick
    // 用来存放回调函数事件
    let callbacks = []
    // 表示是否已经添加到微任务列表中
    let pending = false
    // 遍历callbacks,清空callbacks,依次调用回调函数
    function flushCallbacks () {
    penging = false
    const copies = callbacks.slice(0)
    callbacks.length = 0
    for (let i = 0; i capies[i]()
    }
    }
    // 把事件添加到微任务列表中去
    let microTimerFunc
    let p = Promise.resolve()
    microTimerFunc = () => {
    p.then(flushCallbacks)
    }
    function nextTick (cb?: Function, ctx?: Object) {
    // 一进来先向callback中添加回调事件
    callbacks.push(() => {
    if (cb) {
    cb.call(ctx)
    }
    })
    // 如果pending为false,则表示是第一次执行nextTick,将其添加到微任务中
    // 如果pending为true,表示之前微任务列表中已经添加了这个方法,直接退出
    if (!pending) {
    pending = true
    microTimerFunc()
    }
    }

    renderMixin 被调用时,在 Vue 的原型上挂载了两个方法:$nextTick_render


推荐阅读
  • 本文详细介绍了 PHP 中对象的生命周期、内存管理和魔术方法的使用,包括对象的自动销毁、析构函数的作用以及各种魔术方法的具体应用场景。 ... [详细]
  • 本文详细介绍了Java反射机制的基本概念、获取Class对象的方法、反射的主要功能及其在实际开发中的应用。通过具体示例,帮助读者更好地理解和使用Java反射。 ... [详细]
  • 在多线程并发环境中,普通变量的操作往往是线程不安全的。本文通过一个简单的例子,展示了如何使用 AtomicInteger 类及其核心的 CAS 无锁算法来保证线程安全。 ... [详细]
  • 本文详细解析了使用C++实现的键盘输入记录程序的源代码,该程序在Windows应用程序开发中具有很高的实用价值。键盘记录功能不仅在远程控制软件中广泛应用,还为开发者提供了强大的调试和监控工具。通过具体实例,本文深入探讨了C++键盘记录程序的设计与实现,适合需要相关技术的开发者参考。 ... [详细]
  • 本文详细介绍了一种利用 ESP8266 01S 模块构建 Web 服务器的成功实践方案。通过具体的代码示例和详细的步骤说明,帮助读者快速掌握该模块的使用方法。在疫情期间,作者重新审视并研究了这一未被充分利用的模块,最终成功实现了 Web 服务器的功能。本文不仅提供了完整的代码实现,还涵盖了调试过程中遇到的常见问题及其解决方法,为初学者提供了宝贵的参考。 ... [详细]
  • 浅析python实现布隆过滤器及Redis中的缓存穿透原理_python
    本文带你了解了位图的实现,布隆过滤器的原理及Python中的使用,以及布隆过滤器如何应对Redis中的缓存穿透,相信你对布隆过滤 ... [详细]
  • 本文介绍了如何在 Vue 3 组合 API 中正确设置 setup() 函数的 TypeScript 类型,以避免隐式 any 类型的问题。 ... [详细]
  • 重要知识点有:函数参数默许值、盈余参数、扩大运算符、new.target属性、块级函数、箭头函数以及尾挪用优化《深切明白ES6》笔记目次函数的默许参数在ES5中,我们给函数传参数, ... [详细]
  • 开机自启动的几种方式
    0x01快速自启动目录快速启动目录自启动方式源于Windows中的一个目录,这个目录一般叫启动或者Startup。位于该目录下的PE文件会在开机后进行自启动 ... [详细]
  • 单元测试:使用mocha和should.js搭建nodejs的单元测试
    2019独角兽企业重金招聘Python工程师标准BDD测试利器:mochashould.js众所周知对于任何一个项目来说,做好单元测试都是必不可少 ... [详细]
  • 本文介绍了如何利用 `matplotlib` 库中的 `FuncAnimation` 类将 Python 中的动态图像保存为视频文件。通过详细解释 `FuncAnimation` 类的参数和方法,文章提供了多种实用技巧,帮助用户高效地生成高质量的动态图像视频。此外,还探讨了不同视频编码器的选择及其对输出文件质量的影响,为读者提供了全面的技术指导。 ... [详细]
  • 本文介绍了如何使用Python的Paramiko库批量更新多台服务器的登录密码。通过示例代码展示了具体实现方法,确保了操作的高效性和安全性。Paramiko库提供了强大的SSH2协议支持,使得远程服务器管理变得更加便捷。此外,文章还详细说明了代码的各个部分,帮助读者更好地理解和应用这一技术。 ... [详细]
  • 在使用 Cacti 进行监控时,发现已运行的转码机未产生流量,导致 Cacti 监控界面显示该转码机处于宕机状态。进一步检查 Cacti 日志,发现数据库中存在 SQL 查询失败的问题,错误代码为 145。此问题可能是由于数据库表损坏或索引失效所致,建议对相关表进行修复操作以恢复监控功能。 ... [详细]
  • 本文深入解析了JDK 8中HashMap的源代码,重点探讨了put方法的工作机制及其内部参数的设定原理。HashMap允许键和值为null,但键为null的情况只能出现一次,因为null键在内部通过索引0进行存储。文章详细分析了capacity(容量)、size(大小)、loadFactor(加载因子)以及红黑树转换阈值的设定原则,帮助读者更好地理解HashMap的高效实现和性能优化策略。 ... [详细]
  • 本文介绍了一种自定义的Android圆形进度条视图,支持在进度条上显示数字,并在圆心位置展示文字内容。通过自定义绘图和组件组合的方式实现,详细展示了自定义View的开发流程和关键技术点。示例代码和效果展示将在文章末尾提供。 ... [详细]
author-avatar
潇洒看不惯_185
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有