We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
看了一下公司的项目,从源代码角度看到了一些可优化的部分,有些比较典型,列举如下
/** * 获取 browser 信息 */ export function getBrowser() { const ua = window.navigator.userAgent || ""; const isAndroid = /android/i.test(ua); const isIos = /iphone|ipad|ipod/i.test(ua); const isWechat = /micromessenger\/([\d.]+)/i.test(ua); const isWeibo = /(weibo).*weibo__([\d.]+)/i.test(ua); const isQQ = /qq\/([\d.]+)/i.test(ua); const isQQBrowser = /(qqbrowser)\/([\d.]+)/i.test(ua); const isQzone = /qzone\/.*_qz_([\d.]+)/i.test(ua); // 安卓 chrome 浏览器,很多 app 都是在 chrome 的 ua 上进行扩展的 const isOriginalChrome = /chrome\/[\d.]+ Mobile Safari\/[\d.]+/i.test(ua) && isAndroid; // chrome for ios 和 safari 的区别仅仅是将 Version/<VersionNum> 替换成了 CriOS/<ChromeRevision> // ios 上很多 app 都包含 safari 标识,但它们都是以自己的 app 标识开头,而不是 Mozilla const isSafari = /safari\/([\d.]+)$/i.test(ua) && isIos && ua.indexOf("Crios") < 0 && ua.indexOf("Mozilla") === 0; return { isAndroid, isIos, isWechat, isWeibo, isQQ, isQQBrowser, isQzone, isOriginalChrome, isSafari }; }
getBrowser 方法是用于环境信息的方法,获取的方式本身没什么问题,但是有些信息有个特点:是不可变的,多次调用的结果其实是一样的。 这种情况,我们其实可以把这些环境信息缓存下来,避免重复获取。
const env = (function() { return getBrowser(); })()
就是通过自执行函数,预执行 getBrowser 方法,并将结果缓存在 env 变量中,这样就解决了多次调用重复执行的问题。但是这种方式有个问题:会延长加载脚本的时间,而且有可能自始至终都没用到这些信息,这样也是资源的浪费。 我们可以用单例模式的思想做进一步优化。
const getEvn = (function() { let env; return function() { if (!env) { env = getBrowser(); } return env; } })()
这样的话,只有第一次执行时会调用 getBrowser 方法,后续调用都是用的第一次缓存的结果。上面的代码其实就是典型的单例模式的抽象实现,getBrowser 方法完全可以替换成其他方法。
const getSingle = function(fn) { let ret; return function() { if (!ret) { result = fn.apply(this, arguments) } return ret; } } const getEnv = getSingle(getBrowser);
/** * 判断两个版本 * 比如:'1.5.5','1.5.0'进行比较,返回的是5,前面的版本大于后面5个版本 * @param {*} preV * @param {*} nextV */ export const compareVersion = (preV, nextV) => { const pvs = preV.split("."); const nvs = nextV.split("."); const rv = pvs[0] - nvs[0]; return rv === 0 && preV !== nextV ? compareVersion(pvs.splice(1).join("."), nvs.splice(1).join(".")) : rv; };
上面的主要是通过递归的方式判断2个版本号的大小。主要有2个问题
第一个问题很容易解决,只需要把递归方法的参数统一用数组即可。
const compareVersion = (preV, nextV) => { const pvs = preV.split("."); const nvs = nextV.split("."); function _compare(pvs, nvs) { const rv = pvs[0] - nvs[0]; return rv === 0 && pvs.length > 1 && nvs.length > 1 ? _compare(pvs.slice(1), nvs.slice(1)) : rv; } return _compare(pvs, nvs); };
考虑到这个逻辑其实就是2个数组值大小的比对,用遍历可容易实现。
const compareVersion = (preV, nextV) => { const pvs = preV.split("."); const nvs = nextV.split("."); let ret, i = 0; for (; i < pvs.length && i < nvs.length; i++) { ret = pvs[i] - nvs[i]; if (ret !== 0) { break; } } return ret; };
另外举个比较典型的遍历 > 递归的例子就是斐波那契数列 F(n) = F(n - 1) + F(n - 2)。如果用递归去解斐波那契数列,会造成大量的计算冗余,性能很低。 当然也可以通过缓存的方式避免重复计算,但是用遍历的方式是更好的选择。
function fib(n) { if (n === 1) { return 0; } if (n === 2) { return 1; } let t1 = 0, t2 = 1, i = 3; for (; i <= n; i++) { if (i === n) { return t1 + t2; } [t1, t2] = [t2, t1 + t2]; } }
/** * 十进制数字精度转换 * @param {待转换数字} num * @param {保留小数位数}} decimals * @param {是否返回“+”符号} withSign * 对于null, undefined, NaN均返回0.00 */ function toFixed(num, decimals = 2, withSign, isOmitZero) { let number = num; if (typeof decimals !== "number") { throw new TypeError("传入toFixed的decimals参数类型不正确"); } if (typeof num !== "number") { number = Number(num); if (Number.isNaN(number)) { return (0).toFixed(decimals); } } if (Number.isNaN(number)) { return (0).toFixed(decimals); } let result = number.toFixed(decimals); if (isOmitZero) { result = result.replace(/(?:\.0*|(\.\d+?)0+)$/, "$1"); } if (withSign && num > 0) { result = `+${result}`; } return result; }
当我们要实现一个比较复杂的功能时,我们需要对逻辑进行拆封和排序。以上面的 toFixed 方法为例,它实现的是数据格式化的功能,有4个参数
因为不同的数据类型格式化的方式是不同的,所以我们可以按数据的类型将这个功能分为3个部分:
尽量将特殊情况判断放在前面,这样可以使后面的分支中少很多的特殊情况判断,提取出可抽象的公共的代码逻辑
function toFixed(num, decimals = 2, withSign, isOmitZero) { function zs(num, decimal) { return num + '.' + '0'.repeat(decimal); } function xs(num, decimal) { return parseFloat((num + '0'.repeat(decimal) + '1')).toFixed(decimal); } function isInt(n){ return parseInt(n) == parseFloat(n) } function isFloat(n) { return parseInt(n) < parseFloat(n) } let ret = num; if (num == 0 && num !== 0 && num !== '0') { if (isOmitZero) { return '0.00'; } } else if (typeof num === 'string' || typeof num === 'number') { if (isInt(num)) { ret = zs(num, decimals); } else if (isFloat(num)) { ret = xs(num, decimals); } if (withSign && ret > 0) { ret = '+' + ret; } } return ret; }
这样可以让代码的层次更加的清晰、易读。 还可以用策略模式进一步减少分支判断:
const formats = { zs: function(num, decimal) { return num + '.' + '0'.repeat(decimal); }, xs: function(num, decimal) { return parseFloat((num + '0'.repeat(decimal) + '1')).toFixed(decimal); }, default: function(num, decimal) { return num; }, } function whichFormat(n){ const i = parseInt(n); const f = parseFloat(n); if (i == f) { return 'zs'; } else if (i < f) { return 'xs'; } else { return 'default'; } } function toFixed(num, decimals = 2, withSign, isOmitZero) { let ret = num; if (num == 0 && num !== 0 && num !== '0') { if (isOmitZero) { return '0.00'; } } else if (typeof num === 'string' || typeof num === 'number') { ret = formats[whichFormat(num)](num, decimals); if (withSign && ret > 0) { ret = '+' + ret; } } return ret; }
可以看到主要的逻辑实现已经从原方法中进行了拆封,而且该逻辑是可扩展的,原方法中只剩下一些特殊情况的处理逻辑。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
看了一下公司的项目,从源代码角度看到了一些可优化的部分,有些比较典型,列举如下
1. 缓存数据
getBrowser 方法是用于环境信息的方法,获取的方式本身没什么问题,但是有些信息有个特点:是不可变的,多次调用的结果其实是一样的。
这种情况,我们其实可以把这些环境信息缓存下来,避免重复获取。
就是通过自执行函数,预执行 getBrowser 方法,并将结果缓存在 env 变量中,这样就解决了多次调用重复执行的问题。但是这种方式有个问题:会延长加载脚本的时间,而且有可能自始至终都没用到这些信息,这样也是资源的浪费。
我们可以用单例模式的思想做进一步优化。
这样的话,只有第一次执行时会调用 getBrowser 方法,后续调用都是用的第一次缓存的结果。上面的代码其实就是典型的单例模式的抽象实现,getBrowser 方法完全可以替换成其他方法。
2. 递归的使用
上面的主要是通过递归的方式判断2个版本号的大小。主要有2个问题
第一个问题很容易解决,只需要把递归方法的参数统一用数组即可。
考虑到这个逻辑其实就是2个数组值大小的比对,用遍历可容易实现。
另外举个比较典型的遍历 > 递归的例子就是斐波那契数列 F(n) = F(n - 1) + F(n - 2)。如果用递归去解斐波那契数列,会造成大量的计算冗余,性能很低。
当然也可以通过缓存的方式避免重复计算,但是用遍历的方式是更好的选择。
3. 代码的组织结构
当我们要实现一个比较复杂的功能时,我们需要对逻辑进行拆封和排序。以上面的 toFixed 方法为例,它实现的是数据格式化的功能,有4个参数
因为不同的数据类型格式化的方式是不同的,所以我们可以按数据的类型将这个功能分为3个部分:
尽量将特殊情况判断放在前面,这样可以使后面的分支中少很多的特殊情况判断,提取出可抽象的公共的代码逻辑
这样可以让代码的层次更加的清晰、易读。
还可以用策略模式进一步减少分支判断:
可以看到主要的逻辑实现已经从原方法中进行了拆封,而且该逻辑是可扩展的,原方法中只剩下一些特殊情况的处理逻辑。
The text was updated successfully, but these errors were encountered: