代码规范文档
背景
目前前端团队开发出现技术断层显现比较严重,由于不同开发人员承担不同的产品线业务,加上长期没有进行技术评审和代码评审,导致开发人员的代码风格差别很大,长期以往会出现代码可读性差的问题,这无疑会增加其他维护人员的开发成本,导致整个团队开发低效;
再有如果长期封闭解决问题,好的经验得不到沉淀,一些问题也不能及时暴露,对于个人的成长和团队发展也都是一种阻碍。
目的
- 打造可扩展性高的团队项目
开发人员
- 提升开发人员的规范意识和能力
规范化开发,也是以团队的力量更好的帮助开发人员更为高效,更为有质量地进行开发
- 提升前端的
基础代码规范
基础代码规范目前已经有很多成型的工具可以帮助开发人员在开发时培养
基本的规范习惯,针对目前的项目基本代码规范的实施分为两种方式
- 利用规范工具
针对于新增的小型项目,目前构建工具中已经添加eshint,针对新开发的项
目可以利用工具,在开发过程中随时进行代码检查;
- 规范建议
已经存在的项目,例如godman,如果利用工具检查代码规范之后修改工作
量会很大,针对这种情况,需要继续参与开发的同学通过参考基础代码规范建议来进行开发;因为开发时间有限,建议部分可以理解为是工具检查中的子集,只建议比较重要规范问题;
基础规范建议
基础规范包括h5规范,css规范和js规范; js规范中还包括了es6规范;
CSS规范
- 选择器
【必选】不要在文件中使用内联式引入样式,不管是定义在样式便签里还是直接定义在元素上,这样会很难追踪样式规则;除非必须要用的场景,如进度条进度控制;
示例:<p style="color: red">
解释:这样会很难追踪样式规则,样式很多的情况下,不方便统一管理
【建议】只出现一次的元素用id,其他的用class;
解释:使用id的选择器的性能是最高的,所以如果有单独的元素,尽量用id;该建议主要使用与多页系统;
【建议】尽量指向明确,不要用标签选择器
示例: a { color : red }
解释: 标签的选择效率非常地,建议指向明确的id或者class元素
- 加载
【必选】杜绝使用import的方式加载css
解释:import是串行加载。用link代替
js规范
- 变量
【建议】声明包含元素的数组与对象,只有当内部元素的形式较为简单时,才允许写在一行。元素复杂的情况,还是应该换行书写。
// good
var arr1 = [];
var arr2 = [1, 2, 3];
var obj1 = {};
var obj2 = {name: 'obj'};
var obj3 = {
name: 'obj',
age: 20,
sex: 1
};
// bad
var arr1 = [ ];
var arr2 = [ 1, 2, 3 ];
var obj1 = { };
var obj2 = { name: 'obj' };
var obj3 = {name: 'obj', age: 20, sex: 1};
【建议】一个 var 声明多个变量,容易导致较长的行长度,并且在修改时容易造成逗号和分号的混淆。
// good
var hangModules = [];
var missModules = [];
var visited = {};
// bad
var hangModules = [],
missModules = [],
visited = {};
【建议】多行判断语句拆分写
if (user.isAuthenticated()
&& user.isInRole('admin')
&& user.hasAuthority('add-admin')
|| user.hasAuthority('delete-admin')
) {
// Code
}
- 命名
【建议】变量名,函数用驼峰的方式,变量名为名词,函数名为动词
var loadingModules = {};
function stringFormat(source) {
}
【建议】常量使用全字母大写,单词间下划线分隔的方式命名
var HTML_ENTITY = {};
【建议】类使用pascal命名
function TextNode(options) {
}
【必选】自定义事件的事件名必须全小写。
解释:在 JavaScript 广泛应用的浏览器环境,绝大多数 DOM 事件名称都是全小写的。为了遵循大多数 JavaScript 开发者的习惯,在设计自定义事件时,事件名也应该全小写。
- 条件
【必选】禁止使用==进行条件判断,使用===
解释:因为js为弱类型选择器,用==有隐藏的风险,如0==false
【建议】使用尽可能简单的表达式
字符串
// good
if (!name) {
}
// bad
if (name === '') {
}
数组
// good
if (collection.length) {
// ......
}
// bad
if (collection.length > 0) {
// ......
}
布尔
// good
if (!notTrue) {
// ......
}
// bad
if (notTrue === false) {
// ......
}
null或者undefined
// good
if (noValue == null) {
// ......
}
// bad
if (noValue === null || typeof noValue === 'undefined') {
// ......
}
【建议】不要在循环体中包含函数表达式,事先将函数提取到循环体外。
解释:循环体中的函数表达式,运行过程中会生成循环次数个函数对象。
// good
function clicker() {
// ......
}
for (var i = 0, len = elements.length; i < len; i++) {
var element = elements[i];
addListener(element, 'click', clicker);
}
// bad
for (var i = 0, len = elements.length; i < len; i++) {
var element = elements[i];
addListener(element, 'click', function () {});
}
建议\] 对循环内多次使用的不变值,在循环外用变量缓存。 解释:循环体中的会产生多个变量对象 // good var width = wrap.offsetWidth + 'px'; for (var i = 0, len = elements.length; i \< len; i++) { var element = elements\[i\]; element.style.width = width; // ...... } // bad for (var i = 0, len = elements.length; i \< len; i++) { var element = elements\[i\]; element.style.width = wrap.offsetWidth + 'px'; // ...... } \[建议\] 对有序集合进行遍历时,缓存 length。 解释:虽然现代浏览器都对数组长度进行了缓存,但对于一些宿主对象和老旧浏览器的数组对象,在每次 length 访问时会动态计算元素个数,此时缓存 length 能有效提高程序性能。 for (var i = 0, len = elements.length; i \< len; i++) { var element = elements\[i\]; // ...... } 1. 类型转化 \[建议\] 转换成 string 时,使用 + '' 解释: // good num + ''; // bad new String(num); num.toString(); String(num); \[建议\] 转换成 number 时,通常使用 + 解释: // good +str; // bad Number(str); \[建议\] string 转换成 number,要转换的字符串结尾包含非数字并期望忽略时,使用 parseInt。 var width = '200px'; parseInt(width, 10); 【建议】 转换成 boolean 时,使用 !!。 var num = 3.14; !!num; 1. 对象 【必选】声明对象时用对象字面量{}声明 解释:new Object会调用对象构造函数,初始化对象,效率较空对象低 // good var obj = {}; // bad var obj = new Object(); 【必选】不允许修改和扩展任何原生对象和宿主对象的原型。 String.prototype.trim = function () { }; 【建议】属性访问时,尽量使用 解释:通常在 JavaScript 中声明的对象,属性命名是使用 Camel 命名法,用 . 来访问更清晰简洁。部分特殊的属性(比如来自后端的JSON),可以通过 \[expr\] 方式访问。 info.age; info\['more-info'\]; 【建议】for in 遍历对象时, 使用 hasOwnProperty 过滤掉原型中的属性。 var newInfo = {}; for (var key in info) { if (info.hasOwnProperty(key)) { newInfo\[key\] = info\[key\]; } } 1. 数组 【必选】使用数组字面量 \[\] 创建新数组,除非想要创建的是指定长度的数组。 解释: 效率更高 // good var arr = \[\]; // bad var arr = new Array(); 【必选】 遍历数组不使用 for in。 解释:数组对象可能存在数字以外的属性, 这种情况下 for in 不会得到正确结果. var arr = \['a', 'b', 'c'\]; arr.other = 'other things'; // good for (var i = 0, len = arr.length; i \< len; i++) { console.log(i); } // bad for (i in arr) { console.log(i); } 1. 函数 【建议】 一个函数的长度控制在 50 行以内。 解释:将过多的逻辑单元混在一个大函数中,易导致难以维护。一个清晰易懂的函数应该完成单一的逻辑单元。复杂的操作应进一步抽取,通过函数的调用来体现流程。 【建议】 一个函数的参数控制在 6 个以内。 解释:除去不定长参数以外,函数具备不同逻辑意义的参数建议控制在 6 个以内,过多参数会导致维护难度增大。某些情况下,如使用 AMD Loader 的 require 加载多个模块时,其 callback 可能会存在较多参数,因此对函数参数的个数不做强制限制。 1. 闭包 【建议】 在适当的时候将闭包内大对象置为 null。 解释:虽然现在浏览器已经优化了垃圾回收机制,但是某些复杂情况下,造成循环引用,垃圾无法回收 // good for (var i = 0, len = arr.length; i \< len; i++) { console.log(i); } // bad for (i in arr) { console.log(i); } 1. 动态特性 【必选】 避免使用直接 eval 函数。 1. DOM 【建议】操作dom的时候尽量避免reflow 解释: js最大的执行性能主要消耗在DOM操作上,所以要尽量避免这种情 况,会引起reflow的场景如下: 1. DOM元素的添加、修改(内容)、删除。 2. 应用新的样式或者修改任何影响元素布局的属性。 3. Resize浏览器窗口、滚动页面。 4. 读取元素的某些属性(offsetLeft、offsetTop、offsetHeight、offsetWidth、scrollTop/Left/Width/Height、clientTop/Left/Width/Height、getComputedStyle()、currentStyle(in IE)) 。 **E** **S6规范** 1. 命名 【建议】导出单一类时,确保你的文件名就是你的类名 // file contents class CheckBox { // ... } module.exports = CheckBox; // in some other file // bad const CheckBox = require('./checkBox'); // bad const CheckBox = require('./check_box'); // good const CheckBox = require('./CheckBox'); 【建议】导出一个默认小驼峰命名的函数时,文件名应该就是导出的方法名 function makeStyleGuide() { } export default makeStyleGuide; 1. 变量 【建议】为引用使用const 关键字,而不是var // bad var a = 1; var b = 2; // good const a = 1; const b = 2; 【建议】如果你必须修改引用,使用 let 代替 var // bad var count = 1; if (true) { count += 1; } // good, use the let. let count = 1; if (true) { count += 1; } 1. 对象 【建议】使用定义对象属性的简短形式 const lukeSkywalker = 'Luke Skywalker'; // bad const obj = { lukeSkywalker: lukeSkywalker }; // good const obj = { lukeSkywalker }; 1. 数组 【必选】使用 ... 来拷贝数组 // bad const len = items.length; const itemsCopy = \[\]; let i; for (i = 0; i \< len; i++) { itemsCopy\[i\] = items\[i\]; } // good const itemsCopy = \[...items\]; 【建议】使用 Array.from 将类数组对象转换为数组 const foo = document.querySelectorAll('.foo'); const nodes = Array.from(foo); 1. 解构赋值 【建议】访问或使用对象的多个属性时请使用对象的解构赋值 解释:解构赋值避免了为这些属性创建临时变量或对象。 // bad function getFullName(user) { const firstName = user.firstName; const lastName = user.lastName; return \`${firstName} ${lastName}\`; } // good function getFullName(obj) { const { firstName, lastName } = obj; return \`${firstName} ${lastName}\`; } // best function getFullName({ firstName, lastName }) { return \`${firstName} ${lastName}\`; } 【建议】使用数组解构赋值 const arr = \[1, 2, 3, 4\]; // bad const first = arr\[0\]; const second = arr\[1\]; // good const \[first, second\] = arr; 【建议】函数有多个返回值时使用对象解构,而不是数组解构 // bad function processInput(input) { // then a miracle occurs return \[left, right, top, bottom\]; } // the caller needs to think about the order of return data const \[left, __, top\] = processInput(input); // good function processInput(input) { // then a miracle occurs return { left, right, top, bottom }; } // the caller selects only the data they need const { left, right } = processInput(input); 1. 字符串 【建议】编程构建字符串时,使用字符串模板而不是字符串连接 解释: 模板给你一个可读的字符串,简洁的语法与适当的换行和字符串插值特性 // bad function sayHi(name) { return 'How are you, ' + name + '?'; } // bad function sayHi(name) { return \['How are you, ', name, '?'\].join(); } // good function sayHi(name) { return \`How are you, ${name}?\`; } 1. 函数 【建议】使用函数声明而不是函数表达式 解释:函数声明拥有函数名,在调用栈中更加容易识别。并且,函数声明会整体提升,而函数表达式只会提升变量本身。这条规则也可以这样描述,始终使用[箭头函数](#箭头函数)来代替函数表达式。 // bad const foo = function () { }; // good function foo() { } 【必选】永远不要使用 arguments,使用 ... 操作符来代替 解释:... 操作符可以明确指定你需要哪些参数,并且得到的是一个真实的数组,而不是 arguments 这样的类数组对象。 // bad function concatenateAll() { const args = Array.prototype.slice.call(arguments); return args.join(''); } // good function concatenateAll(...args) { return args.join(''); } 【必选】使用函数参数默认值语法,而不是修改函数的实参 // really bad function handleThings(opts) { opts = opts \|\| {}; // ... } // still bad function handleThings(opts) { if (opts === void 0) { opts = {}; } // ... } // good function handleThings(opts = {}) { // ... } 【必选】当必须使用函数表达式时(例如传递一个匿名函数时),请使用箭头函数 解释: 箭头函数提供了更简洁的语法,并且箭头函数中 this 对象的指向是不变的,this 对象绑定定义时所在的对象,这通常是我们想要的。如果该函数的逻辑非常复杂,请将该函数提取为一个函数声明。 // bad \[1, 2, 3\].map(function (x) { return x \* x; }); // good \[1, 2, 3\].map((x) =\> { return x \* x }); 1. 继承 【必选】总是使用 class 关键字,避免直接修改 prototype 解释: class 语法更简洁,也更易理解。 // bad function Queue(contents = \[\]) { this._queue = \[...contents\]; } Queue.prototype.pop = function() { const value = this._queue\[0\]; this._queue.splice(0, 1); return value; } // good class Queue { constructor(contents = \[\]) { this._queue = \[...contents\]; } pop() { const value = this._queue\[0\]; this._queue.splice(0, 1); return value; } } 1. 模块 【建议】总是在非标准的模块系统中使用标准的 import 和 export 语法,我们总是可以将标准的模块语法转换成支持特定模块加载器的语法。 // bad const AirbnbStyleGuide = require('./AirbnbStyleGuide'); module.exports = AirbnbStyleGuide.es6; // ok import AirbnbStyleGuide from './AirbnbStyleGuide'; export default AirbnbStyleGuide.es6; // best import { es6 } from './AirbnbStyleGuide'; export default es6; 【必选】不要使用通配符 \* 的 import 解释:这样确保了只有一个默认的 export 项 // bad import \* as AirbnbStyleGuide from './AirbnbStyleGuide'; // good import AirbnbStyleGuide from './AirbnbStyleGuide'; 【必选】不要直接从一个 import 上 export 解释:虽然一行代码看起来更简洁,但是有一个明确的 import 和一个明确的 export 使得代码行为更加明确。 // bad // filename es6.js export default { es6 } from './airbnbStyleGuide'; // good // filename es6.js import { es6 } from './AirbnbStyleGuide'; export default es6; **框架规范** **j** **Query** 1.变量 【建议】为 jQuery 对象命名时添加 $ 前缀 // bad const sidebar = $('.sidebar'); // good const $sidebar = $('.sidebar'); 2.选择器 【建议】尽量ID选择器。实际运用的是js的document.getElementById(),所以速度较其他选择器快。 【建议】使用类选择器时不要指定元素的类型 // bad var $products = $("div.products"); // slow // good var $products = $(".products"); // fast 【建议】对DOM查询使用级联的 $('.sidebar ul') ,在指定作用域进行查询时使用 find 解释:ID父亲容器下面查找子元素请用.find()方法。这样做快的原因是通过id选择元素不会使用Sizzle引擎 // bad $('ul', '.sidebar').hide(); // bad $('.sidebar').find('ul').hide(); // good $('.sidebar ul').hide(); // good $('.sidebar \> ul').hide(); // good $sidebar.find('ul').hide(); 【建议】多级查找中,右边尽量指定得详细点而左边则尽量简单点。 // bad $("div.data .gonzalez"); // good $(".data td.gonzalez"); 【必选】避免使用万能选择器 // bad $('div.container \> \*'); // good $('div.container').children(); 【必选】ID已经表示唯一了,背后使用的是document.getElementById(),所以不要和其他选择器混淆了。 // bad $('#outer #inner'); $('div#inner'); $('.outer-container #inner'); // good $('#inner'); 3.DOM操作 【建议】操作任何元素前先将其从文档卸载,然后再贴回去 解释:将元素从文档卸载后对其的操作不会再引起重绘等对页面性能造成较大影响的问题 var $myList = $("#list-container \> ul").detach(); //...a lot of complicated things on $myList $myList.appendTo("#list-container"); 【建议】使用连接字符串或数组join(),然后再append()。 // bad var $myList = $("#list"); for(var i = 0; i \< 10000; i++){ $myList.append("\
You {text} this. Click to toggle. \
); } }); 1. componentDidMount 【建议】在该方法内获取后端数据 解释:在该方法中DOM节点已经渲染完成,获取数据之后可以直接可以直接插入 【建议】在该方法中操作真实DOM 解释:该方法中DOM已经渲染完成,在这个方法中可以访问到,使用ref属性 1. 存在期 1. componentWillReceiveProps 组件的props通过父组件更改了,该方法会被调用; 【建议】在该方法中更新state,触发组件的重新渲染 解释: 1. shouldComponentUpdate 如果确定props或者state的改变不需要重新渲染,可以通过在这个方法里返回false在阻止掉后面流程; 【建议】尽量在每个组件中实现该方法 解释:该方法可以灵活的控制该组件是否要被重新渲染,在一般的react项目中,经常出现的场景是父组件的重新渲染会导致子组件的重新渲染,当组件嵌套关系复杂的时候,可能要执行多次无用的render,这对于性能的消耗很大; shouldComponentUpdate: function(nextProps, nextState){ return this.state.checked === nextState.checked; //return false 则不更新组件 } 【必选】不要在该方法内调用setState 解释: 会引起循环引用 1. componentWillUpdate 这个方法和 componentWillMount 类似,在组件接收到了新的 props 或 者 state 即将进行重新渲染前,componentWillUpdate(object nextProps, object nextState) 会被调用; 【必选】不要在此方面里再去更新 props 或者 state。 解释:同理render,在该方法中更新Props和state可能会引起死循环; 1. render 2. componentDidUpdate 这个方法和 componentDidMount 类似,在组件重新被渲染之后, componentDidUpdate(object prevProps, object prevState) 会被调用。不过 componentDidMount只在实例化之后被调用一次,而componentDidUpdate会在每次render之后被调用;可以在这里访问并修改 DOM。 **react组件抽取规范** 利用mvvm框架编程的最大特点就是组件化,合理的抽取组件可以大大提升开发效率;那么如何能够合理的抽取组件,首先需要明白组件具有哪些特性,根据这些特性确定组件,然庵后抽取 **组件特性** 1. 页面组件和通用组件 最常见的组件是页面的组成单位,多个组件组成一个页面, 这样的组件叫做页面 组件;一般的页面组件更和具体业务挂钩,所以也叫业务组件; 不属于页面组成部分,但是被多个页面使用的基础组件叫做通用组件,如弹窗,输入框等;这部分组件也被叫做是基础组件; 1. 组件具有复用性 组件可以被多次使用,如果一个组件只被一个页面所用则不能称之为组件 1. 组件和组件之间是独立的 抽取的组件是保持各个组件之间是独立的,如果两个组件之间有频繁的联动则抽取为一个组件; **组件抽取原则** 组件的抽取要遵循组件的特性,好的组件应该符合以下原则 1. 复用性较高 一般的,组件的复用次数如果达到2次以上,即可抽取为组件 1. 抽取的组件不可太复杂 不同的组件承担职责不同,有的基础组件,如错误弹窗就比较简单,只需要接受输 入展示组件即可,但是一些业务组件由于业务关系会变得很复杂,这时候要对业务组件进行拆分 1. 组件输入要可控明确 好的组件其输入,如props是可控的,输入的个数一般控制在10个以内,如果有 太多的输入,则要考虑是否要分拆组件; 1. 组件之间是相互独立的 所谓组件独立是指组件自己处理自己逻辑,自己请求自己的数据,尽管有父组件子 组件的关系,但是父组件不会影响子组件的逻辑;但是父组件的重新渲染会导致子组件重新渲染;并且父组件和子组件之间不要有太复杂的数据交互,如很复杂的props,或者是子组件要调用父组件的回调方法多次,这种情况考虑合为一个组件 1. 通用组件的抽取不以页面为单位 通用组件不是属于任何一个页面的,虽然是可以被多次调用,但是却不是页面的组 成部分,通用组件要做到尽可能的输出简单;业务组件则是以页面为单位;