Open Leecason opened 5 years ago
单一职责原则 (single responsibility principle)
在 JavaScript 开发中,大部分时间都在与函数打交道,所以函数应该有良好的命名,函数体内的逻辑清晰明了。如果一个函数过长,需要加注释才会让这个函数显得易读。那么这些函数就需要重构了。
如果在函数中有一段代码可以被独立出来,那我们最好把这部分代码放入一个独立的函数中。
好处: 1.避免出现超大函数 2.独立出来的函数有助于代码复用 3.独立出来的函数更容易被复写 4.独立出来的函数如果拥有一个良好的命名,它本身就起到了注释的作用
function getInfo () {
ajax('url', function (data) {
console.log(data.id);
console.log(data.name);
});
}
改为
function getInfo () {
ajax('url', function (data) {
printDetail(data);
});
}
function printDetail (data) {
console.log(data.id);
console.log(data.name);
}
如果一个函数体内有一些条件分支语句,而这些条件分支语句内部散布了一些重复的代码,那么就有必要进行合并去重
let paging = function (currentPage) {
if (currentPage <= 0) {
currentPage = 0;
jump(currentPage);
} else if (currentPage >= totalPage) {
currentPage = totalPage;
jump(currentPage);
} else {
jump(currentPage);
}
}
改为
let paging = function (currentPage) {
if (currentPage <= 0) {
currentPage = 0;
} else if (currentPage >= totalPage) {
currentPage = totalPage;
}
jump(currentPage);
}
复杂的条件分支语句是导致程序难以阅读和理解的重要原因,而且容易导致一个庞大的函数。
function getPrice (price) {
let date = new Date();
if (date.getMonth() >= 6 && data.getMonth <= 9) {
return price * 0.8;
}
return price;
}
改为
function isSummer () {
let date = new Date();
return date.getMonth() >= 6 && data.getMonth <= 9;
}
function getPrice (price) {
if (isSummer()) {
return price * 0.8;
}
return price;
}
在函数体内,如果有些代码实际上负责的是一些重复性的工作,那么合理利用循环不仅可以完成同样的功能,还可以使代码量更少。
function createXHR () {
let xhr;
try {
xhr = new ActiveXObject('MSXML2.XMLHttp.6.0');
} catch (e) {
try {
xhr = new ActiveXObject('MSXML2.XMLHttp.3.0');
} catch (e) {
xhr = new ActiveXObject('MSXML2.XMLHttp');
}
}
return xhr;
}
改为
const versions = ['MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp'];
function createXHR () {
for (var i = 0, version; version = versions[i++];) {
try {
return new ActiveXObject(version);
} catch (e) {
}
}
}
嵌套的条件分支语句是代码维护者的噩梦。我们可以挑选一些条件分支,在进入这些条件后,就立即让这个函数退出。要做到这一点,有一个常见的技巧,就是在面对一个嵌套的 if 分支时,我们可以把外层 if 表达式进行反转。
function deleteObj (obj) {
if (!obj.isReadOnly) {
if (obj.isFolder) {
deleteFolder(obj);
} else if (obj.isFile) {
deleteFile(obj);
}
}
}
改为
function deleteObj (obj) {
if (obj.isReadOnly) return;
if (obj.isFolder) {
deleteFolder(obj);
return;
}
if (obj.isFile) {
deleteFile(obj);
return;
}
}
有时候一个函数可能接收多个参数,而参数越多,函数就越难理解和使用。使用的人首先得搞明白全部参数的含义,在使用的时候,还要避免少传了某个参数或者把两个参数搞反了位置。而且如果我们想在第三个参数和第四个参数之间增加一个新的参数,就会涉及到许多代码的修改。这时我们就可以把参数都放入一个对象内,然后把该对象传入函数,函数需要的数据就可以自行从该对象里获取。不用再关心参数的数量和顺序,只需要保证参数对应的 key 值不变就可以了。
function setInfo (id, name, address, sex, mobile) {
...
}
setInfo(000, 'sven', 'chengdu', 'male', '135...');
改为
setInfo (info) {
...
}
setInfo({
id: 000,
name: 'sven',
address: 'chengdu',
sex: 'male',
mobile: '135...'
});
如果调用一个函数时需要传入多个参数,那么这个函数是让人望而生畏的,我们必须要搞清楚这些参数代表的含义,而如果一个函数如果不需要传入任何参数就可以使用,这种是函数是受欢迎的。实际开发中向函数传入参数不可避免,但我们应该尽量减少函数接收的参数数量。
有些程序员喜欢用三目运算符来代替传统的 if、else。理由是性能高,代码少。即使三目运算符的效率真的比 if、else 高,这点差距也是可以忽略的。实际开发中即使把一段代码循环100w次,三用三目运算符和 if、else 的时间开销也处在同一级别。
同样,相比损失的代码可读性和可维护性,三目运算符节省的代码量也可以忽略不计,让 js 文件加载更快的办法有很多,如压缩、缓存、使用 CDN 等。不应该把注意力只放在使用三目运算符节省的字符数量上。如果条件分支逻辑简单清晰,是可以使用三目运算符的。
let global = typeof window !== "undefined" ? window : this;
如果条件分支逻辑非常复杂,最好还是按部就班的使用 if、else,一是阅读容易,二是修改的时候更加方便。
在 JavaScript 中,可以很容易的实现方法的链式调用,即让方法调用结束后返回自身。 使用链式调用的方式并不会造成太多阅读上的困难,也确实能省下一些字符和中间变量,节省的字符同样是忽略不计的。缺点就是在调试的时候不方便,其中一条链中有问题必须先拆开才能定位错误。 如果该链结构相对稳定,后期不易发生修改,那么应该使用链式调用。
new User().setId(000).setName('sven');
如果该链容易发生变化,为了调试和维护,应该使用普通调用。
let user = new User();
user.setId(000);
user.setName('sven');
面向对象设计鼓励将行为分布在合理数量的更小对象中。
function Spirit (name) {
this.name = name;
}
Spirit.prototype.attack = function (type) {
if (type === 'waveBoxing') {
console.log('使用波动拳')
} else if (type === 'whirlKick') {
console.log('使用旋风腿');
}
}
改为
function Attack (spirit) {
this.spirit = spirit;
}
Attack.prototype.start = function (type) {
return this.list[type].call(this);
}
Attack.prototype.list = {
waveBoxing () {
console.log('使用波动拳');
},
whirlKick () {
console.log('使用旋风腿');
}
}
function Spirit (name) {
this.name = name;
this.attackObj = new Attack(this);
}
Spirit.prototype.attack = function (type) {
this.attackObj.start(type);
}
假设在函数体内有一个两重循环语句,我们需要在内层循环中判断,当达到某个临界条件时退出外层的循环,我们可以引入一个控制标记变量。
function func () {
let flag = false;
for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
if (i * j > 30) {
flag = true;
break;
}
}
if (flag) {
break;
}
}
}
第二种做法是设置循环标记。
function func () {
outerloop:
for (let i = 0; i < 10; i++) {
innerloop:
for (let j = 0; j < 10; j++) {
if (i * j > 30) {
break outerloop;
}
}
}
}
更简单的做法是在需要中止循环的时候直接退出整个方法。
function func () {
for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
if (i * j > 30) {
return;
}
}
}
}
用 return 直接退出方法会带来一个问题,如果在循环之后还有一段代码需要被执行,我们提前退出了整个方法,后面的代码就得不到被执行的机会,为了解决这个问题,我们可以把循环后面的代码直接放到 return 后面,如果代码较多,应该提炼成一个单独的函数。
function func () {
for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
if (i * j > 30) {
print(i)
return;
}
}
}
}
什么是重构
何时重构