Vue 源码所有工具类方法汇总(包含常量)
近期打算写一个Vue源码解析的系列,源码中有很多地方调用了公共方法,故把所有公共方法整理在了一起,方便后面超链接。后面也会把很多重要的方法也放在本文中,如果有时间,笔者会对本文部分方法补充中文注释。
src/core/util/
src/shared/
SSR_ATTR
export const SSR_ATTR = "data-server-rendered";
复制代码
ASSET_TYPES
export const ASSET_TYPES = ["component", "directive", "filter"];
复制代码
LIFECYCLE_HOOKS
export const LIFECYCLE_HOOKS = [
"beforeCreate",
"created",
"beforeMount",
"mounted",
"beforeUpdate",
"updated",
"beforeDestroy",
"destroyed",
"activated",
"deactivated",
"errorCaptured",
"serverPrefetch"
];
复制代码
hasProto
// ===================== env.js
// can we use **proto**?
export const hasProto = "**proto**" in {};
复制代码
inBrowser
// Browser environment sniffing
export const inBrowser = typeof window !== "undefined";
复制代码
inWeex
export const inWeex =
typeof WXEnvironment !== "undefined" && !!WXEnvironment.platform;
复制代码
weexPlatform
export const weexPlatform = inWeex && WXEnvironment.platform.toLowerCase();
复制代码
UA
export const UA = inBrowser && window.navigator.userAgent.toLowerCase();
复制代码
isIE
export const isIE = UA && /msie|trident/.test(UA);
复制代码
isIE9
export const isIE9 = UA && UA.indexOf("msie 9.0") > 0;
复制代码
isEdge
export const isEdge = UA && UA.indexOf("edge/") > 0;
复制代码
isAndroid
export const isAndroid =
(UA && UA.indexOf("android") > 0) || weexPlatform === "android";
复制代码
isIOS
export const isIOS =
(UA && /iphone|ipad|ipod|ios/.test(UA)) || weexPlatform === "ios";
复制代码
isChrome
export const isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge;
复制代码
isPhantomJS
export const isPhantomJS = UA && /phantomjs/.test(UA);
复制代码
isFF
export const isFF = UA && UA.match(/firefox\/(\d+)/);
复制代码
nativeWatch
// Firefox has a "watch" function on Object.prototype...
export const nativeWatch = {}.watch;
复制代码
supportsPassive
export let supportsPassive = false;
if (inBrowser) {
try {
const opts = {};
Object.defineProperty(
opts,
"passive",
({
get() {
/* istanbul ignore next */
supportsPassive = true;
}
}: Object)
); // https://github.com/facebook/flow/issues/285
window.addEventListener("test-passive", null, opts);
} catch (e) {}
}
复制代码
isServerRendering
let _isServer;
export const isServerRendering = () => {
if (_isServer === undefined) {
/* istanbul ignore if */
if (!inBrowser && !inWeex && typeof global !== "undefined") {
// detect presence of vue-server-renderer and avoid
// Webpack shimming the process
_isServer =
global["process"] && global["process"].env.VUE_ENV === "server";
} else {
_isServer = false;
}
}
return _isServer;
};
复制代码
devtools
// detect devtools
export const devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__;
复制代码
isNative
/* istanbul ignore next */
export function isNative(Ctor: any): boolean {
return typeof Ctor === "function" && /native code/.test(Ctor.toString());
}
复制代码
hasSymbol
export const hasSymbol =
typeof Symbol !== "undefined" &&
isNative(Symbol) &&
typeof Reflect !== "undefined" &&
isNative(Reflect.ownKeys);
复制代码
SimpleSet _Set
let _Set; // $flow-disable-line
/* istanbul ignore if */ if (typeof Set !== "undefined" && isNative(Set)) {
// use native Set when available.
_Set = Set;
} else {
// a non-standard Set polyfill that only works with primitive keys.
_Set = class Set implements SimpleSet {
set: Object;
constructor() {
this.set = Object.create(null);
}
has(key: string | number) {
return this.set[key] === true;
}
add(key: string | number) {
this.set[key] = true;
}
clear() {
this.set = Object.create(null);
}
};
}
interface SimpleSet {
has(key: string | number): boolean;
add(key: string | number): mixed;
clear(): void;
}
export { _Set };
export type { SimpleSet };
复制代码
warn
export let warn = noop;
复制代码
tip
export let tip = noop;
复制代码
generateComponentTrace
export let generateComponentTrace = (noop: any); // work around flow check
复制代码
formatComponentName
export let formatComponentName = (noop: any);
复制代码
handleError
export function handleError(err: Error, vm: any, info: string) {
// Deactivate deps tracking while processing error handler to avoid possible infinite rendering.
// See: https://github.com/vuejs/vuex/issues/1505
pushTarget();
try {
if (vm) {
let cur = vm;
while ((cur = cur.$parent)) {
const hooks = cur.$options.errorCaptured;
if (hooks) {
for (let i = 0; i < hooks.length; i++) {
try {
const capture = hooks[i].call(cur, err, vm, info) === false;
if (capture) return;
} catch (e) {
globalHandleError(e, cur, "errorCaptured hook");
}
}
}
}
}
globalHandleError(err, vm, info);
} finally {
popTarget();
}
}
复制代码
invokeWithErrorHandling
export 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)) {
// issue #9511
// reassign to res to avoid catch triggering multiple times when nested calls
res = res.catch(e => handleError(e, vm, info + ` (Promise/async)`));
}
} catch (e) {
handleError(e, vm, info);
}
return res;
}
function globalHandleError(err, vm, info) {
if (config.errorHandler) {
try {
return config.errorHandler.call(null, err, vm, info);
} catch (e) {
// if the user intentionally throws the original error in the handler,
// do not log it twice
if (e !== err) {
logError(e, null, "config.errorHandler");
}
}
}
logError(err, vm, info);
}
复制代码
logError
function logError(err, vm, info) {
if (process.env.NODE_ENV !== "production") {
warn(`Error in ${info}: "${err.toString()}"`, vm);
}
/* istanbul ignore else */
if ((inBrowser || inWeex) && typeof console !== "undefined") {
console.error(err);
} else {
throw err;
}
}
复制代码
unicodeRegExp
/**
* unicode letters used for parsing html tags, component names and property paths.
* using https://www.w3.org/TR/html53/semantics-scripting.html#potentialcustomelementname
* skipping \u10000-\uEFFFF due to it freezing up PhantomJS
*/
export const unicodeRegExp = /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/;
复制代码
isReserved
/**
* Check if a string starts with $ or _
*/
export function isReserved(str: string): boolean {
const c = (str + "").charCodeAt(0);
return c === 0x24 || c === 0x5f;
}
复制代码
def
/**
* Define a property.
*/
export function def(obj: Object, key: string, val: any, enumerable?: boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
});
}
复制代码
parsePath
/**
* Parse simple path.
*/
const bailRE = new RegExp(`[^${unicodeRegExp.source}.$_\\d]`);
export function parsePath(path: string): any {
if (bailRE.test(path)) {
return;
}
const segments = path.split(".");
return function(obj) {
for (let i = 0; i < segments.length; i++) {
if (!obj) return;
obj = obj[segments[i]];
}
return obj;
};
}
复制代码
isUsingMicroTask
/* @flow */
/* globals MutationObserver */
export let isUsingMicroTask = false;
复制代码
nextTick
const callbacks = [];
let pending = false;
function flushCallbacks() {
pending = false;
const copies = callbacks.slice(0);
callbacks.length = 0;
for (let i = 0; i < copies.length; i++) {
copies[i]();
}
}
// Here we have async deferring wrappers using microtasks.
// In 2.5 we used (macro) tasks (in combination with microtasks).
// However, it has subtle problems when state is changed right before repaint
// (e.g. #6813, out-in transitions).
// Also, using (macro) tasks in event handler would cause some weird behaviors
// that cannot be circumvented (e.g. #7109, #7153, #7546, #7834, #8109).
// So we now use microtasks everywhere, again.
// A major drawback of this tradeoff is that there are some scenarios
// where microtasks have too high a priority and fire in between supposedly
// sequential events (e.g. #4521, #6690, which have workarounds)
// or even between bubbling of the same event (#6566).
let timerFunc;
// The nextTick behavior leverages the microtask queue, which can be accessed
// via either native Promise.then or MutationObserver.
// MutationObserver has wider support, however it is seriously bugged in
// UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
// completely stops working after triggering a few times... so, if native
// Promise is available, we will use it:
/* istanbul ignore next, $flow-disable-line */
if (typeof Promise !== "undefined" && isNative(Promise)) {
const p = Promise.resolve();
timerFunc = () => {
p.then(flushCallbacks);
// In problematic UIWebViews, Promise.then doesn't completely break, but
// it can get stuck in a weird state where callbacks are pushed into the
// microtask queue but the queue isn't being flushed, until the browser
// needs to do some other work, e.g. handle a timer. Therefore we can
// "force" the microtask queue to be flushed by adding an empty timer.
if (isIOS) setTimeout(noop);
};
isUsingMicroTask = true;
} else if (
!isIE &&
typeof MutationObserver !== "undefined" &&
(isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === "[object MutationObserverConstructor]")
) {
// Use MutationObserver where native Promise is not available,
// e.g. PhantomJS, iOS7, Android 4.4
// (#6466 MutationObserver is unreliable in IE11)
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;
} else if (typeof setImmediate !== "undefined" && isNative(setImmediate)) {
// Fallback to setImmediate.
// Techinically it leverages the (macro) task queue,
// but it is still a better choice than setTimeout.
timerFunc = () => {
setImmediate(flushCallbacks);
};
} else {
// Fallback to setTimeout.
timerFunc = () => {
setTimeout(flushCallbacks, 0);
};
}
export function nextTick(cb?: Function, ctx?: Object) {
let _resolve;
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx);
} catch (e) {
handleError(e, ctx, "nextTick");
}
} else if (_resolve) {
_resolve(ctx);
}
});
if (!pending) {
pending = true;
timerFunc();
}
// $flow-disable-line
if (!cb && typeof Promise !== "undefined") {
return new Promise(resolve => {
_resolve = resolve;
});
}
}
复制代码
mergeData
function mergeData(to: Object, from: ?Object): Object {
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;
}
复制代码
mergeDataOrFn
/**
* Data
*/
export function mergeDataOrFn(
parentVal: any,
childVal: any,
vm?: Component
): ?Function {
if (!vm) {
// in a Vue.extend merge, both should be functions
if (!childVal) {
return parentVal;
}
if (!parentVal) {
return childVal;
}
// when parentVal & childVal are both present,
// we need to return a function that returns the
// merged result of both functions... no need to
// check if parentVal is a function here because
// it has to be a function to pass previous merges.
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() {
// instance merge
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;
}
};
}
}
复制代码
validateComponentName
export function validateComponentName(name: string) {
if (
!new RegExp(`^[a-zA-Z][\\-\\.0-9_${unicodeRegExp.source}]*$`).test(name)
) {
warn(
'Invalid component name: "' +
name +
'". Component names ' +
"should conform to valid custom element name in html5 specification."
);
}
if (isBuiltInTag(name) || config.isReservedTag(name)) {
warn(
"Do not use built-in or reserved HTML elements as component " +
"id: " +
name
);
}
}
复制代码
mergeOptions
/**
* Merge two option objects into a new one.
* Core utility used in both instantiation and inheritance.
*/
export function mergeOptions(
parent: Object,
child: Object,
vm?: Component
): Object {
if (process.env.NODE_ENV !== "production") {
checkComponents(child);
}
if (typeof child === "function") {
child = child.options;
}
normalizeProps(child, vm);
normalizeInject(child, vm);
normalizeDirectives(child);
// Apply extends and mixins on the child options,
// but only if it is a raw options object that isn't
// the result of another mergeOptions call.
// Only merged options has the _base property.
if (!child._base) {
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm);
}
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm);
}
}
}
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);
}
return options;
}
复制代码
normalizeProps
/**
* Ensure all props option syntax are normalized into the
* Object-based format.
*/
function normalizeProps(options: Object, vm: ?Component) {
const props = options.props;
if (!props) return;
const res = {};
let 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 (const 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;
}
复制代码
normalizeInject
/**
* Normalize all injections into Object-based format
*/
function normalizeInject(options: Object, vm: ?Component) {
const inject = options.inject;
if (!inject) return;
const normalized = (options.inject = {});
if (Array.isArray(inject)) {
for (let i = 0; i < inject.length; i++) {
normalized[inject[i]] = {
from: inject[i]
};
}
} else if (isPlainObject(inject)) {
for (const key in inject) {
const val = inject[key];
normalized[key] = isPlainObject(val)
? extend(
{
from: key
},
val
)
: {
from: val
};
}
} else if (process.env.NODE_ENV !== "production") {
warn(
`Invalid value for option "inject": expected an Array or an Object, ` +
`but got ${toRawType(inject)}.`,
vm
);
}
}
复制代码
normalizeDirectives
/**
* Normalize raw function directives into object format.
*/
function normalizeDirectives(options: Object) {
const dirs = options.directives;
if (dirs) {
for (const key in dirs) {
const def = dirs[key];
if (typeof def === "function") {
dirs[key] = {
bind: def,
update: def
};
}
}
}
}
复制代码
assertObjectType
function assertObjectType(name: string, value: any, vm: ?Component) {
if (!isPlainObject(value)) {
warn(
`Invalid value for option "${name}": expected an Object, ` +
`but got ${toRawType(value)}.`,
vm
);
}
}
复制代码
resolveAsset
/**
* Resolve an asset.
* This function is used because child instances need access
* to assets defined in its ancestor chain.
*/
export function resolveAsset(
options: Object,
type: string,
id: string,
warnMissing?: boolean
): any {
/* istanbul ignore if */
if (typeof id !== "string") {
return;
}
const assets = options[type];
// check local registration variations first
if (hasOwn(assets, id)) return assets[id];
const camelizedId = camelize(id);
if (hasOwn(assets, camelizedId)) return assets[camelizedId];
const PascalCaseId = capitalize(camelizedId);
if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId];
// fallback to prototype chain
const res = assets[id] || assets[camelizedId] || assets[PascalCaseId];
if (process.env.NODE_ENV !== "production" && warnMissing && !res) {
warn("Failed to resolve " + type.slice(0, -1) + ": " + id, options);
}
return res;
}
复制代码
validateProp
export function validateProp(
key: string,
propOptions: Object,
propsData: Object,
vm?: Component
): any {
const prop = propOptions[key];
const absent = !hasOwn(propsData, key);
let value = propsData[key];
// boolean casting
const booleanIndex = getTypeIndex(Boolean, prop.type);
if (booleanIndex > -1) {
if (absent && !hasOwn(prop, "default")) {
value = false;
} else if (value === "" || value === hyphenate(key)) {
// only cast empty string / same name to boolean if
// boolean has higher priority
const stringIndex = getTypeIndex(String, prop.type);
if (stringIndex < 0 || booleanIndex < stringIndex) {
value = true;
}
}
}
// check default value
if (value === undefined) {
value = getPropDefaultValue(vm, prop, key);
// since the default value is a fresh copy,
// make sure to observe it.
const prevShouldObserve = shouldObserve;
toggleObserving(true);
observe(value);
toggleObserving(prevShouldObserve);
}
if (
process.env.NODE_ENV !== "production" &&
// skip validation for weex recycle-list child component props
!(__WEEX__ && isObject(value) && "@binding" in value)
) {
assertProp(prop, key, value, vm, absent);
}
return value;
}
复制代码
getPropDefaultValue
/**
* Get the default value of a prop.
*/
function getPropDefaultValue(
vm: ?Component,
prop: PropOptions,
key: string
): any {
// no default, return undefined
if (!hasOwn(prop, "default")) {
return undefined;
}
const def = prop.default;
// warn against non-factory defaults for Object & Array
if (process.env.NODE_ENV !== "production" && isObject(def)) {
warn(
'Invalid default value for prop "' +
key +
'": ' +
"Props with type Object/Array must use a factory function " +
"to return the default value.",
vm
);
}
// the raw prop value was also undefined from previous render,
// return previous default value to avoid unnecessary watcher trigger
if (
vm &&
vm.$options.propsData &&
vm.$options.propsData[key] === undefined &&
vm._props[key] !== undefined
) {
return vm._props[key];
}
// call factory function for non-Function types
// a value is Function if its prototype is function even across different execution context
return typeof def === "function" && getType(prop.type) !== "Function"
? def.call(vm)
: def;
}
复制代码
emptyObject
export const emptyObject = Object.freeze({});
复制代码
isUndef
// These helpers produce better VM code in JS engines due to their
// explicitness and function inlining.
export function isUndef(v: any): boolean % checks {
return v === undefined || v === null
}
复制代码
isDef
export function isDef(v: any): boolean % checks {
return v !== undefined && v !== null
}
复制代码
isTrue
export function isTrue(v: any): boolean % checks {
return v === true
}
复制代码
isFalse
export function isFalse(v: any): boolean % checks {
return v === false
}
复制代码
isPrimitive
/**
* Check if value is primitive.
*/
export function isPrimitive(value: any): boolean % checks {
return (
typeof value === 'string' ||
typeof value === 'number' ||
// $flow-disable-line
typeof value === 'symbol' ||
typeof value === 'boolean'
)
}
复制代码
isObject
/**
* Quick object check - this is primarily used to tell
* Objects from primitive values when we know the value
* is a JSON-compliant type.
*/
export function isObject(obj: mixed): boolean % checks {
return obj !== null && typeof obj === 'object'
}
复制代码
toRawType
/**
* Get the raw type string of a value, e.g., [object Object].
*/
const _toString = Object.prototype.toString;
export function toRawType(value: any): string {
return _toString.call(value).slice(8, -1);
}
复制代码
isPlainObject
/**
* Strict object type check. Only returns true
* for plain JavaScript objects.
*/
export function isPlainObject(obj: any): boolean {
return _toString.call(obj) === "[object Object]";
}
复制代码
isRegExp
export function isRegExp(v: any): boolean {
return _toString.call(v) === "[object RegExp]";
}
复制代码
isValidArrayIndex
/**
* Check if val is a valid array index.
*/
export function isValidArrayIndex(val: any): boolean {
const n = parseFloat(String(val));
return n >= 0 && Math.floor(n) === n && isFinite(val);
}
复制代码
isPromise
export function isPromise(val: any): boolean {
return (
isDef(val) &&
typeof val.then === "function" &&
typeof val.catch === "function"
);
}
复制代码
toString
/**
* Convert a value to a string that is actually rendered.
*/
export function toString(val: any): string {
return val == null
? ""
: Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)
? JSON.stringify(val, null, 2)
: String(val);
}
复制代码
toNumber
/**
* Convert an input value to a number for persistence.
* If the conversion fails, return original string.
*/
export function toNumber(val: string): number | string {
const n = parseFloat(val);
return isNaN(n) ? val : n;
}
复制代码
makeMap
/**
* Make a map and return a function for checking if a key
* is in that map.
*/
export function makeMap(
str: string,
expectsLowerCase?: boolean
): (key: string) => true | void {
const map = Object.create(null);
const list: Array = str.split(",");
for (let i = 0; i < list.length; i++) {
map[list[i]] = true;
}
return expectsLowerCase ? val => map[val.toLowerCase()] : val => map[val];
}
复制代码
isBuiltInTag
/**
* Check if a tag is a built-in tag.
*/
export const isBuiltInTag = makeMap("slot,component", true);
复制代码
isReservedAttribute
/**
* Check if an attribute is a reserved attribute.
*/
export const isReservedAttribute = makeMap("key,ref,slot,slot-scope,is");
复制代码
remove
/**
* Remove an item from an array.
*/
export function remove(arr: Array, item: any ): Array<any> | void {
if (arr.length) {
const index = arr.indexOf(item);
if (index > -1) {
return arr.splice(index, 1);
}
}
}
复制代码
hasOwn
/**
* Check whether an object has the property.
*/
const hasOwnProperty = Object.prototype.hasOwnProperty;
export function hasOwn(obj: Object | Array<*>, key: string): boolean {
return hasOwnProperty.call(obj, key);
}
复制代码
cached
/**
* Create a cached version of a pure function.
*/
export function cached<F: Function>(fn: F): F {
const cache = Object.create(null);
return (function cachedFn(str: string) {
const hit = cache[str];
return hit || (cache[str] = fn(str));
}: any);
}
复制代码
camelize
/**
* Camelize a hyphen-delimited string.
*/
const camelizeRE = /-(\w)/g;
export const camelize = cached(
(str: string): string => {
return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ""));
}
);
复制代码
capitalize
/**
* Capitalize a string.
*/
export const capitalize = cached(
(str: string): string => {
return str.charAt(0).toUpperCase() + str.slice(1);
}
);
复制代码
hyphenate
/**
* Hyphenate a camelCase string.
*/
const hyphenateRE = /\B([A-Z])/g;
export const hyphenate = cached(
(str: string): string => {
return str.replace(hyphenateRE, "-$1").toLowerCase();
}
);
复制代码
bind
/**
* Simple bind polyfill for environments that do not support it,
* e.g., PhantomJS 1.x. Technically, we don't need this anymore
* since native bind is now performant enough in most browsers.
* But removing it would mean breaking code that was able to run in
* PhantomJS 1.x, so this must be kept for backward compatibility.
*/
/* istanbul ignore next */
function polyfillBind(fn: Function, ctx: Object): Function {
function boundFn(a) {
const l = arguments.length;
return l
? l > 1
? fn.apply(ctx, arguments)
: fn.call(ctx, a)
: fn.call(ctx);
}
boundFn._length = fn.length;
return boundFn;
}
function nativeBind(fn: Function, ctx: Object): Function {
return fn.bind(ctx);
}
export const bind = Function.prototype.bind ? nativeBind : polyfillBind;
复制代码
toArray
/**
* Convert an Array-like object to a real Array.
*/
export function toArray(list: any, start?: number): Array<any> {
start = start || 0;
let i = list.length - start;
const ret: Array = new Array(i);
while (i--) {
ret[i] = list[i + start];
}
return ret;
}
复制代码
extend
/**
* Mix properties into target object.
*/
export function extend(to: Object, _from: ?Object): Object {
for (const key in _from) {
to[key] = _from[key];
}
return to;
}
复制代码
toObject
/**
* Merge an Array of Objects into a single Object.
*/
export function toObject(arr: Array ): Object {
const res = {};
for (let i = 0; i < arr.length; i++) {
if (arr[i]) {
extend(res, arr[i]);
}
}
return res;
}
复制代码
noop
/**
* Perform no operation.
* Stubbing args to make Flow happy without leaving useless transpiled code
* with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/).
*/
export function noop(a?: any, b?: any, c?: any) {}
复制代码
no
/**
* Always return false.
*/
export const no = (a?: any, b?: any, c?: any) => false;
复制代码
identity
/**
* Return the same value.
*/
export const identity = (_: any) => _;
复制代码
genStaticKeys
/**
* Generate a string containing static keys from compiler modules.
*/
export function genStaticKeys(modules: Array ): string {
return modules
.reduce((keys, m) => {
return keys.concat(m.staticKeys || []);
}, [])
.join(",");
}
复制代码
looseEqual
/**
* Check if two values are loosely equal - that is,
* if they are plain objects, do they have the same shape?
*/
export function looseEqual(a: any, b: any): boolean {
if (a === b) return true;
const isObjectA = isObject(a);
const isObjectB = isObject(b);
if (isObjectA && isObjectB) {
try {
const isArrayA = Array.isArray(a);
const isArrayB = Array.isArray(b);
if (isArrayA && isArrayB) {
return (
a.length === b.length &&
a.every((e, i) => {
return looseEqual(e, b[i]);
})
);
} else if (a instanceof Date && b instanceof Date) {
return a.getTime() === b.getTime();
} else if (!isArrayA && !isArrayB) {
const keysA = Object.keys(a);
const keysB = Object.keys(b);
return (
keysA.length === keysB.length &&
keysA.every(key => {
return looseEqual(a[key], b[key]);
})
);
} else {
/* istanbul ignore next */
return false;
}
} catch (e) {
/* istanbul ignore next */
return false;
}
} else if (!isObjectA && !isObjectB) {
return String(a) === String(b);
} else {
return false;
}
}
复制代码
looseIndexOf
/**
* Return the first index at which a loosely equal value can be
* found in the array (if value is a plain object, the array must
* contain an object of the same shape), or -1 if it is not present.
*/
export function looseIndexOf(arr: Array, val: mixed ): number {
for (let i = 0; i < arr.length; i++) {
if (looseEqual(arr[i], val)) return i;
}
return -1;
}
复制代码
once
/**
* Ensure a function is called only once.
*/
export function once(fn: Function): Function {
let called = false;
return function() {
if (!called) {
called = true;
fn.apply(this, arguments);
}
};
}
复制代码
defineReactive
/**
* Define a reactive property on an Object.
*/
export function defineReactive(
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
// 没有定义get 或者 定义了get并且定义了set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
// 非shadowDOM,val也需要响应式
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() { // 收集依赖
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
/**
*
*
* @param {*} newVal
*/
set: function reactiveSetter(newVal) { // 触发依赖
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
// 新值等于就值就不触发更新
// (newVal !== newVal && value !== value) 考虑 NaN的情况
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if (getter && !setter) return // 之前定义,只有get,没有set,这次也不定义set
if (setter) { // 如果之前定义了,调用之前定义的setter
setter.call(obj, newVal)
} else {
val = newVal
}
// shawdom 要求与外界隔离,故不需要响应式
childOb = !shallow && observe(newVal)
dep.notify() // 触发依赖,视图重新渲染
}
})
}
复制代码