vue2工具函数

227 阅读3分钟

前言

学习目标

源码地址: vue/src/shared

打包后的工具函数:github.com/vuejs/vue/b…

工具函数

在线查看源码地址vue/util

由于源码使用了了Flow类型,下文仅贴出打包后的代码

1. emptyObject 冻结对象

var emptyObject = Object.freeze({});
Object.isFrozen(emptyObject) // true

1.1 Object.freeze

Object.freeze()  方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象。

1.2 Object.isFrozen

Object.isFrozen()方法判断一个对象是否被冻结

2. isUndef 是否是未定义

function isUndef(v) {
  return v === undefined || v === null;
}

3. isDef 是否是已经定义

function isDef(v) {
  return v !== undefined && v !== null;
}

4. isTrue 是否是true

function isTrue(v) {
  return v === true;
}

5. isFalse 是否是false

function isFalse(v) {
  return v === false;
}

6. isPrimitive 判断字符串值是否是原始值

/**
 * Check if value is primitive.
 */
function isPrimitive(value) {
  return (
    typeof value === "string" ||
    typeof value === "number" ||
    // $flow-disable-line
    typeof value === "symbol" ||
    typeof value === "boolean"
  );
}

7. isObject 是否对象

/**
 * Quick object check - this is primarily used to tell
 * Objects from primitive values when we know the value
 * is a JSON-compliant type.
 */
function isObject(obj) {
  return obj !== null && typeof obj === "object";
}

8. toRawType 转换成原始类型

/**
 * Get the raw type string of a value, e.g., [object Object].
 */
var _toString = Object.prototype.toString;

function toRawType(value) {
  return _toString.call(value).slice(8, -1);
}

利用Object.prototype.toString 检测类型对象并截取类型

9. isPlainObject 是否是纯纯的对象

/**
 * Strict object type check. Only returns true
 * for plain JavaScript objects.
 */
function isPlainObject(obj) {
  return _toString.call(obj) === "[object Object]";
}

10. isRegExp 是否是正则表达式

function isRegExp(v) {
  return _toString.call(v) === "[object RegExp]";
}

11. isValidArrayIndex 是否是可用的数组索引值

/**
 * Check if val is a valid array index.
 */
function isValidArrayIndex(val) {
  var n = parseFloat(String(val));
  return n >= 0 && Math.floor(n) === n && isFinite(val);
}

isFinite 函数用来判断被传入的参数值是否为一个有限数值(finite number)。在必要情况下,参数会首先转为一个数值。

12. isPromise 是否是Promise

 function isPromise(val) {
  return (
    isDef(val) &&
    typeof val.then === "function" &&
    typeof val.catch === "function"
  );
}

13. toString 转字符串

/**
 * Convert a value to a string that is actually rendered.
 */
function toString(val) {
  return val == null
    ? ""
    : Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)
    ? JSON.stringify(val, null, 2)
    : String(val);
}

14. toNumber 转数字

/**
 * Convert an input value to a number for persistence.
 * If the conversion fails, return original string.
 */
function toNumber(val) {
  var n = parseFloat(val);
  return isNaN(n) ? val : n;
}

/**
 * Convert an input value to a number for persistence.
 * If the conversion fails, return original string.
 */
function toNumber(val) {
  var n = parseFloat(val);
  return isNaN(n) ? val : n;
}

15. makeMap 创建map(对象)

/**
 * Make a map and return a function for checking if a key
 * is in that map.
 */
function makeMap(str, expectsLowerCase) {
  var map = Object.create(null);
  var list = str.split(",");
  for (var i = 0; i < list.length; i++) {
    map[list[i]] = true;
  }
  return expectsLowerCase
    ? function (val) {
        return map[val.toLowerCase()];
      }
    : function (val) {
        return map[val];
      };
}

传入一个以逗号分隔的字符串,生成一个 map(键值对),并且返回一个函数检测 key 值在不在这个 map 中。第二个参数是小写选项。

Object.create(null);可以创建一个原型链负担最小的对象

16. isBuiltInTag 是否是内置的 tag

/**
 * Check if a tag is a built-in tag.
 */
var isBuiltInTag = makeMap("slot,component", true);

17. isReservedAttribute 是否是保留的属性

/**
 * Check if an attribute is a reserved attribute.
 */
var isReservedAttribute = makeMap("key,ref,slot,slot-scope,is");

vue内置标签属性

18. remove 移除数组中的中一项

/**
 * Remove an item from an array.
 */
function remove(arr, item) {
  if (arr.length) {
    var index = arr.indexOf(item);
    if (index > -1) {
      return arr.splice(index, 1);
    }
  }
}

查找并删除某项

19. hasOwn 检测是否是有某属性

/**
 * Check whether an object has the property.
 */
var hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn(obj, key) {
  return hasOwnProperty.call(obj, key);
}

20. cached 缓存

/**
 * Create a cached version of a pure function.
 */
