记住,您编写的每一行代码都会有相应的阅读成本。看这代码的人可能是团队的成员,甚至是你未来的自己。
一、软件设计3R层次结构
- readable可读性
- reuseable可复用性
- refactorable可重构性
关键思想:
- 保持代码一致:一致的风格比“正确”的风格更重要。——编码规范、ESlint
- 易于阅读和理解:应当使别人理解它所需的时间最小化。——命名、函数式编程
- 易于维护。——设计模式思想
二、为什么我极力推荐eslint
据广泛估计,开发人员将70%的代码维护时间花在阅读上以理解它。这真让人大开眼界。居然达到了70%。难怪程序员每天编写的代码行数的平均值大约是10行。我们每天花7个小时来阅读代码,去理解这10行怎么运行!
团队合作带来的问题
看别人的代码
- 团队其他人和自己书写代码的风格不一致,很难去适应别人的编程风格。
- 甚至一个文件有多个人改动过,有多种编程风格,看着不仅难受,阅读代码也较吃力。
改别人的代码
- 代码没有格式化便提交,增加团队成员的阅读成本。
- 团队成员使用不同的编辑器,每个人都自有一套格式化代码风格,很容易因格式化代码生成多余的改动,从而产生冲突,也不利于解决。
- 代码提交到git上,由于格式化带来的改动,反而不利于查看这次提交实际改动了哪些代码。
eslint解决了什么
- 不要使用编辑器自带的格式化,用在项目中配置的eslint的规则去格式化代码,团队使用一致的编程风格,会让代码更容易阅读。
- 设置每次保存的时候就格式化代码,不用每次都手动输入空格或者分号来保持编程风格,也不用每次都手动格式化代码。
- 每次提交代码,不会因为代码格式化问题产生多余的改动,干净的提交,便于以后查看提交记录解决问题。
经过一年多使用eslint的切身体会,我确实感受到eslint的好用之处,以及在团队协作中带来的好处,让你在修改别人写的代码的时候,也不会因为不统一的代码风格难受。
三、项目中的优化
下面我们将从代码的易读、性能等方面,举例说明在实际项目中我们可以怎么做来优化我们的代码。
命名
坏味道:命名采用缩写。
推荐:选择具体有实际意义的词,不要用别人看不懂的缩写。
函数
坏味道:函数参数太多,命名模糊。
推荐:函数参数不要超过2个,超过的话可以考虑用对象传参,获取参数的时候用解构赋值。
坏味道:函数过大,难于阅读。
推荐:一个函数只做一件事原则,把代码片段提取出来放入一个独立的函数,这样的好处是函数名也起到了注释的效果,有助于阅读代码,提取出来的函数有助于代码复用。
值的不变性
重新赋值
var/let声明的值可被重新赋值,var可能引起变量提升,带来隐藏的问题。
let x = 1;
x = 2;
const声明的值不可被重新赋值,但如果声明的变量是引用类型,值的内部仍然可被改变
const x = 2;
// 试着改变`x`看看!
x = 3; // Error!
const x = [ 2 ];
x[0] = 3; // 可以改变
x = [1]; // Error!
推荐:程序中尽量避免var的使用,因为var会带来作用域的问题;用const声明不会被改变的变量。
值的冻结
Object.freeze:将一个可变的对象/数组/函数转换成一个“不可变值”,但只针对最顶层。
var x = Object.freeze( [ 2, 3, [4, 5] ] );
// 不允许:
x[0] = 42;
// 允许:
x[2][0] = 42;
Object.freeze(..)提供了浅的、单一的不变性,如果您想要一个非常不可变的值,就必须手动遍历整个对象/数组结构,并对每个子对象/数组应用Object.freeze(..)。
推荐:不会被修改的对象/数组用Object.freeze“冻结”起来。
for循环
for循环中的代码会在每次循环的时候都会执行,不要在循环里写无意义的代码。
推荐:
- 将一些操作放在循环体外面
- 如果要提前进入下一次循环,用continue
- 如果要提前退出循环,用break
多个弹窗的情况
我们页面中有多个弹窗,需要用变量控制弹窗的显示隐藏
坏味道:一个变量控制一个弹窗的显示隐藏,变量数很多,容易混淆哪个变量控制哪个弹窗
推荐:通过给变量赋值,判断变量是否与值相等来控制弹窗显示隐藏
合理使用数据结构
// 坏味道
data() {
return {
total: 0,
pageSize: 12,
currentPage: 1,
name: '',
}
}
// 推荐
data() {
return {
queryInfo: {
total: 0,
pageSize: 12,
currentPage: 1,
name: '',
},
}
}
坏味道:数据过于扁平,变量很多
推荐:把相关功能的几个变量封装在一个对象里面,对象名起到注释作用,说明这些变量的作用
在调后端接口获取数据传参的时候,如果对象的属性和我们要传给接口的参数一直,那么传参的时候只需要传一个对象就可以了,不用再写一遍。
适当复用代码
DRY(dont repeat yourself)原则。
1.常用工具方法
坏味道:在组件里面用到什么方法复制粘贴一遍
推荐:像格式化时间、判断浏览器类型、防抖节流等等,整理出来放在一个文件里面,导出方法,在用到的地方导入
2.使用mixins
坏味道:在组件把类似功能的代码复制粘贴一遍
推荐:类似的功能提取出来放在mixins里面,同时也要考虑复用性。比如table相关的变量和方法
条件判断
多重条件,即代码逻辑中有很多条件判断语句,比如if-else分支。
1.使用布尔值的快捷方式
// 坏味道
if (isValid === true) {}
if (arr.length === 0) {}
// 推荐
if (isValid) {}
if (!arr.length) {}
2.合理使用三元语句
简单的两重条件判断可以用三元表达式代替:
// 坏味道
if (a === b) {
res = true;
} else {
res = false;
}
// 推荐
res = a === b ? true : false;
同时也要避免不必要的三元表达式:
// 坏味道
const res = a ? a : b;
// 推荐
const res = a || b;
3.把复杂的条件分支语句提炼成函数
// 坏味道
var getPrice = function (price) {
var date = new Date();
if (date.getMonth() >= 6 && date.getMonth() <= 9) {
// 夏天
return price * 0.8;
}
return price;
};
// 推荐
var isSummer = function () {
var date = new Date();
return date.getMonth() >= 6 && date.getMonth() <= 9;
};
var getPrice = function (price) {
if (isSummer()) {
// 夏天
return price * 0.8;
}
return price;
};
4.避免条件判断的重复过程
// 惰性加载函数
// 坏味道
var addEvent = function (elem, type, handler) {
if (window.addEventListener) {
return elem.addEventListener(type, handler, false);
}
if (window.attachEvent) {
return elem.attachEvent("on" + type, handler);
}
};
// 推荐
var addEvent = function (ele, type, handler) {
if (window.addEventListener) {
addEvent = function (ele, type, handler) {
ele.addEventListener(ele, type, handler, false);
};
} else if (window.attachEvent) {
addEvent = function (ele, type, handler) {
ele.addEventListener(ele, type, handler);
};
}
addEvent(ele, type, handler);
};
5.巧用return
1.提前return
// 坏味道
var del = function (obj) {
var ret;
if (!obj.isReadOnly) {
// 不为只读的才能被删除
if (obj.isFolder) {
// 如果是文件夹
ret = deleteFolder(obj);
} else if (obj.isFile) {
// 如果是文件
ret = deleteFile(obj);
}
}
return ret;
};
// 推荐
var del = function (obj) {
if (obj.isReadOnly) {
// 反转 if 表达式
return;
}
if (obj.isFolder) {
return deleteFolder(obj);
}
if (obj.isFile) {
return deleteFile(obj);
}
};
优点:
- 更少的嵌套,看起来更简洁
- 程序不用再往下执行
缺点:提前返回导致函数有多个输出,可能难以读取函数以了解其输出行为,在流控制结构(if逻辑等等)中,会导致代码可读性更差。
现在大多数文章都在推崇尽可能早的return,但我们也不应该忽视它可能会带来的问题,所以在实际项目中,我们也要合理选择在什么时候return。
2.用return退出多重循环
// 坏味道
var func = function () {
var flag = false;
for (var i = 0; i < 10; i++) {
for (var j = 0; j < 10; j++) {
if (i * j > 30) {
flag = true;
break;
}
}
if (flag === true) {
break;
}
}
// do something
console.log(i);
};
// 推荐
var print = function (i) {
console.log(i);
};
var func = function () {
for (var i = 0; i < 10; i++) {
for (var j = 0; j < 10; j++) {
if (i * j > 30) {
return print(i);
}
}
}
};
6.if-else的替代写法
实际项目中的例子: 程序中大量的if-else阅读起来比较吃力,但实际我们可以有很多写法来代替。下面会举例一些场景的代码,再结合设计模式中的一些思想,说说怎么处理这些多重条件的情况更好。
6.1 策略模式
定义:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
JavaScript语言的特性,决定了我们实用策略模式很简单。
// 坏味道
function travel(type) {
if (type === "plane") {
//...
} else if (type === "train") {
//...
} else if (type === "bus") {
//...
}
}
// 推荐
function travel(type) {
const options = {
plane: () => {},
train: () => {},
bus: () => {},
};
return options[type]();
}
6.2 迭代器模式
定义:提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示,又叫遍历器。
Array.prototype.includes采用的就是迭代器的思想:
// 坏味道
if (color === "red" || color === "green" || color === "blue") {
console.log(color + "属于三原色");
}
// 推荐
if (["red", "green", "blue"].includes(color)) {
console.log(color + "属于三原色");
}
我们再看一个更复杂的文件上传例子
// 迭代器实现 上传文件
// 坏味道
var getUploadObj = function () {
try {
return new ActiveXObject("TXFTNActiveX.FTNUpload"); // IE 上传控件
} catch (e) {
if (supportFlash()) {
// supportFlash 函数未提供
var str = '<object type="application/x-shockwave-flash"></object>';
return $(str).appendTo($("body"));
} else {
var str = '<input name="file" type="file"/>'; // 表单上传
return $(str).appendTo($("body"));
}
}
};
// 推荐
var getActiveUploadObj = function () {
try {
return new ActiveXObject("EXFTNActiveX.FTNUpload");
} catch (e) {
return false;
}
};
var getFlashUploadObj = function () {
if (supportFlash()) {
var str = '<object type="application/x-shockwave-flash"></object>';
return $(str).appendTo($("body"));
}
return false;
};
var getFormUpladObj = function () {
var str = '<input name="file" type="file" class="ui-file"/>'; // 表单上传
return $(str).appendTo($("body"));
};
Function.prototype.after = function (fn) {
var self = this;
return function () {
var ret = self.apply(this, arguments);
if (!ret) {
return fn.apply(this, arguments);
}
return ret;
};
};
var uploadObj = iteratorUploadObj(
getActiveUploadObj,
getFlashUploadObj,
getFormUpladObj
);
其中迭代器可以这样去实现
// 迭代器
var iteratorUploadObj = function () {
for (var i = 0, fn; (fn = arguments[i++]); ) {
var uploadObj = fn();
if (uploadObj !== false) {
return uploadObj;
}
}
};
6.3 职责链模式
定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间 的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
我们利用after去实现职责链
Function.prototype.after = function (fn) {
var _this = this;
return function () {
var ret = _this.apply(this.arguments);
fn.apply(this, arguments);
return ret;
};
};
同样是上面那个文件上传的例子,用迭代器模式这样实现
// 职责链模式
var getActiveUploadObj = function () {
try {
return new ActiveXObject("TXFTNActiveX.FTNUpload"); // IE 上传控件
} catch (e) {
return "nextSuccessor";
}
};
var getFlashUploadObj = function () {
if (supportFlash()) {
var str = '<object type="application/x-shockwave-flash"></object>';
return $(str).appendTo($("body"));
}
return "nextSuccessor";
};
var getFormUpladObj = function () {
return $('<form><input name="file" type="file"/></form>').appendTo($("body"));
};
var getUploadObj = getActiveUploadObj
.after(getFlashUploadObj)
.after(getFormUpladObj);
console.log(getUploadObj());
6.4 状态模式
定义:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
Light.prototype.buttonWasPressed = function () {
if (this.state === "off") {
console.log("弱光");
this.state = "weakLight";
} else if (this.state === "weakLight") {
console.log("强光");
this.state = "strongLight";
} else if (this.state === "strongLight") {
console.log("关灯");
this.state = "off";
}
};
// 状态模式
var Light = function () {
this.currState = FSM.off; // 设置当前状态
this.button = null;
};
Light.prototype.press = function () {
this.currState.buttonWasPressed.call(self); // 把请求委托给 FSM 状态机
};
var FSM = {
off: {
buttonWasPressed: function () {
console.log("弱光");
this.currState = "weakLight";
},
},
weakLight: {
buttonWasPressed: function () {
console.log("强光");
this.currState = "strongLight";
},
},
strongLight: {
buttonWasPressed: function () {
console.log("关灯");
this.currState = "off";
},
},
};
var light = new Light();
light.press();
四、团队需要完善的地方
- 团队编码规范
- eslint统一规范
- 定期code review
五、参考
一名合格前端工程师必备素质:代码整洁之道
你所需要知道的代码整洁之道
JavaScript轻量级函数式编程
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!