Understanding Vue.js mergeOptions: How Merge Strategies Work

This article deeply analyzes Vue.js's mergeOptions function, explaining how merge strategies are defined and applied to options like data, props, hooks, assets, and watchers, with detailed code examples and step‑by‑step breakdowns to help developers understand Vue's option merging mechanics.

WeDoctor Frontend Technology
WeDoctor Frontend Technology
WeDoctor Frontend Technology
Understanding Vue.js mergeOptions: How Merge Strategies Work

Yuan Dong, a front‑end engineer at WeDoctor, shares an in‑depth analysis of Vue.js's option merging mechanism.

Definition of Merge Strategies

After standardizing props, inject, directives, and the extends / mixins options, the core of mergeOptions is the definition of merge strategies.

The final part of the mergeOptions source code is:

const options = {}
let key
for (key in parent) {
  mergeField(key)
}
for (key in child) {
  if (!hasOwn(parent, key)) {
    mergeField(key)
  }
}
function mergeField (key) {
  const strat = strats[key] || defaultStrat
  options[key] = strat(parent[key], child[key], vm, key)
}

Both parent and child objects are iterated, and for each key the mergeField() function is called. mergeField() selects a strategy from strats[key] or falls back to defaultStrat and applies it.

defaultStrat

/**
 * Default strategy.
 */
const defaultStrat = function (parentVal, childVal) {
  return childVal === undefined
    ? parentVal
    : childVal
}

The logic of defaultStrat is simple: if the child provides a value, use it; otherwise fall back to the parent value, giving priority to the child.

strats[key]

const strats = config.optionMergeStrategies
export type Config = {
  // user
  optionMergeStrategies: { [key: string]: Function };
  ...
}

Developers can define custom merge functions for specific keys in config.optionMergeStrategies.

config.optionMergeStrategies.el, config.optionMergeStrategies.propsData

if (process.env.NODE_ENV !== 'production') {
  strats.el = strats.propsData = function (parent, child, vm, key) {
    if (!vm) {
      warn(`option "${key}" can only be used during instance creation with the `new` keyword.`)
    }
    return defaultStrat(parent, child)
  }
}

Both el and propsData use the default strategy: the child value wins if present, otherwise the parent value is used.

config.optionMergeStrategies.data, config.optionMergeStrategies.provide

strats.data = function (parentVal, childVal, vm) {
  if (!vm) {
    if (childVal && typeof childVal !== 'function') {
      process.env.NODE_ENV !== 'production' && warn('The "data" option should be a function that returns a per‑instance value in component definitions.', vm)
      return parentVal
    }
    return mergeDataOrFn(parentVal, childVal)
  }
  return mergeDataOrFn(parentVal, childVal, vm)
}
strats.provide = mergeDataOrFn

The data and provide strategies delegate to mergeDataOrFn, handling both constructor‑level and instance‑level merges.

If no vm (i.e., the component is created via Vue.extend()), the function checks whether childVal is a function and merges accordingly.

If a vm instance exists (i.e., created with new Vue()), the instance data is merged with the constructor data.

export function mergeDataOrFn (parentVal, childVal, vm) {
  if (!vm) {
    // Vue.extend merge: both should be functions
    if (!childVal) {
      return parentVal
    }
    if (!parentVal) {
      return childVal
    }
    return function mergedDataFn () {
      return mergeData(
        typeof childVal === 'function' ? childVal.call(this, this) : childVal,
        typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal
      )
    }
  } else {
    return function mergedInstanceDataFn () {
      const instanceData = typeof childVal === 'function'
        ? childVal.call(vm, vm)
        : childVal
      const defaultData = typeof parentVal === 'function'
        ? parentVal.call(vm, vm)
        : parentVal
      if (instanceData) {
        return mergeData(instanceData, defaultData)
      } else {
        return defaultData
      }
    }
  }
}

The helper mergeData recursively merges two data objects, skipping the internal __ob__ marker.

