本文基于Vue2源码,深入解析JavaScript开发中的实用技巧。重点讲解如何利用缓存机制提升复杂函数性能,通过正则与缓存实现字符串风格转换,以及如何通过判断函数源码特征区分自定义函数与原生宿主函数,并介绍运行环境检测逻辑。
前言
阅读优秀代码是提升编程能力的有效途径,高质量的源码如同经典著作,能拓宽视野并带来启发。近期在研读Vue2源码时,我收集到不少JavaScript编码技巧,特此整理分享,既作为学习笔记,也供同行参考。
1. 缓存函数
假设存在一个逻辑复杂且耗时较长的函数 superComputed,若每次调用都重新计算,将导致用户等待时间过长。为解决此问题,需引入结果缓存机制:当输入参数相同时,直接返回缓存结果。这与后端通过文件缓存数据库查询结果以减轻服务器压力的思路类似,前端应如何实现这一功能?
const superComputed = (str) => {
// 假设这个函数执行时间很长
console.info("===> 超级计算开始了……");
return `输入:${str}`;
};我们可以编写一个 cached 函数来封装目标函数。该 cached 函数接收目标函数作为参数,并返回一个新的封装函数。在 cached 函数内部,可利用 Object 或 Map 来存储前一次函数调用的结果,从而实现缓存效果。
vue/src/shared/util.js该 cached 函数的具体实现代码如下:
/**
* Create a cached version of a pure function.
*/
export function cached<F: Function>(fn: F): F {
const cache = Object.create(null);
return (function cachedFn(str: string) {
const hit = cache[str];
return hit || (cache[str] = fn(str));
}: any);
}为了验证效果,我们对 cached 进行微调,使其能够实际运行。每次执行 superComputed 时,都会打印出 ===> 超级计算开始了……,以便观察函数是否真正被缓存。
具体代码如下:
const superComputed = (str) => {
// 假设这个函数执行时间很长
console.info("===> 超级计算开始了……");
return `输入:${str}`;
};
const cached = (fn) => {
const cache = Object.create(null);
return (str) => {
const hit = cache[str];
return hit || (cache[str] = fn(str));
};
};
const cacheSuperComputed = cached(superComputed);
console.log(cacheSuperComputed("DevPoint"));
console.log(cacheSuperComputed("DevPoint"));
console.log(cacheSuperComputed("juejin"));执行上述代码后,控制台输出结果如下:
===> 超级计算开始了…… 输入:DevPoint 输入:DevPoint ===> 超级计算开始了…… 输入:juejin
从输出结果可以看出,当参数保持不变时,程序直接获取了缓存中的数据,避免了重复计算。
2. 将dev-point转换为devPoint
在开发过程中,变量命名风格往往不统一。我们可以编写一个函数,将不同风格的字符串转换为统一的格式。
vue/src/shared/util.js/**
* Camelize a hyphen-delimited string.
*/
const camelizeRE = /-(\w)/g
export const camelize = cached((str: string): string => {
return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
})对该功能稍作修改,其中使用的 cached 函数正是前文介绍的缓存函数。
const camelizeRE = /-(\w)/g;
const camelize = cached((str) => {
return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ""));
});
console.log(camelize("dev-point")); // devPoint3. 自定义函数判断
这里提到的“自定义函数”是指开发者自行定义的函数,而非JavaScript的原生宿主函数。一种直观的思路是将函数转换为字符串进行比对,先看以下示例结果:
console.log(cached.toString());
console.log(toString.toString());执行结果如下:
// 下面是自定义函数的结果
(fn) => {
const cache = Object.create(null);
return (str) => {
const hit = cache[str];
return hit || (cache[str] = fn(str));
};
}
// 下面是原生宿主函数的结果
function toString() { [native code] } 观察结果可知,原生宿主函数 toString 的字符串表示始终呈现为 function fnName() { [native code] } 格式。利用这一特征,我们可以有效区分自定义函数与原生函数。接下来,我们将查看Vue项目中的具体实现方式。
vue/src/core/util/env.js在源码文件的第 58 行,相关代码如下:
/* istanbul ignore next */
export function isNative (Ctor: any): boolean {
return typeof Ctor === 'function' && /native code/.test(Ctor.toString())
}4. JS运行环境
随着前端技术的快速发展,JavaScript代码需适应多种运行环境。为了增强兼容性,判断当前代码所处的运行环境至关重要。下面我们来学习Vue是如何实现环境判断的:
vue/src/core/util/env.js从源码文件的第 6 行开始,相关代码如下:
// Browser environment sniffing
export const inBrowser = typeof window !== 'undefined'
export const inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform
export const weexPlatform = inWeex && WXEnvironment.platform.toLowerCase()
export const UA = inBrowser && window.navigator.userAgent.toLowerCase()
export const isIE = UA && /msie|trident/.test(UA)
export const isIE9 = UA && UA.indexOf('msie 9.0') > 0
export const isEdge = UA && UA.indexOf('edge/') > 0
export const isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android')
export const isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios')
export const isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge
export const isPhantomJS = UA && /phantomjs/.test(UA)
export const isFF = UA && UA.match(/firefox\/(\d+)/)这些判断逻辑非常值得借鉴,此处不再展开详述。此前在《4个值得收藏的Javascript技巧》一文中已介绍过浏览器判断方法,此处可相互参照。
至此,关于JavaScript函数技巧的学习分享完毕。如需了解更多相关技巧,欢迎查阅本站往期文章或继续浏览后续内容,感谢大家的支持!