Vue2源码中实用的工具函数
前言
在Vue2中,也有许多的工具函数,这里记录一些常用的工具函数。本文我们学习Vue2中shared/util.js中的工具函数。本文学习的是打包后的 dist/vue.js 14行到379行
工具函数
emptyObject
1/*!
2 * Vue.js v2.6.14
3 * (c) 2014-2021 Evan You
4 * Released under the MIT License.
5 */
6 var emptyObject = Object.freeze({});
freeze冻结一个对象,使其不可修改。这个函数在Vue2中经常用到,比如在Vue2中,data函数返回的对象就是被冻结的,防止用户修改data函数返回的对象。
isUndef是否未定义
1 function isUndef (v) {
2 return v === undefined || v === null
3 }
判断一个变量是否未定义或者为null,在JavaScript中,假值有6个
- undefined
- null
- false
- 0
- NaN
- ''
为了判断准确,Vue2 源码中封装了isDef
、 isTrue
、isFalse
函数来准确判断。
isDef是否已经定义
1 function isDef (v) {
2 return v !== undefined && v !== null
3 }
isTrue是否为真值
1function isTrue (v) {
2 return v === true
3 }
isFalse是否为假
1function isFalse (v) {
2 return v === false
3 }
isPrimitive是否基本类型
1 function isPrimitive (value) {
2 return (
3 typeof value === 'string' ||
4 typeof value === 'number' ||
5 // $flow-disable-line
6 typeof value === 'symbol' ||
7 typeof value === 'boolean'
8 )
9 }
isObject是否对象
因为 typeof null
是 'object'。数组等用这个函数判断也是 true
1 /**
2 * Quick object check - this is primarily used to tell
3 * Objects from primitive values when we know the value
4 * is a JSON-compliant type.
5 */
6 function isObject (obj) {
7 return obj !== null && typeof obj === 'object'
8 }
toRawType 转换成原始类型
Object.prototype.toString;
返回一个表示该对象的字符串
1 function toRawType (value) {
2 return _toString.call(value).slice(8, -1)
3 }
isPlainObject是否是纯对象
1 function isPlainObject (obj) {
2 return _toString.call(obj) === '[object Object]'
3 }
isRegExp是否是正则表达式
1function isRegExp (v) {
2 return _toString.call(v) === '[object RegExp]'
isValidArrayIndex 是否是可用的数组索引值
数组可用的索引值是 0 ('0')、1 ('1') 、2 ('2') …
1function isValidArrayIndex (val) {
2 var n = parseFloat(String(val));
3 return n >= 0 && Math.floor(n) === n && isFinite(val)
4 }
该全局 isFinite()
函数用来判断被传入的参数值是否为一个有限数值(finite number)。在必要情况下,参数会首先转为一个数值。
1isFinite(Infinity); // false
2isFinite(NaN); // false
3isFinite(-Infinity); // false
4isFinite(0); // true
5isFinite(2e64); // true,在更强壮的 Number.isFinite(null) 中将会得到 false
6isFinite("0"); // true,在更强壮的 Number.isFinite('0') 中将会得到 false
isPromise判断是否是promise对象
1 function isPromise (val) {
2 return (
3 isDef(val) &&
4 typeof val.then === 'function' &&
5 typeof val.catch === 'function'
6 )
toString 转换成字符串
转换成字符串。是数组或者对象并且对象的 toString
方法是 Object.prototype.toString
,用 JSON.stringify 转换。
1 /**
2 * Convert a value to a string that is actually rendered.
3 */
4function toString (val) {
5 return val == null
6 ? ''
7 : Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)
8 ? JSON.stringify(val, null, 2)
9 : String(val)
10}
toNumber转数字
转换成数字,如果转换失败,返回原值
1 function toNumber (val) {
2 var n = parseFloat(val);
3 return isNaN(n) ? val : n
4 }
makeMap生成一个map(对象)
传入一个以逗号分隔的字符串,生成一个map
键值对,并且返回一个函数检测key
值在不在这个map
中。第二个参数是小写选项。
1 function makeMap (
2 str,
3 expectsLowerCase
4 ) {
5 var map = Object.create(null);
6 var list = str.split(',');
7 for (var i = 0; i < list.length; i++) {
8 map[list[i]] = true;
9 }
10 return expectsLowerCase
11 ? function (val) { return map[val.toLowerCase()]; }
12 : function (val) { return map[val]; }
13 }
isBuiltInTag判断是否是内置标签
1/**
2 * Check if a tag is a built-in tag.
3 */
4var isBuiltInTag = makeMap('slot,component', true);
5
6// 返回的函数,第二个参数不区分大小写
7isBuiltInTag('slot') // true
8isBuiltInTag('component') // true
9isBuiltInTag('Slot') // true
10isBuiltInTag('Component') // true
isReservedAttribute判断是否是保留属性
1/**
2 * Check if an attribute is a reserved attribute.
3 */
4var isReservedAttribute = makeMap('key,ref,slot,slot-scope,is');
5
6isReservedAttribute('key') // true
7isReservedAttribute('ref') // true
8isReservedAttribute('slot') // true
9isReservedAttribute('slot-scope') // true
10isReservedAttribute('is') // true
11isReservedAttribute('IS') // undefined
remove 移除数组中的元素
1 /**
2 * Remove an item from an array.
3 */
4 function remove (arr, item) {
5 if (arr.length) {
6 var index = arr.indexOf(item);
7 if (index > -1) {
8 return arr.splice(index, 1)
9 }
10 }
11 }
注意:通过splice
方法移除数组,是个耗性能的操作,因为数组的索引会重新计算。
在axios InterceptorManager源码中,拦截器用数组存储的。但实际移除拦截器时,只是把拦截器置为 null 。而不是用`splice移除。最后执行时为 null 的不执行,同样效果,但性能更好。
1'use strict';
2
3var utils = require('./../utils');
4
5function InterceptorManager() {
6 this.handlers = [];
7}
8
9/**
10 * Add a new interceptor to the stack
11 *
12 * @param {Function} fulfilled The function to handle `then` for a `Promise`
13 * @param {Function} rejected The function to handle `reject` for a `Promise`
14 *
15 * @return {Number} An ID used to remove interceptor later
16 */
17InterceptorManager.prototype.use = function use(fulfilled, rejected, options) {
18 this.handlers.push({
19 fulfilled: fulfilled,
20 rejected: rejected,
21 synchronous: options ? options.synchronous : false,
22 runWhen: options ? options.runWhen : null
23 });
24 return this.handlers.length - 1;
25};
26
27/**
28 * Remove an interceptor from the stack
29 *
30 * @param {Number} id The ID that was returned by `use`
31 */
32InterceptorManager.prototype.eject = function eject(id) {
33 if (this.handlers[id]) {
34 this.handlers[id] = null;
35 }
36};
37
38/**
39 * Clear all interceptors from the stack
40 */
41InterceptorManager.prototype.clear = function clear() {
42 if (this.handlers) {
43 this.handlers = [];
44 }
45};
46
47/**
48 * Iterate over all the registered interceptors
49 *
50 * This method is particularly useful for skipping over any
51 * interceptors that may have become `null` calling `eject`.
52 *
53 * @param {Function} fn The function to call for each interceptor
54 */
55InterceptorManager.prototype.forEach = function forEach(fn) {
56 utils.forEach(this.handlers, function forEachHandler(h) {
57 if (h !== null) {
58 fn(h);
59 }
60 });
61};
62
63module.exports = InterceptorManager;
hasOwn 检测是否是自己的属性
1/**
2 * Check whether an object has the property.
3 */
4var hasOwnProperty = Object.prototype.hasOwnProperty;
5function hasOwn (obj, key) {
6 return hasOwnProperty.call(obj, key)
7}
8
9// 例子:
10
11// 特别提醒:__proto__ 是浏览器实现的原型写法,后面还会用到
12// 现在已经有提供好几个原型相关的API
13// Object.getPrototypeOf
14// Object.setPrototypeOf
15// Object.isPrototypeOf
16
17// .call 则是函数里 this 显示指定以为第一个参数,并执行函数。
18
19hasOwn({__proto__: { a: 1 }}, 'a') // false
20hasOwn({ a: undefined }, 'a') // true
21hasOwn({}, 'a') // false
22hasOwn({}, 'hasOwnProperty') // false
23hasOwn({}, 'toString') // false
24// 是自己的本身拥有的属性,不是通过原型链向上查找的。
cached缓存
利用闭包特性,缓存数据
1/**
2 * Create a cached version of a pure function.
3 */
4 function cached (fn) {
5 var cache = Object.create(null);
6 return (function cachedFn (str) {
7 var hit = cache[str];
8 return hit || (cache[str] = fn(str))
9 })
10 }
camelizeRE连字符转小驼峰
连字符 - 转驼峰 on-click => onClick
1 /**
2 * Camelize a hyphen-delimited string.
3 */
4 var camelizeRE = /-(\w)/g;
5 var camelize = cached(function (str) {
6 return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; })
7 });
capitalize首字母转大写
1 /**
2 * Capitalize a string.
3 */
4 var capitalize = cached(function (str) {
5 return str.charAt(0).toUpperCase() + str.slice(1)
6 });
hyphenateRE小驼峰转连字符
1/**
2 * Hyphenate a camelCase string.
3 */
4 var hyphenateRE = /\B([A-Z])/g;
5 var hyphenate = cached(function (str) {
6 return str.replace(hyphenateRE, '-$1').toLowerCase()
7 });
polyfillBind bind 的垫片
1/**
2 * Simple bind polyfill for environments that do not support it,
3 * e.g., PhantomJS 1.x. Technically, we don't need this anymore
4 * since native bind is now performant enough in most browsers.
5 * But removing it would mean breaking code that was able to run in
6 * PhantomJS 1.x, so this must be kept for backward compatibility.
7 */
8
9/* istanbul ignore next */
10function polyfillBind (fn, ctx) {
11 function boundFn (a) {
12 var l = arguments.length;
13 return l
14 ? l > 1
15 ? fn.apply(ctx, arguments)
16 : fn.call(ctx, a)
17 : fn.call(ctx)
18 }
19
20 boundFn._length = fn.length;
21 return boundFn
22}
23
24function nativeBind (fn, ctx) {
25 return fn.bind(ctx)
26}
27
28var bind = Function.prototype.bind
29 ? nativeBind
30 : polyfillBind;
兼容了老版本浏览器不支持原生的 bind
函数。同时兼容写法,对参数的多少做出了判断,使用call
和apply
实现,据说参数多适合用 apply
,少用 call
性能更好。
toArray把类数组转换成真正的数组
把类数组转换成数组,支持从哪个位置开始,默认从 0 开始。
1/**
2 * Convert an Array-like object to a real Array.
3 */
4function toArray (list, start) {
5 start = start || 0;
6 var i = list.length - start;
7 var ret = new Array(i);
8 while (i--) {
9 ret[i] = list[i + start];
10 }
11 return ret
12}
13
14// 例子:
15function fn(){
16 var arr1 = toArray(arguments);
17 console.log(arr1); // [1, 2, 3, 4, 5]
18 var arr2 = toArray(arguments, 2);
19 console.log(arr2); // [3, 4, 5]
20}
21fn(1,2,3,4,5);
extend合并
1/**
2 * Mix properties into target object.
3 */
4function extend (to, _from) {
5 for (var key in _from) {
6 to[key] = _from[key];
7 }
8 return to
9}
10
11// 例子:
12const data = { name: 'YY' };
13const data2 = extend(data, { mp: 'Demo', name: 'Vue' });
14console.log(data); // { name: "Vue", mp: "Demo" }
15console.log(data2); // { name: "Vue", mp: "Demo" }
16console.log(data === data2); // true
toObject 转对象
1/**
2 * Merge an Array of Objects into a single Object.
3 */
4function toObject (arr) {
5 var res = {};
6 for (var i = 0; i < arr.length; i++) {
7 if (arr[i]) {
8 extend(res, arr[i]);
9 }
10 }
11 return res
12}
13
14// 数组转对象
15toObject(['测试', '测试数据'])
16// {0: '测', 1: '试', 2: '数', 3: '据'}
noop空函数
1 /* eslint-disable no-unused-vars */
2
3 /**
4 * Perform no operation.
5 * Stubbing args to make Flow happy without leaving useless transpiled code
6 * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/).
7 */
8 function noop (a, b, c) {}
no 一直返回false
1/**
2 * Always return false.
3 */
4var no = function (a, b, c) { return false; };
5/* eslint-enable no-unused-vars */
identity返回参数本身
1/**
2 * Return the same value.
3 */
4var identity = function (_) { return _; };
genStaticKeys 生成静态属性
1/**
2 * Generate a string containing static keys from compiler modules.
3 */
4function genStaticKeys (modules) {
5 return modules.reduce(function (keys, m) {
6 return keys.concat(m.staticKeys || [])
7 }, []).join(',')
8}
looseEqual 宽松相等
由于数组、对象等是引用类型,所以两个内容看起来相等,严格相等都是不相等。
1var a = {};
2var b = {};
3a === b; // false
4a == b; // false
所以该函数是对数组、日期、对象进行递归比对。如果内容完全相等则宽松相等。
1/**
2 * Check if two values are loosely equal - that is,
3 * if they are plain objects, do they have the same shape?
4 */
5function looseEqual (a, b) {
6 if (a === b) { return true }
7 var isObjectA = isObject(a);
8 var isObjectB = isObject(b);
9 if (isObjectA && isObjectB) {
10 try {
11 var isArrayA = Array.isArray(a);
12 var isArrayB = Array.isArray(b);
13 if (isArrayA && isArrayB) {
14 return a.length === b.length && a.every(function (e, i) {
15 return looseEqual(e, b[i])
16 })
17 } else if (a instanceof Date && b instanceof Date) {
18 return a.getTime() === b.getTime()
19 } else if (!isArrayA && !isArrayB) {
20 var keysA = Object.keys(a);
21 var keysB = Object.keys(b);
22 return keysA.length === keysB.length && keysA.every(function (key) {
23 return looseEqual(a[key], b[key])
24 })
25 } else {
26 /* istanbul ignore next */
27 return false
28 }
29 } catch (e) {
30 /* istanbul ignore next */
31 return false
32 }
33 } else if (!isObjectA && !isObjectB) {
34 return String(a) === String(b)
35 } else {
36 return false
37 }
38}
looseIndexOf 宽松的 indexOf
该函数实现的是宽松相等。原生的 indexOf
是严格相等。
1/**
2 * Return the first index at which a loosely equal value can be
3 * found in the array (if value is a plain object, the array must
4 * contain an object of the same shape), or -1 if it is not present.
5 */
6function looseIndexOf (arr, val) {
7 for (var i = 0; i < arr.length; i++) {
8 if (looseEqual(arr[i], val)) { return i }
9 }
10 return -1
11}
once 确保函数只执行一次
利用闭包特性,存储状态
1/**
2 * Ensure a function is called only once.
3 */
4function once (fn) {
5 var called = false;
6 return function () {
7 if (!called) {
8 called = true;
9 fn.apply(this, arguments);
10 }
11 }
12}
13
14
15const fn1 = once(function(){
16 console.log('哎嘿,无论你怎么调用,我只执行一次');
17});
18
19fn1(); // '哎嘿,无论你怎么调用,我只执行一次'
20fn1(); // 不输出
21fn1(); // 不输出
22fn1(); // 不输出
生命周期等
1var SSR_ATTR = 'data-server-rendered';
2
3var ASSET_TYPES = [
4 'component',
5 'directive',
6 'filter'
7];
8
9[Vue 生命周期](https://cn.vuejs.org/v2/api/#%E9%80%89%E9%A1%B9-%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E9%92%A9%E5%AD%90)
10
11var LIFECYCLE_HOOKS = [
12 'beforeCreate',
13 'created',
14 'beforeMount',
15 'mounted',
16 'beforeUpdate',
17 'updated',
18 'beforeDestroy',
19 'destroyed',
20 'activated',
21 'deactivated',
22 'errorCaptured',
23 'serverPrefetch'
24];