代码简写
查找条件简写方法
// 普通方法
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();
}
形参不超过三个,对测试函数也方便。多了就使用对象参数。
同时建议使用对象解构语法,有几个好处:
- 能清楚看到函数签名有哪些熟悉,
- 可以直接重新命名,
- 解构自带克隆,防止副作用,
- 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, date: Date.now() });
};
// good
const addItemToCart = (cart, item) => {
return [...cart, { item, date: Date.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 = {
show: function (size) {
console.log('开始渲染谷歌地图', size));
}
};
const baiduMap = {
render: function (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);