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 源码中封装了isDefisTrueisFalse函数来准确判断。

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(valnull2)
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__: { a1 }}, 'a'// false
20hasOwn({ aundefined }, '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 (_, creturn 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 函数。同时兼容写法,对参数的多少做出了判断,使用callapply实现,据说参数多适合用 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(arguments2);
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, creturn 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(thisarguments);
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];
posted @ 2025-05-24 18:25  YangYang-it  阅读(24)  评论(0)    收藏  举报