函数最佳实践
函数最佳实践
-
避免回调
回调很混乱,会导致代码嵌套过深,使用 Promise 替代回调。bad : ❌ getUser(function (err, user) { getProfile(user, function (err, profile) { getAccount(profile, function (err, account) { getReports(account, function (err, reports) { sendStatistics(reports, function (err) { console.error(err); }); }); }); }); }); good ✅ getUser() .then(getProfile) .then(getAccount) .then(getReports) .then(sendStatistics) .catch((err) => console.error(err)); good or using Async/Await ✅✅ async function sendUserStatistics() { try { const user = await getUser(); const profile = await getProfile(user); const account = await getAccount(profile); const reports = await getReports(account); return sendStatistics(reports); } catch (e) { console.error(err); } } -
**避免过长参数:**函数规范,参数尽量限制为3个以内;三个及以上,需要考虑独立为JSON对象,通过es6解构获取
bad : ❌ const setUserInfo = function( id, name, address, sex, mobile){ console.log(id,name,address,sex,mobile) }; setUserInfo( '0921', 'ricky', 'sh', 'male', '131********'); good ✅ const setUserInfo = function( obj ){ const {id,name,address,sex,mobile} = obj; console.log(id,name,address,sex,mobile) }; setUserInfo({ id: '0921', name: 'ricky', address: 'sh', sex: 'male', mobile: '137********', }); -
尽早从函数返回,减少分支
bad : ❌ const del = function( obj ){ let ret; if ( !obj.isReadOnly ){ // 不为只读的才能被删除 if ( obj.isFolder ){ // 如果是文件夹 ret = deleteFolder( obj ); }else if ( obj.isFile ){ // 如果是文件 ret = deleteFile( obj ); } } return ret; }; good ✅ const del = function( obj ){ if ( obj.isReadOnly ){ // 反转if表达式 return; } if ( obj.isFolder ){ return deleteFolder( obj ); } if ( obj.isFile ){ return deleteFile( obj ); } }; //减少了临时变量,并提前返回 -
分解条件表达式
bad : ❌ const ieIEMac = navigator.userAgent.toLowerCase().includes("mac") && navigator.userAgent.toLowerCase().includes("ie") 不利于阅读 good ✅ const userAgent = navigator.userAgent.toLowerCase(); const isMac = () => userAgent.includes("mac"); const isIE = () => userAgent.toLowerCase().includes("ie"); const isMacIE = isMac() && isIE(); -
函数最好使用参数默认值
bad : ❌ function fn (name, age) { var name = name || 'ricky' var age = age || 18 console.log(name, age) } fn() // ricky 18 good ✅ function fn (name = 'ricky', age = 18) { console.log(name, age) } fn() // ricky 18 -
switch 或者 if elseif过多,可以考虑多态重构
使用类及多态 ,可以把逻辑的拆分表述的更清晰
可以针对 switch语句中的每种分支逻辑创建一个类,用多态来承载各个类型特有的行为,从而除去复杂的分支 逻辑switch(bird.type){ case 'EuropeanSwallow': return 'average'; case 'AfricanSwallow': return (bird.numberOfCocounts > 2) ? 'tired' : 'average'; case 'NorwegianBlueParrot': return (bird.voltage >100 ) ? 'scorched' : 'beautiful'; default: return 'unkonwn'; } class EuropeanSwallow extends Bird { get plumage() { return 'average'; } } class AfricanSwallow extends Bird{ get plumage(){ return (this.numberOfCocounts > 2) ? 'tired' : 'average'; } } class NorwegianBlueParrot extends Bird{ get plumage(){ return (this.voltage >100 ) ? 'scorched' : 'beautiful'; } }优先使用MAP来减少
// bad ❌ const getColorByStatus = (status) => { switch (status) { case "success": return "green"; case "failure": return "red"; case "warning": return "yellow"; case "loading": default: return "blue"; } }; // good ✅ const statusColors = { success: "green", failure: "red", warning: "yellow", loading: "blue", }; const getColorByStatus = (status) => statusColors[status] || "blue"; -
参数复用,柯里化
本示例只是表达可以通过柯里化 展现可以复用参数的一种方式
function check(reg,txt){
return reg.test(txt)
}
bad : ❌
// 需要传两个参数
check(/\d+/g,'test1') //true
check(/\d+/g,'test') //false
good:✅
let curryingCheck = currying(check)
var hasNumber = curryingCheck(/\d+/g);
hasNumber('test1') //true
hasNumber('test') //false
//柯里化的实现函数,供参考
function currying(fun,initArgs){
let len = fun.length;
let _t = this;
let args = initArgs || [];
return function() {
let _args = [...args, ...arguments]; //参数
if(_args.length <len){
return currying.call(_t,fun,_args);
}
return fun.apply(this,_args);
}
}
-
管道函数替代循环
bad : ❌ const names=[]; for(const i of input){ if(i.job === 'programmer'){ names.push(i.name); } } good ✅ const names =input.filter(i=>i.job === 'programmer').map(i => i.name) -
闭包记住的是 变量的引用,而不是闭包创建时刻该变量的值
bad : ❌ const divs = document.getElementsByTagName("div"); for(var i=0;i< divs.length;i++){ divs[i].addEventListener("click",function(){ alert("divs #" +i + " was clicked."); },false); } bad : ❌ for(var i=0;i< divs.length;i++)(function(n){ divs[n].addEventListener("click",function(){ alert("divs #" +n + " was clicked."); },false); })(i); good ✅ for(let i=0;i< divs.length;i++){ divs[i].addEventListener("click",function(){ alert("divs #" +i + " was clicked."); },false); } -
settimeout/setInterval 需要清除
-
避免无限递归,要有终止条件
bad : ❌ const repeat = (n) => {return repeat(n-1)+'-repeat'} // Uncaught RangeError: Maximum call stack size exceeded good ✅ const repeat = (n) => {return n >1 ? repeat(n-1)+'-repeat':'repeat'} //'repeat-repeat-repeat'
数组最佳实践
尽量考虑使用lodash提供的方法
-
使用...
bad : ❌ const a = [1,2,3]; const b = [1,5,6]; const c = a.concat(b);//[1,2,3,1,5,6] const obj1 = { a:1, } const obj2 = { b:1, } const obj = Object.assign({}, obj1, obj2);//{a:1,b:1} good ✅ const a = [1,2,3]; const b = [1,5,6]; const c = [...new Set([...a,...b])];//去重 [1,2,3,5,6] const obj1 = { a:1, } const obj2 = { b:1, } const obj = {...obj1,...obj2};//{a:1,b:1} -
使用Array.from 将类数组对象转化为数组,const arr = Array.form(arrLike)
-
优先使用includes减少if判断条件
bad : ❌ if( type == 1 || type == 2 || type == 3 || type == 4 || ){ //... } good ✅ const condition = [1,2,3,4]; if( condition.includes(type) ){ //... } -
使用Array.find查找
const data = [ { type: 'test1', name: 'abc' }, { type: 'test2', name: 'cde' }, { type: 'test1', name: 'fgh' }, ] bad : ❌ function findtest1(name) { for (let i = 0; i < data.length; ++i) { if (data[i].type === 'test1' && data[i].name === name) { return data[i]; } } } good ✅ filteredData = data.find(data => data.type === 'test1' && data.name === 'fgh'); console.log(filteredData); // { type: 'test1', name: 'fgh' } -
使用for-of或数组方法替代for(;;)
const arr=[1,2,3,4,5]; bad : ❌ for(let i=0;i<arr.length;i++){ console.log(arr[i]); } good ✅ for(let item of arr) { console.log(item) } 1 2 3 4 5 -
扁平化数组
//一个部门JSON数据中,属性名是部门id,属性值是个部门成员id数组集合,现在要把有部门的成员id都提取到一个数组集合中。 const deps = { '采购部':[1,2,3], '人事部':[5,8,12], '行政部':[5,14,79], '运输部':[3,64,105], } bad : ❌ let member = []; for (let item in deps){ const value = deps[item]; if(Array.isArray(value)){ member = [...member,...value] } } member = [...new Set(member)] good ✅ let member = Object.values(deps).flat(Infinity);