function cached(fn) {
  var cache = Object.create(null);
  return function cachedFn(str) {
    var hit = cache[str];
    return hit || (cache[str] = fn(str));
  };
}

闭包的利用,可当作优化运行时的,可以写个阶乘试试

21. camelize 连字符转小驼峰

/**
 * Camelize a hyphen-delimited string.
 */
var camelizeRE = /-(\w)/g;
var camelize = cached(function (str) {
  return str.replace(camelizeRE, function (_, c) {
    return c ? c.toUpperCase() : "";
  });
});

22. capitalize 首字母转大写

/**
 * Capitalize a string.
 */
var capitalize = cached(function (str) {
  return str.charAt(0).toUpperCase() + str.slice(1);
});

23. hyphenate 小驼峰转连字符

/**
 * Hyphenate a camelCase string.
 */
var hyphenateRE = /\B([A-Z])/g;
var hyphenate = cached(function (str) {
  return str.replace(hyphenateRE, "-$1").toLowerCase();
});

24. polyfillBind 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, ctx) {
  function boundFn(a) {
    var 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, ctx) {
  return fn.bind(ctx);
}

var bind = Function.prototype.bind ? nativeBind : polyfillBind;

兼容了老版本浏览器不支持原生的 bind 函数

24. toArray 把类数组转成真正的数组

/**
 * Convert an Array-like object to a real Array.
 */
function toArray(list, start) {
  start = start || 0;
  var i = list.length - start;
  var ret = new Array(i);
  while (i--) {
    ret[i] = list[i + start];
  }
  return ret;
}

把数组或集合(如document.getElementsByClassName获取到的节点集合)转换成新数组,支持从哪个位置开始,默认从 0 开始。

25. extend 合并

/**
 * Mix properties into target object.
 */
function extend(to, _from) {
  for (var key in _from) {
    to[key] = _from[key];
  }
  return to;
}

26. toObject 转对象

/**
 * Merge an Array of Objects into a single Object.
 */
function toObject(arr) {
  var res = {};
  for (var i = 0; i < arr.length; i++) {
    if (arr[i]) {
      extend(res, arr[i]);
    }
  }
  return res;
}

27. 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/).
 */
function noop(a, b, c) {}

28. no 一直返回 false

/**
 * Always return false.
 */
var no = function (a, b, c) {
  return false;
};

29. identity 返回参数本身

/**
 * Return the same value.
 */
var identity = function (_) {
  return _;
};

30. genStaticKeys 生成静态属性

/**
 * Generate a string containing static keys from compiler modules.
 */
function genStaticKeys(modules) {
  return modules
    .reduce(function (keys, m) {
      return keys.concat(m.staticKeys || []);
    }, [])
    .join(",");
}

31. looseEqual 宽松相等比较

/**
 * Check if two values are loosely equal - that is,
 * if they are plain objects, do they have the same shape?
 */
function looseEqual(a, b) {
  if (a === b) {
    return true;
  }
  var isObjectA = isObject(a);
  var isObjectB = isObject(b);
  if (isObjectA && isObjectB) {
    try {
      var isArrayA = Array.isArray(a);
      var isArrayB = Array.isArray(b);
      if (isArrayA && isArrayB) {
        return (
          a.length === b.length &&
          a.every(function (e, i) {
            return looseEqual(e, b[i]);
          })
        );
      } else if (a instanceof Date && b instanceof Date) {
        return a.getTime() === b.getTime();
      } else if (!isArrayA && !isArrayB) {
        var keysA = Object.keys(a);
        var keysB = Object.keys(b);
        return (
          keysA.length === keysB.length &&
          keysA.every(function (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;
  }
}

对引用类型进行递归比对,判断是否相对

32. looseIndexOf 宽松的 indexOf

/**
 * 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.
 */
function looseIndexOf(arr, val) {
  for (var i = 0; i < arr.length; i++) {
    if (looseEqual(arr[i], val)) {
      return i;
    }
  }
  return -1;
}

33. once 确保函数只执行一次

/**
 * Ensure a function is called only once.
 */
function once(fn) {
  var called = false;
  return function () {
    if (!called) {
      called = true;
      fn.apply(this, arguments);
    }
  };
}

闭包了个变量作为锁,以此控制执行

34. 生命周期和公共变量

var SSR_ATTR = "data-server-rendered";

var ASSET_TYPES = ["component", "directive", "filter"];

var LIFECYCLE_HOOKS = [
  "beforeCreate",
  "created",
  "beforeMount",
  "mounted",
  "beforeUpdate",
  "updated",
  "beforeDestroy",
  "destroyed",
  "activated",
  "deactivated",
  "errorCaptured",
  "serverPrefetch",
];

总结

万丈高楼平地起,这些工具函数就像螺钉螺帽一样,在代码中调用后可以使得行文更紧凑、代码更美观、结构更牢固。