代码规范
为什么需要规范?
因为软件工程化需要协同开发,我们不得不接触其他开发者编写的代码,如果不注重编程规范,随意命名变量,代码毫无章法,阅读起来晦涩难懂,那么我们维护一个大型项目的难度势必登天。
选择代码风格
一种经过考虑,被大众接受的代码无疑会使代码阅读起来更熟悉、清爽。这里推荐ESLint进行代码规范和团队分享,你可以根据项目实际需求,选用一种风格或是进行自定义,并可设置一键代码格式化。
声明
- 命名应一目了然
- 统一遵循驼峰命名或连字符命名法
- 形参简写,减少无意义的说明,复数加
s - 使用
$作为存储jQuery对象的变量名前缀 - 类和组件首字母大写,常量全大写,文件名全小写
- 在当前作用域最顶部声明变量
- 由于
js声明提升的特性,变量不应在声明前使用 - 使用字面量创建引用对象,尽可能的使用
let及const进行声明 - 为了直观的确定变量类型,最好在声明时给变量赋一个默认值
- 由于
- 若变量或方法绑定在全局对象
window下,需要显式调用
// bad
var a, b;
// good
let num;
let obj;
// best
let num = 0;
const obj = {}; // 如果不希望对象的引用被修改
// bad
var car = {
userName: 'tom',
carColor: 'red'
};
// good
const car = {
user: 'tom',
color: 'red', // 换行保留逗号,提交记录更清爽
};
let arr = new Array(); // bad
let arr = []; // good
const ul = $('.sidebar ul'); // bad
const $ul = $('.sidebar > ul'); // good
window.myFuc = function(){};
myFuc(); // bad
window.myFuc(); // good
代码块
- 尽早
return,减少代码嵌套 - 为了使逻辑更清晰,应在逻辑语句外声明函数
// bad
function logic(bool) {
if (bool) {
something...
return;
} else {
someElse...
}
}
// good
function logic(bool) {
if (bool) {
return doSomething()
}
doSomeElse()
}
function doSomething(){}
function doSomeElse(){}
逻辑判断
大量的逻辑判断往往产出臃肿的代码,让我们更优雅地处理逻辑。
- 由于
js弱语言类型的特性- 除了
undefined、null、0、NaN、''、false,抽象方法ToBoolean会将其判断为true - 使用简写,优先使用
===和!==,在某些情况下逻辑判断会发生类型转换,==和!=是不严谨的
- 除了
- 巧用运算符
&判断奇偶: 0001 & 0011 => 0001 (两位都是1才是1)|数字取整: 0001 | 0011 => 0011 (两位都是0才是0)<<左移求幂: 1 << 2 => 4
if (name !== '') // bad
if (name) // good
if (list.length > 0) // bad
if (list.length) // good
if (str == '2') // bad
if (str === '2') // good
if ('red' === color || 'blue' === color || 'pink' === color) // bad
if (['red', 'blue', 'pink'].includes(color)) // good
if (num % 2) // 奇数
if (num & 1) // 奇数
// 注意
if (true == []); // => false
if (true == ![]); // => false
我们知道,在js中利用短路与&&,短路或||求值很特别,举例来说:
let a1 = 0 && 'zero'; // 0
let a2 = '0' && 'zero'; // 'zero'
let a3 = '0' && NaN && 'zero'; // NaN
let b1 = '' || 'text'; // 'text'
let b2 = 'str' || 'text'; // 'str'
let b3 = undefined || 'text' || ''; // 'text'
let c = false && '0' || 'text' && null; // null
&&: 若每项都可转换为真,整体值为最后一个值,反之整体值为第一个假值||: 若每项都可转换为假,整体值为最后一个值,反之整体值为第一个真值- 优先级
&&比||高
举个例子,计算员工奖金:由于 tom、amy 业绩突出,分别奖励 5k,3k,其他人则为1k。
可以这样实现:
let bonus = 1000 * ('tom' === staff && 5 || 'amy' === staff && 3 || 1);
也可利用对象键值对之间的映射关系:
let bonus = 1000 * ({ tom: 5, amy: 3 }[staff] || 1);
如果需要处理更复杂的逻辑:
( { tom: tomDo, amy: amyDo }[staff] || elseDo )(); // 加()包裹防止js引擎将{}解析成代码块
function tomDo() {};
function amyDo() {};
function elseDo() {};
过滤falsy值
let res = [0, 1, '2', '', undefined, null, false, NaN].filter(Boolean); // [1,'2']
缓存
在循环中缓存中间变量,避免每次访问值顺着链条查找,语义清晰,性能更好
len = data.length 现代浏览器已经做了优化,视情况可定
// bad
for (var i = 0; i < data.length; i++) {
data[i].index = i;
data[i].type = 'object';
}
// good
for (let i = 0, len = data.length; i < len; i++) {
const el = data[i];
el.index = i;
el.type = 'object';
}
// bad
$('.sidebar').show();
$('.sidebar').css({'width': '50px'});
// good
const $sidebar = $('.sidebar');
$sidebar
.show()
.css({'width': '50px'});
我们常常对变量验证赋值:
const person = {};
if (getSex()) person.sex = getSex(); // 'man'
if (getCar()) person.car = getCar(); // { id: 'abc' }
if (getName()) person.name = getName(); // 'tom'
观察一下,这里面get方法执行了两遍,如果get里进行了大量运算,无疑是一种性能损失
你说这简单:
const person = {};
const sex = getSex();
const car = getCar();
const name = getName();
if (sex) person.sex = sex;
if (car) person.car = car;
if (name) person.name = name;
这次仅执行了一遍,但是声明了更多的变量,代码看起来更臃肿了
我们可以利用中间变量,缓存每个属性的值/址:
let temp = null;
const person = {};
if (temp = getSex()) person.sex = temp;
if (temp = getCar()) person.car = temp;
if (temp = getName()) person.name = temp;
其中temp = getSex()整体value值为运算的结果
函数
- 函数的参数个数不应超过三个,太多的参数不利于拓展重构
- 参数过多可以将功能拆分,或将其组合成一个对象, 使用解构赋值并设置默认值
- 如果调用函数时,是根据参数真假进行逻辑判断,可以利用字符串进行辅助说明
- 不考虑
this指向时尽量使用函数声明,函数表达式的变量名会被提升,但函数内容并不会
// bad
function ajax(url, data, method, success, error) {}
// good
function ajax({ url, data, method = 'GET', success, error }) {}
function getChildren(isDeep, hasBrother){}
// bad
getChildren(true, false);
// good
getChildren('isDeep', !'hasBrother');
console.log(named); // => undefined
named(); // => TypeError named is not a function
superPower(); // => ReferenceError superPower is not defined
var named = function superPower() {
};
两值交换
let a = 1;
let b = 2;
a = [b, b=a][0];
[a, b] = [b, a];
// 交换引用类型的两个属性
const obj = { c: 1, d: 2 };
const { c, d } = obj;
[obj.c, obj.d] = [d, c];
数组去重
我们有很多种方法实现这个常见需求,例如循环判断。但这种命令式的编程既繁琐,语义化也不强,下面我们将优雅地实现这个功能。
Map、filter
function unique(arr) {
const map = new Map();
return array.filter(el => map.has(el) ? false : map.set(el));
}
function unique(arr) {
return arr.filter((el, index, array) => index === array.indexOf(el))
}
reduce
const unique = arr => arr.reduce((a, c) => a.includes(c) ? a : a.concat(c), []);
Set
const unique = arr => [...new Set(arr)];
const unique = arr => Array.from(new Set(arr));
正则表达式
- 正则可以对字符串进行过滤、匹配、提取,简洁强大。暂且按下不表,举几个小例子
let price = "¥35.5/kg";
price.replace(/[^(0-9).]/ig,'') //35.5
tpl = tpl.replace(/(\r|\n)/ig, ''); // 去除换行符
数组扁平化
- 栈
stack
function flat(arr) {
let res = [];
let stack = [];
function forEachRight(arr) {
for (let i = arr.length - 1; i >= 0; i--) {
stack.push(arr[i]);
}
}
forEachRight(arr);
while (stack.length) {
let cur = stack.pop();
if (Array.isArray(cur)) forEachRight(cur);
else res.push(cur);
}
return res;
}
JSON.parse、JSON.stringify
const flat = arr => JSON.parse(`[${JSON.stringify(arr).replace(/\[|]/g, '')}]`);
reduce
const flat = arr => arr.reduce((a, c) => a.concat(Array.isArray(c) ? flat(c): c), []);
while
const flat = arr => {
while(arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr)
}
return arr
};
Generator
function* flat(arr) {
for (const a of arr) {
if (Array.isArray(a)) {
yield* flat(a)
} else {
yield a
}
}
}
[...flat(arr)];
为指定索引插入元素
Array.prototype.insertItem = (index, item) => {
const head = this.slice(0, index);
const foot = this.slice(index, this.length);
return [...head, item, ...foot];
}