A deep dive in the Vue.js source code (#7)

Photo by Markus Spiske on Unsplash

This is the seventh post in a series examining the Vue.js source code line-by-line. In this post, we continue our look at the mergeOptions function.

function mergeOptions (
parent,
child,
vm
) {
if (process.env.NODE_ENV !== 'production') {
checkComponents(child);
}
  if (typeof child === 'function') {
child = child.options;
}
  normalizeProps(child, vm);
normalizeInject(child, vm);
normalizeDirectives(child);
var extendsFrom = child.extends;
if (extendsFrom) {
parent = mergeOptions(parent, extendsFrom, vm);
}
if (child.mixins) {
for (var i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm);
}
}
var options = {};
var key;
for (key in parent) {
mergeField(key);
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key);
}
}
function mergeField (key) {
var strat = strats[key] || defaultStrat;
options[key] = strat(parent[key], child[key], vm, key);
}
return options
}

In the last post, we examined the checkComponents function.

As we continue through the code, the mergeOptions function uses the typeof operator on the child parameter to determine whether the parameter is a function. If thechild parameter is a function, child is set to child.options

if (typeof child === 'function') {
child = child.options;
}

As you will recall, however, in Vue.prototype._init, the mergeOptions function is passed a child parameter of either:

  • options : The options passed to the Vue object constructor function; or
  • {} : an empty object, if no options are passed.

Next, the mergeOptions function calls three normalization functions:

normalizeProps(child, vm);
normalizeInject(child, vm);
normalizeDirectives(child);

Let’s take each in turn. In the rest of this post, we will start to tackle the normalizeProps function. We begin with the comment introducing the function which explains that normalizeProps “Ensure[s] all props option syntax are normalized into the Object-based format.” So, as we would expect, normalizeProps takes an options parameter. It also takes vm (i.e., the Vue instance/this).

function normalizeProps (options, vm) {
var props = options.props;
if (!props) { return }
var res = {};
var i, val, name;
if (Array.isArray(props)) {
i = props.length;
while (i--) {
val = props[i];
if (typeof val === 'string') {
name = camelize(val);
res[name] = { type: null };
} else if (process.env.NODE_ENV !== 'production') {
warn('props must be strings when using array syntax.');
}
}
} else if (isPlainObject(props)) {
for (var key in props) {
val = props[key];
name = camelize(key);
res[name] = isPlainObject(val)
? val
: { type: val };
}
} else if (process.env.NODE_ENV !== 'production') {
warn(
"Invalid value for option \"props\": expected an Array or an Object, " +
"but got " + (toRawType(props)) + ".",
vm
);
}
options.props = res;
}

Then normalizeProps initializes a variable props setting it to theprops property on options:

var props = options.props;

If options does not have a props property, the function returns:

if (!props) { return }

normalizeProps then initializes a variable res and sets it to an empty object:

var res = {};

Followed by a declaration of three variables:

var i, val, name;

The remainder of a the function presents a long conditional statement:

  1. Checking whether props is an array;
  2. If not an array, checking whether the result of calling isPlainObject and passing props as a parameter is true
  3. If not, checking whether the environment is not a production environment.
if (Array.isArray(props)) {
i = props.length;
while (i--) {
val = props[i];
if (typeof val === 'string') {
name = camelize(val);
res[name] = { type: null };
} else if (process.env.NODE_ENV !== 'production') {
warn('props must be strings when using array syntax.');
}
}
} else if (isPlainObject(props)) {
for (var key in props) {
val = props[key];
name = camelize(key);
res[name] = isPlainObject(val)
? val
: { type: val };
}
} else if (process.env.NODE_ENV !== 'production') {
warn(
"Invalid value for option \"props\": expected an Array or an Object, " +
"but got " + (toRawType(props)) + ".",
vm
);
}

First, if props is an array, normalizeProps set the i variable to the length of the props array:

if (Array.isArray(props)) {
i = props.length;
while (i--) {
val = props[i];
if (typeof val === 'string') {
name = camelize(val);
res[name] = { type: null };
} else if (process.env.NODE_ENV !== 'production') {
warn('props must be strings when using array syntax.');
}
}
}

Then the function decrements down through the props array, setting val to the i element of the array:

if (Array.isArray(props)) {
i = props.length;
while (i--) {
val = props[i];

if (typeof val === 'string') {
name = camelize(val);
res[name] = { type: null };
} else if (process.env.NODE_ENV !== 'production') {
warn('props must be strings when using array syntax.');
}
}
}

If the element is a string, normalizeProps (1) calls a function camelize and passes val as a parameter and (2) maps name to the res object as a property and sets it to an object with a type property set to null.

if (Array.isArray(props)) {
i = props.length;
while (i--) {
val = props[i];
if (typeof val === 'string') {
name = camelize(val);
res[name] = { type: null };
}
else if (process.env.NODE_ENV !== 'production') {
warn('props must be strings when using array syntax.');
}
}
}

The camelize function is declared elsewhere, warrants a detour, and will be addressed in the next post. If you like the series and want to motivate me to keep working through it, please feel free to clap, follow, respond, or share to your heart’s content.

Next Post: