Vue2源码中的工具函数
vue2 源码分析 工具函数
打开 vue 仓库,
我们可以在 项目目录结构 描述中,找到 shared 模块。 shared: contains utilities shared across the entire codebase.
本文就是讲 shared 模块,对应的文件路径是:vue/vue/src/shared。
源代码的代码,使用了 Flow 类型,为了方便理解,可以直接看源码仓库中的打包后的 dist/vue.js 14 行到 379 行。
1.1 emptyObject
var emptyObject = Object.freeze({});
冻结对象。第一层无法修改。对象中也有判断是否冻结的方法。
Object.isFrozen(emptyObject); // true
1.2 isUndef 是否是未定义
function isUndef(v) {
return v === undefined || v === null;
}
这里说一个另外的注意点,在 ES3 中undefined是可以被赋值的,而在 ES5 之后全局的undefined就不能赋值了,但局部的还是可以被赋值修改。
(function () {
var undefined = 10;
// 这里会输出10
console.log(undefined); // 10
})();
所以判断undefined更严谨的是使用 void 0
因为在 void 后面随便跟上一个表达式,返回的都是 undefined,而其中最短的就是 void 0 ,且 void 是不能被重写的。
function isUndefined(obj) {
return obj === void 0;
}
在 ES5 之后,void 0 也是有用武之地的,用 void 0 代替 undefined 能节省不少字节的大小,事实上,不少 JavaScript 压缩工具在压缩过程中,正是将 undefined 用 void 0 代替掉了。
JavaScript 中假值有六个
false;
null;
undefined;
0;
""(空字符串);
NaN;
为了判断准确,Vue2 源码中封装了 isDef、 isTrue、isFalse 函数来准确判断
1.3 isDef 是否是已经定义
function isDef(v) {
return v !== undefined && v !== null;
}
1.4 isTrue 是否是 true
function isTrue(v) {
return v === true;
}
1.5 isFalse 是否是 false
function isFalse(v) {
return v === false;
}
1.6 isPrimitive 判断值是否是基本类型
判断是否是字符串、或者数字、或者 symbol、或者布尔值。
function isPrimitive(value) {
return (
typeof value === "string" ||
typeof value === "number" ||
typeof value === "symbol" ||
typeof value === "boolean"
);
}
1.7 isObject 判断是对象(引用类型)
function isObject(obj) {
return obj !== null && typeof obj === "object";
}
// 例子:
isObject([]); // true
// 有时不需要严格区分数组和对象
1.8 toRawType 获得变量类型
Object.prototype.toString() 方法返回一个表示该对象的字符串
var _toString = Object.prototype.toString;
function toRawType(value) {
return _toString.call(value).slice(8, -1);
}
// 例子:
toRawType(""); // 'String'
toRawType(); // 'Undefined'
toRawType([]); // 'Array'
1.9 isPlainObject 是否是纯对象
function isPlainObject(obj) {
return _toString.call(obj) === "[object Object]";
}
// 上文 isObject([]) 也是 true
// 这个就是判断对象是纯对象的方法。
// 例子:
isPlainObject([]); // false
isPlainObject({}); // true
1.10 isRegExp 是否是正则表达式
function isRegExp(v) {
return _toString.call(v) === "[object RegExp]";
}
// 例子:
// 判断是不是正则表达式
isRegExp(/niko/); // true
1.11 isValidArrayIndex 是否是可用的数组索引值
数组可用的索引值是 0 (‘0’)、1 (‘1’) 、2 (‘2’) …
function isValidArrayIndex(val) {
var n = parseFloat(String(val));
return n >= 0 && Math.floor(n) === n && isFinite(val);
}
Math.floor(n) === n可以用来判断是否是整数。
该全局 isFinite() 函数用来判断被传入的参数值是否为一个有限数值(finite number)。在必要情况下,参数会首先转为一个数值。
isFinite(Infinity); // false
isFinite(NaN); // false
isFinite(-Infinity); // false
isFinite(0); // true
isFinite(2e64); // true, 在更强壮的Number.isFinite(null)中将会得到false
isFinite("0"); // true, 在更强壮的Number.isFinite('0')中将会得到false
1.12 isPromise 判断是否是 promise
function isPromise(val) {
return (
isDef(val) &&
typeof val.then === "function" &&
typeof val.catch === "function"
);
}
// 例子:
isPromise(new Promise()); // true
这里用 isDef 判断其实相对 isObject 来判断 来说有点不严谨,但是够用。
1.13 toString 转字符串
转换成字符串。是数组或者对象并且对象的 toString 方法是 Object.prototype.toString,用 JSON.stringify 转换。
function toString(val) {
return val == null
? ""
: Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)
? JSON.stringify(val, null, 2)
: String(val);
}
*这里的_toString 在 1.8 中定义,isPlainObject 在 1.9 中定义
1.14 toNumber 转数字
转换成数字。如果转换失败依旧返回原始字符串。
function toNumber(val) {
var n = parseFloat(val);
return isNaN(n) ? val : n;
}
toNumber("a"); // 'a'
toNumber("1"); // 1
toNumber("1a"); // 1
toNumber("a1"); // 'a1'
1.15 makeMap 生成一个 map (对象)
传入一个以逗号分隔的字符串,生成一个 map(键值对),并且返回一个函数检测 key 值在不在这个 map 中。第二个参数是小写选项,为 true 时大小写不敏感。
function makeMap(str, expectsLowerCase) {
// Object.create(null) 没有原型链的空对象
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];
};
}
// 该方法应用在1.16 1.17
1.16 isBuiltInTag 是否是内置的 tag
var isBuiltInTag = makeMap("slot,component", true);
// 返回的函数,第二个参数不区分大小写
isBuiltInTag("slot"); // true
isBuiltInTag("component"); // true
isBuiltInTag("Slot"); // true
isBuiltInTag("Component"); // true
1.17 isReservedAttribute 是否是保留的属性
var isReservedAttribute = makeMap("key,ref,slot,slot-scope,is");
isReservedAttribute("key"); // true
isReservedAttribute("ref"); // true
isReservedAttribute("slot"); // true
isReservedAttribute("slot-scope"); // true
isReservedAttribute("is"); // true
isReservedAttribute("IS"); // undefined
1.18 remove 移除数组中的中一项
function remove(arr, item) {
if (arr.length) {
var index = arr.indexOf(item);
if (index > -1) {
return arr.splice(index, 1);
}
}
}
splice 其实是一个很耗性能的方法。删除数组中的一项,其他元素都要移动位置。
axios InterceptorManager 拦截器源码 中,拦截器用数组存储的。但实际移除拦截器时,只是把拦截器置为 null 。而不是用 splice 移除。最后执行时为 null 的不执行,效果是相同的。axios 拦截器这个场景下,不得不说为性能做到了很好的考虑。因为拦截器是用户自定义的,理论上可以有无数个,所以做性能考虑是必要的。
axios 拦截器代码示例:
// 代码有删减
// 声明
this.handlers = [];
// 移除
if (this.handlers[id]) {
this.handlers[id] = null;
}
// 执行
if (h !== null) {
fn(h);
}
1.19 hasOwn 检测是否是自己的属性
var hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn(obj, key) {
return hasOwnProperty.call(obj, key);
}
// 例子:
// 特别提醒:__proto__ 是浏览器实现的原型写法,后面还会用到
// 现在已经有提供好几个原型相关的API
// Object.getPrototypeOf
// Object.setPrototypeOf
// Object.isPrototypeOf
// .call 则是函数里 this 显示指定以为第一个参数,并执行函数。
hasOwn({ __proto__: { a: 1 } }, "a"); // false
hasOwn({ a: undefined }, "a"); // true
hasOwn({}, "a"); // false
hasOwn({}, "hasOwnProperty"); // false
hasOwn({}, "toString"); // false
// 是自己的本身拥有的属性,不是通过原型链向上查找的。
1.20 cached 缓存
利用闭包特性,缓存数据
function cached(fn) {
var cache = Object.create(null);
return function cachedFn(str) {
var hit = cache[str];
return hit || (cache[str] = fn(str));
};
}
1.21 camelize 连字符转小驼峰
on-click => onClick
var camelizeRE = /-(\w)/g;
var camelize = cached(function (str) {
return str.replace(camelizeRE, function (_, c) {
return c ? c.toUpperCase() : "";
});
});
camelize("on-click"); // onClick
这里的 replace 方法参数问题, 可以看JS 中 replace 方法这篇博客的例 7
1.22 capitalize 首字母转大写
var capitalize = cached(function (str) {
return str.charAt(0).toUpperCase() + str.slice(1);
});
capitalize("niko"); // Niko
1.23 hyphenate 小驼峰转连字符
var hyphenateRE = /\B([A-Z])/g;
var hyphenate = cached(function (str) {
return str.replace(hyphenateRE, "-$1").toLowerCase();
});
hyphenate("onNikoLogin"); // on-niko-login
1.24 polyfillBind bind 的垫片
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 函数。同时兼容写法,对参数的多少做出了判断,使用call和apply实现,据说参数多适合用 apply,参数少用 call 性能更好。
由于现在浏览器基本都是支持原生bind的,所以直接用 polyfillBind写个例子:
class Dog {
say = function (word) {
console.log(this.name + " say: " + word);
};
}
const dog = new Dog();
class Cat {
constructor() {
this.name = "mao";
}
}
const cat = new Cat();
fn = polyfillBind(dog.say, cat);
fn("hello"); // mao say: hello
1.25 toArray 把类数组转成真正的数组
把类数组转换成数组,支持从指定位置开始,默认从 0 开始。
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;
}
// 例子:
function fn() {
// 使用1.8 toRawType 方法得到参数arguments的类型是 Arguments, 不是Array, 也就是个类数组结构
var arr1 = toArray(arguments);
console.log(arr1); // [1, 2, 3, 4, 5]
var arr2 = toArray(arguments, 2);
console.log(arr2); // [3, 4, 5]
}
fn(1, 2, 3, 4, 5);
1.26 extend 合并
function extend(to, _from) {
for (var key in _from) {
to[key] = _from[key];
}
return to;
}
const person = { name: "Niko" };
const person2 = extend(person, { age: 26, gender: "male" });
console.log(person); // {name: 'Niko', age: 26, gender: 'male'}
console.log(person2); // {name: 'Niko', age: 26, gender: 'male'}
console.log(person === person2); // true
1.27 toObject 数组转对象
function toObject(arr) {
var res = {};
for (var i = 0; i < arr.length; i++) {
if (arr[i]) {
extend(res, arr[i]);
}
}
return res;
}
toObject(["Niko", "Nikola"]);
// {0: 'N', 1: 'i', 2: 'k', 3: 'o', 4: 'l', 5: 'a'}
1.28 noop 空函数
function noop(a, b, c) {}
1.29 no 一直返回 false
var no = function (a, b, c) {
return false;
};
1.30 identity 返回参数本身
var identity = function (_) {
return _;
};
1.31 genStaticKeys 生成静态属性
function genStaticKeys(modules) {
return modules
.reduce(function (keys, m) {
return keys.concat(m.staticKeys || []);
}, [])
.join(",");
}
genStaticKeys([{ staticKeys: ["a", "b", "c"] }, { staticKeys: ["d", "e"] }]);
// a,b,c,d,e
1.32 looseEqual 宽松相等
由于数组、对象等是引用类型,所以两个内容宽松相等,严格相等都是不相等。
var a = {};
var b = {};
a === b; // false
a == b; // false
所以该函数是对数组、日期、对象进行递归比对。如果内容完全相等则宽松相等。
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;
}
}
1.33 looseIndexOf 宽松的 indexOf
原生的indexOf判断是严格相等的,该函数实现的是宽松相等的 indexOf。
function looseIndexOf(arr, val) {
for (var i = 0; i < arr.length; i++) {
if (looseEqual(arr[i], val)) {
return i;
}
}
return -1;
}
1.34 once 确保函数只执行一次
利用闭包特性,存储状态
function once(fn) {
var called = false;
return function () {
if (!called) {
called = true;
fn.apply(this, arguments);
}
};
}
const fn1 = once(function () {
console.log("无论几次调用,只执行一次");
});
fn1(); // '无论几次调用,只执行一次'
fn1(); // 不输出
fn1(); // 不输出
fn1(); // 不输出
1.35 生命周期等
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",
];
原文链接:https://juejin.cn/post/7024276020731592741