JavaScript 代码整洁技巧

53 阅读2分钟

代码简写

查找条件简写方法

// 普通方法  
if (type === 'test1') {  
  test1();  
}  
else if (type === 'test2') {  
  test2();  
}  
else if (type === 'test3') {  
  test3();  
}  
else if (type === 'test4') {  
  test4();  
} else {  
  throw new Error('Invalid value ' + type);  
}  

// 简写方法  
var types = {  
  test1: test1,  
  test2: test2,  
  test3: test3,  
  test4: test4  
};  
var func = types[type];  
(!func) && throw new Error('Invalid value ' + type); func();

函数(方法)

删除重复的代码,don't repeat yourself。很多地方可以注意dry,比如偷懒复制了某段代码、try...catch或条件语句写了重复的逻辑。

 // bad
 try {
    doSomeThing();
    clearStack();
 } catch (e) {
    handleError(e);
    clearStack();
 }
 
 // good
 try {
   doSomeThing();
 } catch (e) {
   handleError(e);
 } finally {
   clearStack();
 }

形参不超过三个,对测试函数也方便。多了就使用对象参数。

同时建议使用对象解构语法,有几个好处:

  1. 能清楚看到函数签名有哪些熟悉,
  2. 可以直接重新命名,
  3. 解构自带克隆,防止副作用,
  4. Linter检查到函数未使用的属性。
// bad  
function createMenu(title, body, buttonText, cancellable) {}  
  
// good  
function createMenu({ title, body, buttonText, cancellable }) {}

函数只做一件事,代码读起来更清晰,函数就能更好地组合、测试、重构。

// bad: 处理了输入框的change事件,并创建文件的切片,并保存相关信息到localStorage  
 function handleInputChange(e) {  
   const file = e.target.files[0];  
     
   // --- 切片 ---  
   const chunkList = [];  
   let cur = 0;  
   while (cur < file.size) {  
     chunkList.push({  
     chunk: file.slice(cur, cur + size)  
   });  
   cur += size;  
 }  
 // --- 保存信息到localstorage ---  
 localStorage.setItem("file", file.name);  
 localStorage.setItem("chunkListLength", chunkList.length);  
 }  
   
 // good: 将三件事分开写,同时自顶而下读,很舒适  
 function handleInputChange(e) {  
   const file = e.target.files[0];  
   const chunkList = createChunk(file);  
   saveFileInfoInLocalStorage(file, chunkList);  
 }  
   
 function createChunk(file, size = SLICE_SIZE) {  
   const chunkList = [];  
   let cur = 0;  
   while (cur < file.size) {  
     chunkList.push({  
       chunk: file.slice(cur, cur + size)  
     });  
     cur += size;  
   }  
   return chunkList  
 }  
   
 function saveFileInfoInLocalStorage(file, chunkList) {  
   localStorage.setItem("file", file.name);  
   localStorage.setItem("chunkListLength", chunkList.length);  
 }

自顶向下地书写函数,人们都是习惯自顶向下读代码,如,为了执行A,需要执行B,为了执行B,需要执行C。如果把A、B、C混在一个函数就很难读了。(看前一个的例子)。

不使用布尔值来作为参数,遇到这种情况时,一定可以拆分函数。

// bad  
function createFile(name, temp) {  
  if (temp) {  
    fs.create(`./temp/${name}`);  
  } else {  
    fs.create(name);  
  }  
}  
  
// good  
function createFile(name) {  
  fs.create(name);  
}  
function createTempFile(name) {  
  createFile(`./temp/${name}`);  
}

避免副作用。

  • 常见就是陷阱就是对象之间共享了状态,使用了可变的数据类型,比如对象和数组。对于可变的数据类型,使用immutable等库来高效克隆。
  • 避免用可变的全局变量。
  • 集中副作用:遇到不可避免的副作用时候,比如读写文件、上报日志,那就在一个地方集中处理副作用,不要在多个函数和类处理副作用。
  • 其他注意的地方
// bad:注意到cart是引用类型!  
const addItemToCart = (cart, item) => {  
  cart.push({ item, dateDate.now() });  
};  

// good  
const addItemToCart = (cart, item) => {  
  return [...cart, { item, dateDate.now() }];  
};

封装复杂的判断条件,提高可读性。

// bad  
if (!(obj => obj != null && typeof obj[Symbol.iterator] === 'function')) {  
  throw new Error('params is not iterable')  
}

// good  
const isIterable = obj => obj != null && typeof obj[Symbol.iterator] === 'function';  
if (!isIterable(promises)) {  
  throw new Error('params is not iterable')  
}

在方法中有多条件判断时候,为了提高函数的可扩展性,考虑下是不是可以使用能否使用多态性来解决。

// 地图接口可能来自百度,也可能来自谷歌  
const googleMap = {  
  showfunction (size) {  
    console.log('开始渲染谷歌地图', size));  
  }  
}; 
const baiduMap = {  
  renderfunction (size) {  
    console.log('开始渲染百度地图', size));  
  }  
}; 
  
// bad: 出现多个条件分支。如果要加一个腾讯地图,就又要改动renderMap函数。  
function renderMap(type) {  
  const size = getSize();  
  if (type === 'google') {  
    googleMap.show(size);  
  } else if (type === 'baidu') {  
    baiduMap.render(size);  
  }  
};  
renderMap('google')  
  
// good:实现多态处理。如果要加一个腾讯地图,不需要改动renderMap函数。  
// 细节:函数作为一等对象的语言中,作为参数传递也会返回不同的执行结果,也是“多态性”的体现。  
function renderMap (renderMapFromApi) {  
  const size = getSize();  
  renderMapFromApi(size);  
}  
renderMap((size) => googleMap.show(size));

注释

TODO注释,记录下应该做但还没做的工作(提前写好命名,可以帮助后来者统一命名风格)
hack注释,警示作用。

class Comment {  
 // todo: 删除功能后期实现  
 delete() {}  
}

// hack: 由于XXX历史原因,只能调度一下。  
setTimeout(doSomething, 0)

对象

多使用getter和setter(getXXX和setXXX)。好处:

  • 在set时方便验证。
  • 可以添加埋点,和错误处理。
  • 可以延时加载对象的属性。
// good  
function makeBankAccount() {  
 let balance = 0;  
 function getBalance() {  
   return balance;  
 }  
 function setBalance(amount) {  
   balance = amount;  
 }  
 return {  
   getBalance,  
   setBalance  
 };  
}  
const account = makeBankAccount();  
account.setBalance(100);