【无标题】

1、 o n 与 on与 onemit

(1)使用方式

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <button @click="clickHandler">按钮</button>
        <p>{{message}}</p>
    </div>
    <script>
        var app = new Vue({
            el: '#app',
            data: {
                message: 'Hello Vue!'
            },
            methods: {
                clickHandler: function () {
                    this.$emit('test','触发test事件并传值');
                }
            },
            mounted() {
                // 监听test事件
                this.$on('test', function(value){
                    this.message = value
                })
            }
        })
    </script>
</body>
</html>

$on( event, callback )

  • 参数

    • {string | Array<string>} event (数组只在 2.2.0+ 中支持),监听的事件
    • {Function} callback,监听的事件被触发后,执行的回调
  • 用法

    监听当前实例上的自定义事件。事件可以由 vm.$emit 触发。回调函数会接收所有传入事件触发函数的额外参数。

$emit( event, […args] )

  • 参数

    • {string} event,触发的事件
    • [...args],传参

    触发当前实例上的事件。附加参数都会传给监听器($on)回调。

(2)源码分析

//  /src/core/instance/event.ts
export function eventsMixin(Vue: typeof Component) {
  const hookRE = /^hook:/
  // 发布订阅模式
  Vue.prototype.$on = function (
    event: string | Array<string>,
    fn: Function
  ): Component {
    const vm: Component = this
    // 如果event是字符串数组,则遍历数组,每个元素执行一次on方法(递归,将字符串数组转化为字符串形式)
    if (isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$on(event[i], fn)
      }
    } else {
      // 如果事件中心有event事件,则直接push处理函数,如果没有event事件,则先注册事件
      (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
  }
  
    // 触发event事件
  Vue.prototype.$emit = function (event: string): Component {
    const vm: Component = this
    if (__DEV__) {
      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}".`
        )
      }
    }
    // cbs-callbacks,取出调度中心中该事件的处理函数
    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
  }
}

o n 、 on、 onemit的设计用到了发布-订阅模式,_events为当前实例的任务调度中心,执行 o n 在调度中心注册事件,并将回调函数写入;执行 on在调度中心注册事件,并将回调函数写入;执行 on在调度中心注册事件,并将回调函数写入;执行emit触发调度中心的事件,取出被触发事件的回调函数依次执行。

(3)简易仿写

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <button @click="clickHandler">按钮</button>
        <p>{{message}}</p>
    </div>
    <script>
        var app = new Vue({
            el: '#app',
            data: {
                message: 'Hello Vue!',
                _events:[]
            },
            methods: {
                clickHandler: function () {
                    this.myEmit('test','自定义触发test事件并传值');
                },
              //仿写$on
                myOn: function(event, callback) {
                    const vm = this
                    if(event instanceof Array){
                        for(let i=0;i<event.length;i++){
                            vm.myOn(event[i], callback)
                        }    
                    }else{
                        (vm._events[event]||(vm._events[event]=[])).push(callback)
                    }
                    return vm
                },
              //仿写$emit
                myEmit: function(event, value) {
                    const vm = this
                    let cbs = vm._events[event]
                    if(cbs){
                        for(let i=0;i<cbs.length;i++){
                            cbs[i](value)
                        }
                    }
                    return vm
                }
            },
            mounted() {
                // 监听test事件
                let that=this
                this.myOn('test', function(value) {
                    that.message = value
                })
            }
        })
    </script>
</body>
</html>

2、$mount

// src/platforms/web/runtime-with-compiler.ts