function mergeData (to, from) {
  if (!from) return to
  let key, toVal, fromVal
  const keys = hasSymbol
    ? Reflect.ownKeys(from)
    : Object.keys(from)
  for (let i = 0; i < keys.length; i++) {
    key = keys[i]
    // in case the object is already observed...
    if (key === '__ob__') continue
    toVal = to[key]
    fromVal = from[key]
    if (!hasOwn(to, key)) {
      set(to, key, fromVal)
    } else if (
      toVal !== fromVal &&
      isPlainObject(toVal) &&
      isPlainObject(fromVal)
    ) {
      mergeData(toVal, fromVal)
    }
  }
  return to
}

Hook Merging Strategy

export const LIFECYCLE_HOOKS = [
  'beforeCreate',
  'created',
  'beforeMount',
  'mounted',
  'beforeUpdate',
  'updated',
  'beforeDestroy',
  'destroyed',
  'activated',
  'deactivated',
  'errorCaptured',
  'serverPrefetch'
]
function mergeHook (parentVal, childVal) {
  const res = childVal
    ? parentVal
      ? parentVal.concat(childVal)
      : Array.isArray(childVal)
        ? childVal
        : [childVal]
    : parentVal
  return res ? dedupeHooks(res) : res
}
function dedupeHooks (hooks) {
  const res = []
  for (let i = 0; i < hooks.length; i++) {
    if (res.indexOf(hooks[i]) === -1) {
      res.push(hooks[i])
    }
  }
  return res
}
LIFECYCLE_HOOKS.forEach(hook => {
  strats[hook] = mergeHook
})

Each lifecycle hook is merged as an array; duplicate functions are removed by dedupeHooks.

Assets (components, directives, filters) Merging Strategy

function mergeAssets (parentVal, childVal, vm, key) {
  const res = Object.create(parentVal || null)
  if (childVal) {
    process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm)
    return extend(res, childVal)
  } else {
    return res
  }
}
export const ASSET_TYPES = [
  'component',
  'directive',
  'filter'
]
ASSET_TYPES.forEach(type => {
  strats[type + 's'] = mergeAssets
})

If the child defines these assets, they are merged with the parent; otherwise the parent assets are returned unchanged.

props / methods / inject / computed Merging Strategy

strats.props =
strats.methods =
strats.inject =
strats.computed = function (parentVal, childVal, vm, key) {
  if (childVal && process.env.NODE_ENV !== 'production') {
    assertObjectType(key, childVal, vm)
  }
  if (!parentVal) return childVal
  const ret = Object.create(null)
  extend(ret, parentVal)
  if (childVal) extend(ret, childVal)
  return ret
}

The strategy creates a new object, copies the parent options, then merges the child options if present.

watch Merging Strategy

strats.watch = function (parentVal, childVal, vm, key) {
  if (parentVal === nativeWatch) parentVal = undefined
  if (childVal === nativeWatch) childVal = undefined
  if (!childVal) return Object.create(parentVal || null)
  if (process.env.NODE_ENV !== 'production') {
    assertObjectType(key, childVal, vm)
  }
  if (!parentVal) return childVal
  const ret = {}
  extend(ret, parentVal)
  for (const key in childVal) {
    let parent = ret[key]
    const child = childVal[key]
    if (parent && !Array.isArray(parent)) {
      parent = [parent]
    }
    ret[key] = parent ? parent.concat(child) : Array.isArray(child) ? child : [child]
  }
  return ret
}

Watchers are merged as arrays, ensuring that multiple handlers for the same key are combined.

Conclusion

The three‑part series thoroughly dissects Vue.js's option merging process, covering data, hooks, assets, props, methods, inject, computed, and watch. Understanding these strategies helps developers customize component behavior and debug complex inheritance scenarios.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

frontendVue.jsmergeOptionsoption merging
WeDoctor Frontend Technology
Written by

WeDoctor Frontend Technology

Official WeDoctor Group frontend public account, sharing original tech articles, events, job postings, and occasional daily updates from our tech team.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.