vue快速入门系列——实例方法(或property)和静态方法

博客 动态
0 442
张三
张三 2022-01-24 11:58:11
悬赏:0 积分 收藏

vue 快速入门 系列 —— 实例方法(或 property)和静态方法

其他章节请看:

vue 快速入门 系列

实例方法(或 property)和静态方法

在 Vue(自身) 项目结构 一文中,我们研究了 vue 项目自身构建过程,也知晓了 import Vue from 'core/index' 就是引入 vue 的核心代码,该文件的前两行对应着 vue 的实例方法和静态方法(或称全局方法),本篇就和大家一起来看一下这两部分。

// vuev2.5.20/src/core/index.js// 返回 Vue 构造函数,并准备好实例方法import Vue from './instance/index'// 静态方法(或称全局方法)import { initGlobalAPI } from './global-api/index'...

Tipvuev2.5.20 只是 vue 克隆到本地的项目名,下面将默认是这个项目。

实例方法(instance/index.js)

instance/index.js 的内容并不复杂,全部代码不到 30 行:

// src/core/instance/index.js 全部代码: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)}// 初始化相关。每次调用 new Vue() 就会被执行,里面包含很多初始化操作initMixin(Vue)// 状态相关。数据相关更确切stateMixin(Vue)// 事件相关eventsMixin(Vue)// 生命周期相关lifecycleMixin(Vue)// 渲染相关renderMixin(Vue)export default Vue

首先定义 Vue 构造函数,接着调用initMixin()stateMixin()等 5 个方法,给 Vue 的原型添加方法(或 property)。以下是 initMixin()stateMixin() 的代码片段:

export function initMixin (Vue: Class<Component>) {  Vue.prototype._init = function (options?: Object) {    ...  }}
export function stateMixin (Vue: Class<Component>) {  ...  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 {    ...  }}

剩余的三个方法也类似,也是给 Vue 的 prototype 增加方法。

以下是每个方法中在 Vue 的原型上定义的方法或 property:

  1. initMixin(Vue) - _init()
  2. stateMixin(Vue) - $data$props$set()$delete()$watch()
  3. eventsMixin(Vue) - $on()$once()$off()$emit()
  4. lifecycleMixin(Vue) - _update()$forceUpdate()$destroy()
  5. renderMixin(Vue) - $nextTick()_render()

接着,我们将逐一分析其中的实现。

initMixin(初始化相关)

此方法仅仅给 Vue 定义了一个原型方法,即 _init()。每次调用 new Vue() 就会被执行。

function Vue (options) {  ...  this._init(options)}

如果你看过 jQuery 的源码,里面也有一个类似的方法,是 init

