前端编码规范
通过几个迭代后,前端代码变得越来越臃肿越来越乱。所以编码规范就排上日程。
结合这段时间我们的经验加上从网上搜集到的实用的编码规范,做一个简单整理。
前端编码规范意见稿
- 统一ESLint文件
- 比如用Airbnb,随着积累可以在其基础上进行扩展。
- React组件
- 如果组件需要维护自己的state或者使用其生命周期方法则用class,除此以外都用function。
- Redux
- 除了纯渲染组件(没有复杂的交互、逻辑),其余都用redux
redux使代码结构更加清晰,可读性较强便于维护(倒逼组件或者模块拆的更加合理)。 redux可当成全局内存库来用,当没有更新state时,无论何时何地都能到一样的数据,便于通用组件的开发 redux可以减少数据的传递,不用依次往下传,随用随取,特别是针对组件层级比较深的情况
- 除了纯渲染组件(没有复杂的交互、逻辑),其余都用redux
- 统一格式化插件(如果要用格式化插件)
- 比如VSCode的prettier或者beauty,千万避免多人用多套格式化插件的情况
JS开发规范
对象
- 不要使用关键字作为key或者属性
1
2
3
4
5
6
7
8
9// bad
var user = {
private: true
};
// good
var user = {
hidden: true
};
数组
如果你不知道数组的长度,使用push
1
2
3
4
5
6
7
8var userArr = [];
// bad
userArr[index] = 'gamehu';
// good
userArr.push('gamehu');当你需要拷贝数组时
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19var len = users.length,
usersCopy = [],
i;
// bad
for (i = 0; i < len; i++) {
usersCopy[i] = items[i];
}
// good
usersCopy = users.slice();
//good ES6
usersCopy = [...users];
usersCopy=Array.from(users);将类数组的对象转成数组.
1
2
3
4
5let args = [].slice.apply(users);
// ES6
let args=Array.from(users);
字符串
- 对字符串使用单引号 ‘’
1
2
3
4
5
6
7
8
9
10
11// bad
var name = "Gamehu";
// good
var name = 'Gamehu';
// bad
var fullName = "Gamehu " + this.lastName;
// good
var fullName = 'Gamehu ' + this.lastName;
函数
绝对不要把参数命名为 arguments, 将覆盖作用域内传过来的 arguments 对象.
1
2
3
4
5
6
7
8
9// bad
function nope(name, options, arguments) {
// ...stuff...
}
// good
function yup(name, options, args) {
// ...stuff...
}当函数的参数特别多的时候用对象封装起来再传递.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//bad
export const resourceStoreDynamicFormForEdit = (group, data, index, form, editableIndex, sortItems, formItemLayout, validatorMap, dataMap, ciType, selectedCiEditable, showAlarmSlowStrategy) => {}
// good
const params={};
params={
group:group, xxx
}
export const resourceStoreDynamicFormForEdit=(params) ={
}函数应该只在一个抽象层次上做一件事.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// bad
function getUserRouteHandler (req, res) {
const { userId } = req.params
// inline SQL query
knex('user')
.where({ id: userId })
.first()
.then((user) => res.json(user))
}
// good
// User model (eg. models/user.js)
const tableName = 'user'
const User = {
getOne (userId) {
return knex(tableName)
.where({ id: userId })
.first()
}
}
// route handler (eg. server/routes/user/get.js)
function getUserRouteHandler (req, res) {
const { userId } = req.params
User.getOne(userId)
.then((user) => res.json(user))
}更高层次的函数在低层次函数的前面,便于阅读.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// bad
// "I need the full name for something..."
function getFullName (user) {
return `${user.firstName} ${user.lastName}`
}
function renderEmailTemplate (user) {
// "oh, here"
const fullName = getFullName(user)
return `Dear ${fullName}, ...`
}
// good
function renderEmailTemplate (user) {
// "I need the full name of the user"
const fullName = getFullName(user)
return `Dear ${fullName}, ...`
}
// "I use this for the email template rendering"
function getFullName (user) {
return `${user.firstName} ${user.lastName}`
}声明函数时最好设置默认值.
1
2
3//es6
function (a=1, b=1) { // function code }如果想避免var变量造成的命名冲突,不存在特殊场景时可考虑使用立即执行函数
1
2
3
4
5
6
7
8
9
10
11立即执行函数,即Immediately Invoked Function Expression (IIFE),正如它的名字,
就是创建函数的同时立即执行。它没有绑定任何事件,也无需等待任何异步操作:
(function() {
// 代码
// ...
})();
function(){…}是一个匿名函数,包围它的一对括号将其转换为一个表达式,
紧跟其后的一对括号调用了这个函数。立即执行函数也可以理解为立即调用一个匿名函数。
立即执行函数最常见的应用场景就是:将var变量的作用域限制于你们函数内,这样可以避免命名冲突。当有场景需要使用私有属性时,使用闭包定义私有变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21function Product() {
var name;
this.setName = function(value) {
name = value;
};
this.getName = function() {
return name;
};
}
var p = new Product();
p.setName("Fundebug");
console.log(p.name); // 输出undefined
console.log(p.getName()); // 输出Fundebug
代码中,对象p的的name属性为私有属性,使用p.name不能直接访问。
变量
总是使用 let、const、var来声明变量,如果不这么做将导致产生全局变量,我们要避免污染全局命名空间。
1
2
3
4
5// bad
superPower = new SuperPower();
// good
var superPower = new SuperPower();使用一个 let 以及新行声明多个变量,缩进4个空格。
1
2
3
4
5
6
7
8
9// bad
let items = getItems();
let goSportsTeam = true;
let dragonball = 'z';
// good
let items = getItems(),
goSportsTeam = true,
dragonball = 'z';最后再声明未赋值的变量,当你想引用之前已赋值变量的时候很有用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14// bad
var i, len,hidden = true,
items = getItems();
// bad
var i, items = getItems(),
hidden = true,
len;
// good
var items = getItems(),
hidden = true,
length,
i;在作用域顶部声明变量,避免变量声明和赋值引起的相关问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53// bad
function() {
test();
console.log('doing stuff..');
//..other stuff..
var name = getName();
if (name === 'test') {
return false;
}
return name;
}
// good
function() {
var name = getName();
test();
console.log('doing stuff..');
//..other stuff..
if (name === 'test') {
return false;
}
return name;
}
// bad
function() {
var name = getName();
if (!arguments.length) {
return false;
}
return true;
}
// good
function() {
if (!arguments.length) {
return false;
}
var name = getName();
return true;
}在声明变量时初始化变量。
1
2
3
4
5
6
7
8var firstName = "",
lastName = "",
price = 0,
discount = 0,
fullPrice = 0,
myArray = [],
myObject = {};在声明变量时别用对象。
1
2
3
4
5
6
7
8Use {} instead of new Object()
Use "" instead of new String()
Use 0 instead of new Number()
Use false instead of new Boolean()
Use [] instead of new Array()
Use /()/ instead of new RegExp()
Use function (){} instead of new Function()用===代替==,因为==会在比较之前进行类型转换。
1
2
3
4
5
6
7
80 == ""; // true
1 == "1"; // true
1 == true; // true
0 === ""; // false
1 === "1"; // false
1 === true; // false注意数字和字符串之间的转换。
1
2
3
4
5
6
7
8var x = 5 + 7; // x.valueOf() is 12, typeof x is a number
var x = 5 + "7"; // x.valueOf() is 57, typeof x is a string
var x = "5" + 7; // x.valueOf() is 57, typeof x is a string
var x = 5 - 7; // x.valueOf() is -2, typeof x is a number
var x = 5 - "7"; // x.valueOf() is -2, typeof x is a number
var x = "5" - 7; // x.valueOf() is -2, typeof x is a number
var x = 5 - "x"; // x.valueOf() is NaN, typeof x is a number在for循环的每次迭代中都不要让JavaScript读取数组的长度。 将长度值存储在另一个变量中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18//bad
var names = ['George',
'Ringo',
'Paul',
'John'];
for(var i=0;i<names.length;i++){
doSomethingWith(names[i]);
}
//good
var names = ['George',
'Ringo',
'Paul',
'John'];
for(var i=0,j=names.length;i<j;i++){
doSomethingWith(names[i]);
}
条件表达式和等号
合理使用 === 和 !== 以及 == 和 !=.
合理使用表达式逻辑操作运算
条件表达式的强制类型转换遵循以下规则:
switch时一定要用default结束
1
2
3
4
5
6
71: 对象 被计算为 true
2: Undefined 被计算为 false
3: Null 被计算为 false
4: 布尔值 被计算为 布尔的值
5: 数字 如果是 +0, -0, or NaN 被计算为 false , 否则为 true
6: 字符串 如果是空字符串 '' 则被计算为 false, 否则为 true使用快捷方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43// bad
if (name !== '') {
// ...stuff...
}
// good
if (name) {
// ...stuff...
}
// bad
if (collection.length > 0) {
// ...stuff...
}
// good
if (collection.length) {
// ...stuff...
}
//bad
if(v){
var x = v;
} else {
var x =10;
}
//good
var x = v || 10;
//bad
var direction;
if(x > 100){
direction = 1;
} else {
direction = -1;
}
//good
var direction = (x > 100) ? 1 : -1;
块
- 给所有多行的块使用大括号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// bad
if (test)
return false;
// good
if (test) return false;
// good
if (test) {
return false;
}
// bad
function() { return false; }
// good
function() {
return false;
}
注释
使用 /** … */ 进行多行注释,包括描述,指定类型以及参数值和返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27// bad
// make() returns a new element
// based on the passed in tag name
//
// @param <String> tag
// @return <Element> element
function make(tag) {
// ...stuff...
return element;
}
// good
/**
* make() returns a new element
* based on the passed in tag name
*
* @param <String> tag
* @return <Element> element
*/
function make(tag) {
// ...stuff...
return element;
}使用 // 进行单行注释,在评论对象的上面进行单行注释,注释前放一个空行.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25// bad
var active = true; // is current tab
// good
// is current tab
var active = true;
// bad
function getType() {
console.log('fetching type...');
// set the default type to 'no type'
var type = this._type || 'no type';
return type;
}
// good
function getType() {
console.log('fetching type...');
// set the default type to 'no type'
var type = this._type || 'no type';
return type;
}如果你有一个问题需要重新来看一下或如果你建议一个需要被实现的解决方法的话需要在你的注释前面加上 FIXME 或 TODO 帮助其他人迅速理解
1
2
3
4
5
6
7function Calculator() {
// FIXME: shouldn't use a global here
total = 0;
return this;
}1
2
3
4
5
6
7function Calculator() {
// TODO: total should be configurable by an options param
this.total = 0;
return this;
}
空白
缩进、格式化能帮助团队更快得定位修复代码BUG.
将tab设为4个空格
1
2
3
4
5
6
7
8
9
10
11
12
13
14// bad
function() {
∙∙var name;
}
// bad
function() {
∙var name;
}
// good
function() {
∙∙∙∙var name;
}大括号前放一个空格
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23{
// bad
function test(){
console.log('test');
}
// good
function test() {
console.log('test');
}
// bad
dog.set('attr',{
age: '1 year',
breed: 'Bernese Mountain Dog'
});
// good
dog.set('attr', {
age: '1 year',
breed: 'Bernese Mountain Dog'
});
}在做长方法链时使用缩进.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26// bad
$('#items').find('.selected').highlight().end().find('.open').updateCount();
// good
$('#items')
.find('.selected')
.highlight()
.end()
.find('.open')
.updateCount();
// bad
var leds = stage.selectAll('.led').data(data).enter().append('svg:svg').class('led', true)
.attr('width', (radius + margin) * 2).append('svg:g')
.attr('transform', 'translate(' + (radius + margin) + ',' + (radius + margin) + ')')
.call(tron.led);
// good
var leds = stage.selectAll('.led')
.data(data)
.enter().append('svg:svg')
.class('led', true)
.attr('width', (radius + margin) * 2)
.append('svg:g')
.attr('transform', 'translate(' + (radius + margin) + ',' + (radius + margin) + ')')
.call(tron.led);
逗号
不要将逗号放前面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25// bad
var once
, upon
, aTime;
// good
var once,
upon,
aTime;
// bad
var hero = {
firstName: 'Bob'
, lastName: 'Parr'
, heroName: 'Mr. Incredible'
, superPower: 'strength'
};
// good
var hero = {
firstName: 'Bob',
lastName: 'Parr',
heroName: 'Mr. Incredible',
superPower: 'strength'
};不要加多余的逗号,这可能会在IE下引起错误,同时如果多一个逗号某些ES3的实现会计算多数组的长度。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// bad
var hero = {
firstName: 'Kevin',
lastName: 'Flynn',
};
var heroes = [
'Batman',
'Superman',
];
// good
var hero = {
firstName: 'Kevin',
lastName: 'Flynn'
};
var heroes = [
'Batman',
'Superman'
];
分号
- 语句结束一定要加分号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// bad
(function() {
var name = 'Skywalker'
return name
})()
// good
(function() {
var name = 'Skywalker';
return name;
})();
// good
;(function() {
var name = 'Skywalker';
return name;
})();
类型转换
在语句的开始执行类型转换.
字符串
1
2
3
4
5
6
7
8
9
10
11
12
13// => this.reviewScore = 9;
// bad
var totalScore = this.reviewScore + '';
// good
var totalScore = '' + this.reviewScore;
// bad
var totalScore = '' + this.reviewScore + ' total score';
// good
var totalScore = this.reviewScore + ' total score';对数字使用 parseInt 并且总是带上类型转换的基数.,如parseInt(value, 10)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27var inputValue = '4';
// bad
var val = new Number(inputValue);
// bad
var val = +inputValue;
// bad
var val = inputValue >> 0;
// bad
var val = parseInt(inputValue);
// good
var val = Number(inputValue);
// good
var val = parseInt(inputValue, 10);
// good
/**
* parseInt was the reason my code was slow.
* Bitshifting the String to coerce it to a
* Number made it a lot faster.
*/
var val = inputValue >> 0;布尔值
1
2
3
4
5
6
7
8
9
10var age = 0;
// bad
var hasAge = new Boolean(age);
// good
var hasAge = Boolean(age);
// good
var hasAge = !!age;
命名约定
避免单个字符名,让你的变量名有描述意义。
1
2
3
4
5
6
7
8
9// bad
function q() {
// ...stuff...
}
// good
function query() {
// ..stuff..
}当命名对象、函数和实例时使用驼峰命名规则
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// bad
var OBJEcttsssss = {};
var this_is_my_object = {};
var this-is-my-object = {};
function c() {};
var u = new user({
name: 'Bob Parr'
});
// good
var thisIsMyObject = {};
function thisIsMyFunction() {};
var user = new User({
name: 'Bob Parr'
});当命名构造函数或类时使用驼峰式大写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// bad
function user(options) {
this.name = options.name;
}
var bad = new user({
name: 'nope'
});
// good
function User(options) {
this.name = options.name;
}
var good = new User({
name: 'yup'
});命名私有属性时前面加个下划线 _
1
2
3
4
5
6// bad
this.__firstName__ = 'Panda';
this.firstName_ = 'Panda';
// good
this._firstName = 'Panda';模块开发的常量定义必须包含“完整的模块名称“
1
2
3
4
5
6// bad
export const GET_ASSET_LIST = 'GET_ASSET_LIST';
// good
export const GET_ASSET_LIST = 'ALARM_GET_ASSET_LIST';望名知意,建议驼峰命名(函数也适用)。
1
2
3
4
5
6
7
8
9
10
11
12
// bad
let fItem;
// good
let formItem;
//bad
let yUnit = unitObj.unit;
//good
let yAxisUnit = unitObj.unit;
存取器
- 属性的存取器函数不是必需的
- 如果你确实有存取器函数的话使用getVal() 和 setVal(‘hello’),java getter、setter风格或者jQuery风格
- 如果属性是布尔值,使用isVal() 或 hasVal()
1
2
3
4
5
6
7
8
9// bad
if (!dragon.age()) {
return false;
}
// good
if (!dragon.hasAge()) {
return false;
} - 可以创建get()和set()函数,但是要保持一致
1
2
3
4
5
6
7
8
9
10
11
12
13function Jedi(options) {
options || (options = {});
var lightsaber = options.lightsaber || 'blue';
this.set('lightsaber', lightsaber);
}
Jedi.prototype.set = function(key, val) {
this[key] = val;
};
Jedi.prototype.get = function(key) {
return this[key];
};
构造器
给对象原型分配方法,而不是用一个新的对象覆盖原型,覆盖原型会使继承出现问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23function Jedi() {
console.log('new jedi');
}
// bad
Jedi.prototype = {
fight: function fight() {
console.log('fighting');
},
block: function block() {
console.log('blocking');
}
};
// good
Jedi.prototype.fight = function fight() {
console.log('fighting');
};
Jedi.prototype.block = function block() {
console.log('blocking');
};方法可以返回 this 帮助方法可链。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29// bad
Jedi.prototype.jump = function() {
this.jumping = true;
return true;
};
Jedi.prototype.setHeight = function(height) {
this.height = height;
};
var luke = new Jedi();
luke.jump(); // => true
luke.setHeight(20) // => undefined
// good
Jedi.prototype.jump = function() {
this.jumping = true;
return this;
};
Jedi.prototype.setHeight = function(height) {
this.height = height;
return this;
};
var luke = new Jedi();
luke.jump()
.setHeight(20);可以写一个自定义的toString()方法,但是确保它工作正常并且不会有副作用。
1
2
3
4
5
6
7
8
9
10
11
12function Jedi(options) {
options || (options = {});
this.name = options.name || 'no name';
}
Jedi.prototype.getName = function getName() {
return this.name;
};
Jedi.prototype.toString = function toString() {
return 'Jedi - ' + this.getName();
};
事件
- 当给事件附加数据时,传入一个哈希而不是原始值,这可以让后面维护时加入更多数据到事件数据里而不用找出并更新那个事件的事件处理器
1
2
3
4
5
6
7
8// bad
$(this).trigger('listingUpdated', listing.id);
...
$(this).on('listingUpdated', function(e, listingId) {
// do something with listingId
}); - 更好:
1
2
3
4
5
6
7
8
9// good
$(this).trigger('listingUpdated', { listingId : listing.id });
...
$(this).on('listingUpdated', function(e, data) {
// do something with data.listingId
});
模块
- 文件应该以驼峰命名,并在同名文件夹下,同时导出的时候名字一致