//先用一个变量保存vue原型上的$mount
const mount = Vue.prototype.$mount //src/platforms/web/runtime/index.ts
//重新定义原型上的$mount
Vue.prototype.$mount = function (
  el?: string | Element,
   //此参数与服务端渲染有关
  hydrating?: boolean
): Component {
  el = el && query(el)
  /* istanbul ignore if */
  // 挂载点不能是body或者html,原因是vue会生成dom对象替换挂载点,如果是body,html,上面的信息会丢失(meta、link、script等)
  if (el === document.body || el === document.documentElement) {
    __DEV__ &&
      warn(
        `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
      )
    return this
  }

  const options = this.$options
  // resolve template/el and convert to render function
  /**
   * eg:
   * new Vue({
        render: h => h(App)
      }).$mount('#app')
   */

  /**
   * eg:
   * export default {
        name: 'HelloWorld',
        data () {
          return {
            msg: 'Welcome to Your Vue.js App'
          }
        },
        template: `<div class="hello">{{ msg }}</div>`
      }
   */

  // 判断options中是否有render,如果有,则直接return,否则走if逻辑
  if (!options.render) {
    let template = options.template
    // 处理template或者el,都转换为模版字符串形式
    if (template) {
      if (typeof template === 'string') {
        // string为id形式
        if (template.charAt(0) === '#') {
          template = idToTemplate(template)
          /* istanbul ignore if */
          if (__DEV__ && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`,
              this
            )
          }
        }
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        if (__DEV__) {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      // @ts-expect-error
      template = getOuterHTML(el)
    }
    
    // 用处理好的template去生成render
    if (template) {
      /* istanbul ignore if */
      if (__DEV__ && config.performance && mark) {
        mark('compile')
      }
			//调用compileToFunctions函数,生成render
      const { render, staticRenderFns } = compileToFunctions(
        template,
        {
          outputSourceRange: __DEV__,
          shouldDecodeNewlines,
          shouldDecodeNewlinesForHref,
          delimiters: options.delimiters,
          comments: options.comments
        },
        this
      )
      //生成的render挂载到options上
      options.render = render
      options.staticRenderFns = staticRenderFns

      /* istanbul ignore if */
      if (__DEV__ && config.performance && mark) {
        mark('compile end')
        measure(`vue ${this._name} compile`, 'compile', 'compile end')
      }
    }
  }
  //调用最开始保存的mount
  return mount.call(this, el, hydrating)
}
//src/platforms/web/runtime/index.ts
// public mount method
Vue.prototype.$mount = function (
  // el,挂载点,可以是sting类型或者DOM对象
  el?: string | Element,
  // 服务端渲染会用到的参数,浏览器环境下不需要此参数
  hydrating?: boolean
): Component {
  // 如果el是字符串,并且在浏览器环境下,调用query函数,将el转换为DOM对象
  el = el && inBrowser ? query(el) : undefined
  // 调用mountComponent函数
  return mountComponent(this, el, hydrating)
}
//core/instance/lifecycle
export function mountComponent(
  vm: Component,
  el: Element | null | undefined,
  hydrating?: boolean
): Component {
  vm.$el = el
  if (!vm.$options.render) {
    // @ts-expect-error invalid type
    //createEmptyVNode创建注释节点
    vm.$options.render = createEmptyVNode
    if (__DEV__) {
      /* istanbul ignore if */
      if (
        (vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
        vm.$options.el ||
        el
      ) {
        warn(
          'You are using the runtime-only build of Vue where the template ' +
            'compiler is not available. Either pre-compile the templates into ' +
            'render functions, or use the compiler-included build.',
          vm
        )
      } else {
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        )
      }
    }
  }
  //触发beforeMount钩子函数
  callHook(vm, 'beforeMount')

  let updateComponent
  /* istanbul ignore if */
  //调用_render生成虚拟Node,调用_update更新虚拟Node
  //if中的逻辑是打开浏览器性能分析时做前端埋点,主要是看else
  if (__DEV__ && 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._update(vm._render(), hydrating)
    }
  }

  const watcherOptions: WatcherOptions = {
    before() {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }

  if (__DEV__) {
    watcherOptions.onTrack = e => callHook(vm, 'renderTracked', [e])
    watcherOptions.onTrigger = e => callHook(vm, 'renderTriggered', [e])
  }

  // 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,
    watcherOptions,
    true /* isRenderWatcher */
  )
  hydrating = false

  // flush buffer for flush: "pre" watchers queued in setup()
  const preWatchers = vm._preWatchers
  if (preWatchers) {
    for (let i = 0; i < preWatchers.length; i++) {
      preWatchers[i].run()
    }
  }

  // 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
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值