一、前言
本篇文章是笔者从事两年前端以来的第一篇源码系列的文章, 平时在搭建项目的基础架构时,必不可少的会添加 utils 工具函数, 那 Vue 源码是怎么写工具函数的呢? 带着疑问随笔者一起走进 Vue 源码的第一课工具函数
二、环境准备
为了降低难度,我们可以选择按照贡献指南中方法打包把 ts 转成了 js
node -v
#v16.9.0
# 克隆项目
git clone https://github.com/vuejs/vue-next.git
cd vue-next
# 全局安装 yarn
npm install --global yarn
yarn install
# 打包构建代码
yarn build
可以得到 vue-next/packages/shared/dist/shared.esm-bundler.js
或者直接看 vue-next/packages/shared/src/index.ts
三、工具函数
1. babelParserDefaultPlugins babel 解析默认插件
const babelParserDefaultPlugins = [
"bigInt",
"optionalChaining",
"nullishCoalescingOperator",
];
这里是几个默认插件,感兴趣可以查看
2. EMPTY_OBJ 空对象
const EMPTY_OBJ =
process.env.NODE_ENV !== "production" ? Object.freeze({}) : {};
const EMPTY_OBJ_1 = Object.freeze({});
EMPTY_OBJ_1.name = "工具函数";
console.log(EMPTY_OBJ_1.name);
const EMPTY_OBJ_2 = Object.freeze({ info: { name: "工具函数" } });
EMPTY_OBJ_2.info.nick = "函数";
EMPTY_OBJ_2.obj = "props2";
console.log(EMPTY_OBJ_2.info.name);
console.log(EMPTY_OBJ_2.obj);
console.log(EMPTY_OBJ_2);
process.env.NODE_ENV 是 node 项目中的一个环境变量,一般定义为:development 和production 。根据环境写代码。比如开发环境,有报错等信息,生产环境则不需要这些报错警告。
3. EMPTY_ARR 空数组
const EMPTY_ARR =
process.env.NODE_ENV !== "production" ? Object.freeze([]) : [];
EMPTY_ARR.push(1);
EMPTY_ARR.length = 3;
console.log(EMPTY_ARR.length);
4. NOOP 空函数
const NOOP = () => {};
const dev = true;
if (dev) {
instance.render = function () {
console.log("render");
};
}
if (instance.render === NOOP) {
console.log("i");
}
5. NO 永远返回 false 的函数
const NO = () => false;
6. isOn 判断是 on 开头,并且 on 后首字母不是小写字母
const onRE = /^on[^a-z]/;
const isOn = (key) => onRE.test(key);
isOn("onChange");
isOn("onchange");
isOn("on1");
onRE 是正则, ^on 是指 on 开头, ^a-z 是指不是 a-z 小写字母
7. isModelListener 监听器
const isModelListener = (key) => key.startsWith("onUpdate:");
isModelListener("onUpdate:change");
isModelListener("1onUpdate:change");
判断字符串是不是onUpdate: 开头
startsWith() 方法用于检测字符串是否以指定的前缀开始。
8. extend 合并
const extend = Object.assign;
const data = { name: "工具", age: 30 };
const data2 = extend(data, { mp: "前端小溪", name: "工具函数" });
console.log(data);
console.log(data2);
console.log(data === data2);
将两个对象合并,如相同属性,后者覆盖前者,且存在继承关系
9. remove 删除数组某一项
const remove = (arr, el) => {
const i = arr.indexOf(el);
if (i > -1) {
arr.splice(i, 1);
}
};
const arr = ["a", 2, "c"];
remove(arr, "c");
console.log(arr);
10. hasOwn 是不是自己本身所拥有的属性
const hasOwnProperty = Object.prototype.hasOwnProperty;
const hasOwn = (val, key) => hasOwnProperty.call(val, key);
hasOwn({ name: "前端小溪" }, "name");
hasOwn({ name: "前端小溪" }, "a");
hasOwn({ name: "前端小溪" }, "toString");
hasOwn({ __proto__: { name: "前端小溪" } }, "name");
hasOwn 可判断是否自身的属性,不包含原型上的方法
11. isArray 判断是不是数组
const isArray = Array.isArray;
isArray([1, 2, 3, 4]);
var obj = {
a: 1,
b: 2,
};
isArray(obj);
isArray(new Array());
isArray("Array");
const fakeArr = { __proto__: Array.prototype, length: 0 };
isArray(fakeArr);
fakeArr instanceof Array;
12. isMap 判断是不是 Map 对象
const isMap = (val) => toTypeString(val) === "[object Map]";
const map = new Map();
const o = { p: "Hello World" };
map.set(o, "content");
map.get(o);
isMap(map);
ES6 新增 Map 与 Set ,详情可查看
13. isSet 判断是不是 Set 对象
const isSet = (val) => toTypeString(val) === "[object Set]";
const set = new Set();
isSet(set);
Set 对象较多在数组去重时用到
14. isDate 判断是不是 Date 对象
const isDate = (val) => val instanceof Date;
isDate(new Date());
isDate({ __proto__: new Date() })(
{ __proto__: [] }
) instanceof Array;
15. isFunction 判断是不是函数
const isFunction = (val) => typeof val === "function";
isFunction(() => {});
16. isString 判断是不是字符串
const isString = (val) => typeof val === "string";
isString(12);
isString("");
isString("12");
17. isSymbol 判断是不是 Symbol
const isSymbol = (val) => typeof val === "symbol";
let s = Symbol();
typeof s;
Symbol 表示独一无二的值
18. isObject 判断是不是对象
const isObject = (val) => val !== null && typeof val === "object";
isObject({});
isObject(null);
19. isPromise 判断是不是 Promise
const isPromise = (val) => {
return isObject(val) && isFunction(val.then) && isFunction(val.catch);
};
const p1 = new Promise(function (resolve, reject) {
resolve("若川");
});
isPromise(p1);
判断参数是否是对象,并且存在 .then 函数和 .catch 函数
20. objectToString 对象转字符串
const objectToString = Object.prototype.toString;
21. toTypeString 对象转字符串
const toTypeString = (value) => objectToString.call(value);
toTypeString({ name: "张三" });
22. toRawType 对象转字符串 截取后几位
const toRawType = (value) => {
return toTypeString(value).slice(8, -1);
};
toRawType({ name: "张三" });
23. isPlainObject 判断是不是纯粹的对象
const isPlainObject = (val) => toTypeString(val) === "[object Object]";
isPlainObject({});
isPlainObject([]);
const Person = function () {
this.name = "前端小溪";
};
isPlainObject(new Person());
24. isIntegerKey 判断是不是数字型的字符串 Key
const isIntegerKey = (key) =>
isString(key) &&
key !== "NaN" &&
key[0] !== "-" &&
"" + parseInt(key, 10) === key;
isIntegerKey("");
isIntegerKey("12");
isIntegerKey("012");
isIntegerKey("a");
判断字符串类型,并且不是 NaN ,并且不是’-'开头,并且可以被 parseInt
25. makeMap && 判断是不是纯粹的对象
makeMap 是将一个带逗号分隔的字符串,生成一个 map(键值对)对象,并返回一个可供检测key 值是否存在的函数,第二个参数是否将参数转小写
function makeMap(str, expectsLowerCase) {
const map = Object.create(null);
const list = str.split(",");
for (let i = 0; i < list.length; i++) {
map[list[i]] = true;
}
return expectsLowerCase
? (val) => !!map[val.toLowerCase()]
: (val) => !!map[val];
}
const isReservedProp = makeMap(
",key,ref," +
"onVnodeBeforeMount,onVnodeMounted," +
"onVnodeBeforeUpdate,onVnodeUpdated," +
"onVnodeBeforeUnmount,onVnodeUnmounted"
);
isReservedProp("key");
isReservedProp("ref");
isReservedProp("onVnodeBeforeMount");
isReservedProp("onVnodeMounted");
isReservedProp("onVnodeBeforeUpdate");
isReservedProp("onVnodeUpdated");
isReservedProp("onVnodeBeforeUnmount");
isReservedProp("onVnodeUnmounted");
26. cacheStringFunction 缓存
const cacheStringFunction = (fn) => {
const cache = Object.create(null);
return (str) => {
const hit = cache[str];
return hit || (cache[str] = fn(str));
};
};
let add = (a, b) => a + b;
let calculate = cacheStringFunction(add);
calculate(10, 20);
calculate(10, 20);
var count = 0;
var fn = function (n) {
count++;
return n < 2 ? n : fn(n - 1) + fn(n - 2);
};
fn = cacheStringFunction(fn);
for (var i = 0; i <= 10; i++) {
fn(i);
}
console.log(count);
缓存函数是指将上次的计算结果缓存起来,当下次调用时,如果遇到相同的参数,就直接返回缓存中的数据
27. camelize 连字符转小驼峰
const camelizeRE = /-(\w)/g;
const camelize = cacheStringFunction((str) => {
return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ""));
});
camelize("on-click");
28. hyphenate 小驼峰转连字符
const hyphenateRE = /\B([A-Z])/g;
const hyphenate = cacheStringFunction((str) =>
str.replace(hyphenateRE$1, "-$1").toLowerCase()
);
hyphenate("onClick");
29. capitalize 字符串转 on 后首字母大写
const capitalize = cacheStringFunction(
(str) => str.charAt(0).toUpperCase() + str.slice(1)
);
const toHandlerKey = cacheStringFunction((str) =>
str ? `on${capitalize(str)}` : ``
);
toHandlerKey("click");
30. hasChanged 判断是不是有变化
const hasChanged = (value, oldValue) => !Object.is(value, oldValue);
hasChanged(1, 2);
31. invokeArrayFns 执行数组里的函数
const invokeArrayFns = (fns, arg) => {
for (let i = 0; i < fns.length; i++) {
fns[i](arg);
}
};
let arr = [
function (val) {
console.log(`我是${val}`);
},
function (val) {
console.log(`今年18岁了`);
},
];
invokeArrayFns(arr, "前端小溪");
32. def 定义对象属性
const def = (obj, key, value) => {
Object.defineProperty(obj, key, {
configurable: true,
enumerable: false,
value,
});
};
33. toNumber 转数字
如果是 parseFloat 返回 NaN 则返回本身, NaN 返回 true, 否则返回
const toNumber = (val) => {
const n = parseFloat(val);
return isNaN(n) ? val : n;
};
toNumber("12");
toNumber("a12");
parseFloat("a12");
isNaN(NaN);
34. getGlobalThis 全局对象
let _globalThis;
const getGlobalThis = () => {
return (
_globalThis ||
(_globalThis =
typeof globalThis !== "undefined"
? globalThis
: typeof self !== "undefined"
? self
: typeof window !== "undefined"
? window
: typeof global !== "undefined"
? global
: {})
);
};
获取全局的 this 指向
初次执行 getGlobalThis 是 undefined ,执行后面的赋值语句
self 是 Web Worker 全局对象
window 是 游览器 全局对象
global 是 Node 全局对象
四、总结
我们学习到了空对象、空数组、空函数、isMap、isSet、isPromise等函数,实现了对原生JavaScript的补充和拓展,camelize、hyphenate、toHandlerKey等函数则是Vue相关事件名称和声明周期名称的处理
我们在搭建自己项目的基础框架时,可以借鉴源码的工具函数,让我们的工具函数具有更高的可复用性和可拓展性
|