var jQuery = function( selector, context ) {	// 返回jQuery对象	return new jQuery.fn.init( selector, context, rootjQuery );}jQuery.fn = jQuery.prototype = {	constructor: jQuery,	init: function( selector, context, rootjQuery ) {    // 很多逻辑的处理    ...  }  ...}

每次执行 jQuery(jqOptions) 都会调用 new jQuery.fn.init(),里面有非常多的处理,所以我们可以给 jqOptions 传递多种不同类型的参数。

同样的,new Vue() 也支持多种参数,所以这个 _init() 里面也会做很多事情:

// 核心代码Vue.prototype._init = function (options?: Object) {    const vm: Component = this    // 合并参数    if (options && options._isComponent) {      initInternalComponent(vm, options)    } else {      vm.$options = mergeOptions(        resolveConstructorOptions(vm.constructor),        options || {},        vm      )    }    // 初始化生命周期、初始化事件...    initLifecycle(vm)    initEvents(vm)    initRender(vm)    // 触发生命钩子:beforeCreate     callHook(vm, 'beforeCreate')    // resolve injections before data/props    // 我们可以使用的顺序:inject -> data/props -> provide    initInjections(vm)     initState(vm)    initProvide(vm) // resolve provide after data/props    callHook(vm, 'created')    if (vm.$options.el) {      // 挂载      vm.$mount(vm.$options.el)    }  }

_init() 方法依次会合并参数、接着对生命周期、事件等做初始化,其中也会依次触发生命周期钩子 beforeCreatecreated,最后根据条件进行挂载。

stateMixin(数据相关)

stateMixin(Vue) 中 给 Vue 的原型定义的方法(或 property)有:$data$props$set()$delete()$watch()

vm.$data、vm.$props

vm.$data ,返回 Vue 实例观察的数据对象。

vm.$props,返回当前组件接收到的 props 对象。

vm.$set()、vm.$delete() 和 vm.$watch()

$set、$delete 和 $watch 都是与数据相关。出现的背景和原理分析请看 侦测数据的变化 - [vue api 原理]

eventsMixin(事件相关)

eventsMixin(Vue) 中定义的的原型方法有:$on$once$off$emit。分别是注册事件,注册只触发一次的事件、解除事件、触发自定义事件。

vm.$on()

这四个方法,$on() 应该是核心,只有先注册事件,才能解绑事件,或触发事件。

用法如下:

vm.$on('test', function (msg) {  console.log(msg)})vm.$emit('test', 'hi')// => "hi"

以下是 $on() 的源码,核心功能就是“收集事件和对应的回调”:

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 {    // 将回调存入 event 对应的数组中    (vm._events[event] || (vm._events[event] = [])).push(fn)    ...  }  return vm}
vm.$once()

$once(),注册只触发一次的事件。

如果你看过 jQuery 源码(事件系统),也能猜到 $once 应该是基于 $on 来实现:

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    // 在 $on 的基础上实现。给回调设置一个代理    vm.$on(event, on)    return vm  }
vm.$off()

$off(),解除事件绑定。

逻辑比较简单:支持没有传参的情况、传参是数组、解绑指定事件、解绑指定事件的回调。请看源码:

Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {  const vm: Component = this  // 如果没有提供参数,则移除所有的事件监听器  if (!arguments.length) {    // Object.create(null) 创建一个原型为null的空对象,以此方式来清空事件    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  }  // specific event  // 解绑指定的事件  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    }  }  return vm}
vm.$emit()

$emit(),触发当前实例上的事件。

取得回调数组,依次触发回调。请看源码:

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}

lifecycleMixin(生命周期相关)

lifecycleMixin(Vue) 中给 Vue 定义的原型方法有:_update()$forceUpdate()$destroy()

vm.$forceUpdate()

迫使 Vue 实例重新渲染。注意它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。

代码出奇的少:

// $forceUpdate() 的全部代码Vue.prototype.$forceUpdate = function () {  const vm: Component = this  if (vm._watcher) {    vm._watcher.update()   }}

关键应该就是 vm._watcher.update()

通过全局搜索,发现 vm._watcher 赋值是在 watcher.js 的构造函数中(行{1}):

// src/core/observer/watcher.jsexport default class Watcher {  vm: Component;  ...  constructor (    vm: Component,    expOrFn: string | Function,    cb: Function,    options?: ?Object,    isRenderWatcher?: boolean  ) {    this.vm = vm    if (isRenderWatcher) {      vm._watcher = this // {1}    }

于是我们知道 vm 的 _watcher 是一个 Watcher。而根据 侦测数据的变化 - [基本实现] 中对 Watcher 的研究,我们写过这样一段代码:

class Watcher{    ...    // 数据改变时收到通知,然后再通知到外界    update(newVal, oldVal){        this.callback(newVal, oldVal)    }}

调用 update(),会通知到外界。这里的外界或许就是 vm,然后 vm 会做一些事情,其中或许就包含重新渲染。

vm.$destroy()

$destroy(),完全销毁一个实例。清理它与其它实例的连接,解绑它的全部指令及事件监听器。请看源码:

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  if (vm._watcher) {    // teardown,取消观察。也就是从所有依赖(Dep)中把自己删除    vm._watcher.teardown()  }  // 解绑用户通过 vm.$watch 添加的监听。  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 已被销毁  vm._isDestroyed = true  // invoke destroy hooks on current rendered tree  // 在当前渲染的树上调用销毁钩子  vm.__patch__(vm._vnode, null)  // 调用钩子:destroyed  callHook(vm, 'destroyed')  // 解绑所有事件  vm.$off()  // remove __vue__ reference  if (vm.$el) {    vm.$el.__vue__ = null  }  // release circular reference (#6759)  // 释放循环引用 (#6759)  if (vm.$vnode) {    vm.$vnode.parent = null  }}

Tipvm._watchers 是在 Watcher 的构造函数中添加元素的,而在 vm.$watch 方法中就有 new Watcher(),所以推测 vm._watchers 中的内容来自 vm.$watch

export default class Watcher {  vm: Component;  ...  constructor (    vm: Component,    expOrFn: string | Function,    cb: Function,    options?: ?Object,    isRenderWatcher?: boolean  ) {    this.vm = vm    if (isRenderWatcher) {      vm._watcher = this    }    vm._watchers.push(this)    ...  }}
Vue.prototype.$watch = function (    expOrFn: string | Function,    cb: any,    options?: Object  ): Function {    const vm: Component = this    ...    const watcher = new Watcher(vm, expOrFn, cb, options)    ...  }

renderMixin(渲染相关)

renderMixin(Vue) 中给 Vue 定义的原型方法有:$nextTick()_render()

vm.$nextTick()

$nextTick(),将回调延迟到下次 DOM 更新循环之后执行

例如:

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

根据用法我们可以猜测 $nextTick() 会将我们我们的回调先保存起来,然后再合适的时间再触发。请看源码:

Vue.prototype.$nextTick = function (fn: Function) {  return nextTick(fn, this)}

代码应该在 nextTick() 中:

// next-tick.jsexport 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)    }  })  //感觉这里有秘密 {1}  if (!pending) {    pending = true    timerFunc()    }  ...}

nextTick() 中,首先将我们的回调保存起来,将回调方法封装到一个匿名函数,然后再存入 callbacks 数组中。

什么时候触发回调?感觉行{1}处有秘密。事实证明我猜对了,请看源码:

// next-tick.js 中相关代码如下:// 存放回调函数const callbacks = []let pending = false// 依次执行回调函数function flushCallbacks () {  pending = false  // 备份数组 callbacks。  // 防止遍历回调时,对 callbacks 修改  const copies = callbacks.slice(0)  // 清空数组 callbacks  callbacks.length = 0  // 依次执行回调  for (let i = 0; i < copies.length; i++) {    copies[i]()  }}// 这里我们有使用微任务的异步延迟包装器。let timerFuncif (typeof Promise !== 'undefined' && isNative(Promise)) {  const p = Promise.resolve()  timerFunc = () => {    p.then(flushCallbacks)    // ios 兼容处理 Promise    if (isIOS) setTimeout(noop)  }  isUsingMicroTask = true} else if (!isIE && typeof MutationObserver !== 'undefined' && (  isNative(MutationObserver) ||  // PhantomJS and iOS 7.x  MutationObserver.toString() === '[object MutationObserverConstructor]')) {  // MutationObserver 也是微任务  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  // window.setImmediate,该特性是非标准的,请尽量不要在生产环境中使用它!  // ie支持, 作用类似于 setTimeout(fn, 0)} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {  timerFunc = () => {    setImmediate(flushCallbacks)  }} else {  // 回退到 setTimeout。  timerFunc = () => {    setTimeout(flushCallbacks, 0)  }}

timerFunc 是一个函数,会根据条件选择不同的异步方法封装。这里有4种异步方法,Promise.thenMutationObserver 是微任务,后两种(setImmediatesetTimeout)是宏任务。

已第一种为例:

timerFunc = () => {  p.then(flushCallbacks)  // ios 兼容处理 Promise  if (isIOS) setTimeout(noop)}

当我们执行 timerFunc() 时,就会立刻执行 p.then(flushCallbacks)flushCallbacks() 方法会依次执行回调函数,但 flushCallbacks() 方法只会在合适的时间被触发(即事件循环中)。

最后我们总结下:执行 this.$nextTick(fn) ,将进入 nextTick(),首先将我们的回调函数用匿名函数封装起来,在将这个匿名函数扔在 callbacks 中,因为没有代办事项(let pending = false),于是执行 timerFunc()

Tip: 有关事件循环、微任务和宏任务的介绍可以看这里

静态方法(global-api/index.js)

global-api/index.js 文件代码总共约 70 行,除去通过 import 导入的 20 行,剩余的都在 initGlobalAPI() 方法中:

export function initGlobalAPI (Vue: GlobalAPI) {  // config  const configDef = {}  configDef.get = () => config  ...  Object.defineProperty(Vue, 'config', configDef)  // 暴露的 util 方法。注意:这些不被视为公共 API 的一部分 - 避免依赖  Vue.util = {    ...  }  // vm.$set 是 Vue.set 的别名。  // delete 和 nextTick 类似  Vue.set = set  Vue.delete = del  Vue.nextTick = nextTick  // 2.6 explicit observable API  // 让一个对象可响应  Vue.observable = <T>(obj: T): T => {    observe(obj)    return obj  }  Vue.options = Object.create(null)  // ASSET_TYPES = [ 'component', 'directive', 'filter' ]  ASSET_TYPES.forEach(type => {    Vue.options[type + 's'] = Object.create(null)  })  // this is used to identify the "base" constructor to extend all plain-object  // components with in Weex's multi-instance scenarios.  Vue.options._base = Vue  extend(Vue.options.components, builtInComponents)  // 定义了一个方法:Vue.use  initUse(Vue)  // 定义了一个方法:Vue.mixin()  initMixin(Vue)  // 初始化继承。定义了一个方法:Vue.extned()  initExtend(Vue)  // 初始化资产注册。定义了三个方法:Vue.component()、Vue.directive()和Vue.filter()  initAssetRegisters(Vue)}

接着我们就源码的顺序依次分析。

Vue.set()、Vue.delete()和Vue.nextTick()

  • Vue.set() 是 vm.$set() 的别名

  • Vue.delete() 是 vm.$delete 的别名

  • Vue.nextTick() 是 vm.$nextTick() 的别名

Vue.observable()

代码很少:

Vue.observable = <T>(obj: T): T => {    observe(obj)    return obj  }

直接看官网介绍:

用法:让一个对象可响应。Vue 内部会用它来处理 data 函数返回的对象。

返回的对象可以直接用于渲染函数和计算属性内,并且会在发生变更时触发相应的更新。也可以作为最小化的跨组件状态存储器,用于简单的场景:

const state = Vue.observable({ count: 0 })const Demo = {  render(h) {    return h('button', {      on: { click: () => { state.count++ }}    }, `count is: ${state.count}`)  }}

initUse

// src/core/global-api/use.jsexport function initUse (Vue: GlobalAPI) {  Vue.use = function (plugin: Function | Object) {    // 按照了的插件    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))    // 如果已经安装了此插件,就直接返回 Vue    if (installedPlugins.indexOf(plugin) > -1) {      return this    }    // additional parameters    // 附加参数    const args = toArray(arguments, 1)    // this 插入第一位    args.unshift(this)    // 插件的 install 属性 如果是函数,则执行 install    if (typeof plugin.install === 'function') {      plugin.install.apply(plugin, args)    // 如果 plugin 本身是函数,就执行 plugin    } else if (typeof plugin === 'function') {      plugin.apply(null, args)    }    // 将该插件放入已安装的插件列表中    installedPlugins.push(plugin)    return this  }}
Vue.use()

initUse(Vue) 中只定义了 Vue.use() 这个全局方法。根据前面的源码分析,此方法的作用应该是安装插件。我们查阅 api 确认一下。

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

Tip:到底什么是插件?官网:插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制——一般有下面几种:

  1. 添加全局方法或者 property。如:vue-custom-element
  2. 添加全局资源:指令/过滤器/过渡等。如 vue-touch
  3. 通过全局混入来添加一些组件选项。如 vue-router
  4. 添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。
  5. 一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如 vue-router

这样说来,插件的用处挺大的。

Tip:与 jQuery 有些类似,jQuery 也提供了方法(如 jQuery.extend = jQuery.fn.extend = function() {})来扩展核心功能。

initMixin

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

initMixin(Vue) 中只定义了一个全局方法 Vue.mixin

mixin 有点类似 Object.assign 的味道。我们看一下官网的介绍:

用法:全局注册一个混入,影响注册之后所有创建的每个 Vue 实例。不推荐在应用代码中使用。

我们在看一下它的用法(来自官网):

// 为自定义的选项 'myOption' 注入一个处理器。Vue.mixin({  created: function () {    var myOption = this.$options.myOption    if (myOption) {      console.log(myOption)    }  }})new Vue({  myOption: 'hello!'})// => "hello!"

根据用法和源码,我们能推测出,Vue.mixin 就是将我们的参数合并到 Vue(this.options) 中,后续创建 Vue 实例时这些参数就会有体现。

initExtend

Vue.extend()

initExtend(Vue) 中只定义了 Vue.extend() 这个全局方法。

extend 这个方法名,又让我想起 Object.assign、jQuery.extend。让我们先看一下 api 如何介绍:

用法:使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。

好像猜错了。根据用法介绍,Vue.extend 的作用更像 es6 中 Class 的 extends。直接分析其源码再次验证我们的猜测:

Vue.extend = function (extendOptions: Object): Function {  extendOptions = extendOptions || {}  // Super 指向 Vue  const Super = this  const SuperId = Super.cid  // 缓存相关 {1}  const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})  if (cachedCtors[SuperId]) {    return cachedCtors[SuperId]  }  const name = extendOptions.name || Super.options.name  ...  // 定义一个子类 Sub  const Sub = function VueComponent (options) {    this._init(options)  }  // 以 Vue 的原型作为新建对象的原型,并将新建对象设置为 Sub 的原型  // 用 new Sub() 创建的对象也能访问到 Vue.prototype 中的方法或属性  Sub.prototype = Object.create(Super.prototype)  Sub.prototype.constructor = Sub  Sub.cid = cid++  // 合并参数  Sub.options = mergeOptions(    Super.options,    extendOptions  )  // 新增 super 属性,指向 Vue  Sub['super'] = Super  if (Sub.options.props) {    initProps(Sub)  }  if (Sub.options.computed) {    initComputed(Sub)  }  // 允许进一步的扩展/混合/插件使用  Sub.extend = Super.extend  Sub.mixin = Super.mixin  Sub.use = Super.use  ...  // 在扩展时保留对超级选项的引用。  Sub.superOptions = Super.options  Sub.extendOptions = extendOptions  Sub.sealedOptions = extend({}, Sub.options)  // 缓存构造函数  cachedCtors[SuperId] = Sub  // 返回子类 Sub  return Sub}

执行 Vue.extend() 将返回一个子类 Sub,该子类继承了父级(Vue)。为了提高性能,里面增加了缓存机制(行{1})。

initAssetRegisters

initAssetRegisters(Vue) 中定义了三个全局方法。

export function initAssetRegisters (Vue: GlobalAPI) {  /**   * Create asset registration methods.   * 创建资产注册方法。   */  ASSET_TYPES.forEach(type => {    Vue[type] = function (      id: string,      definition: Function | Object    ): Function | Object | void {      ...    }  })}

ASSET_TYPES 是一个数组:

export const ASSET_TYPES = [  'component',  'directive',  'filter']

Tip:如果你看过 jQuery,你也会发现有类似的写法,也就是当多个方法的逻辑相似,就可以写在一起,显得精简。

Vue.component()、Vue.directive()和Vue.filter()

直接看源码有点难懂(注释可稍后再看):

ASSET_TYPES.forEach(type => {  Vue[type] = function (    id: string,    definition: Function | Object  ): Function | Object | void {    // 没有传第二个参数,表示获取。比如获取指令    if (!definition) {      return this.options[type + 's'][id]    } else {      // 组件的特别处理      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    }  }})

我们翻阅一下对应 api:

  • Vue.filter( id, [definition] ) - 注册或获取全局过滤器。
// 注册Vue.filter('my-filter', function (value) {  // 返回处理后的值})// getter,返回已注册的过滤器var myFilter = Vue.filter('my-filter')
  • Vue.directive() - 注册或获取全局指令。

  • Vue.component() - 注册或获取全局组件

核心就是注册获取。感觉作用比较简单,有点类似注册事件,取得事件的回调。现在我们在来看一下源码和相应的注释,会发现容易理解多了。

Tip:组件、指令、过滤器当然不是回调这么简单,所以我们可以猜测:这里只是负责注册和获取,至于如何生效,却不在此处。

分布它处的方法

vm.$mount()

如果 Vue 实例在实例化时没有收到 el 选项,则它处于“未挂载”状态,没有关联的 DOM 元素。可以使用 vm.$mount() 手动地挂载一个未挂载的实例。

项目中搜索 $mount ,发现有三个文件对其有定义 Vue.prototype.$mount =

  1. platforms\web\runtime\index.js
  2. platforms\weex\runtime\index.js
  3. platforms\web\entry-runtime-with-compiler.js

前两个文件是根据不同的平台(web或weex)对 $mount 方法的不同的定义。

而最后一个文件有些意思,请看源码:

// entry-runtime-with-compiler.js// 函数劫持。保存原始 $mountconst mount = Vue.prototype.$mount    // {1}// 对原始 $mount 进行封装。主要做的工作是将模板编译成渲染函数Vue.prototype.$mount = function (     // {2}  el?: string | Element,  hydrating?: boolean): Component {  el = el && query(el)  ...  const options = this.$options  // resolve template/el and convert to render function  // 解析模板/el并转换为渲染函数  if (!options.render) {    ...  }  return mount.call(this, el, hydrating)}

在 Vue(自身) 项目结构 一文中,我们知道 entry-runtime-with-compiler.js 生成的文件是完整版本(dist/vue.js),完整版本是包含编译器的,而我们在 模板 一文中也知晓了编译器的主要作用,将模板编译成渲染函数。

所以我们能推测出这个 $mount(行{2}),就是在原始 $mount(行{1})中增加编译器的功能。

其他章节请看:

vue 快速入门 系列

作者:彭加李
出处:https://www.cnblogs.com/pengjiali/p/15834451.html
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
posted @ 2022-01-24 09:47 彭加李 阅读(1) 评论(0) 编辑 收藏 举报
回帖
    张三

    张三 (王者 段位)

    1111 积分 (2)粉丝 (41)源码

     

    温馨提示

    亦奇源码

    最新会员