title: 前端编码规范 author: Gamehu tags: - 前端 categories: - 工作 date: 2019-04-30 15:15:00 --- 通过几个迭代后,前端代码变得越来越臃肿越来越乱。所以编码规范就排上日程。 结合这段时间我们的经验加上从网上搜集到的实用的编码规范,做一个简单整理。 # 前端编码规范意见稿 # - 统一ESLint文件 - 比如用Airbnb,随着积累可以在其基础上进行扩展。 - React组件 - 如果组件需要维护自己的state或者使用其生命周期方法则用class,除此以外都用function。 - Redux - 除了纯渲染组件(没有复杂的交互、逻辑),其余都用redux > redux使代码结构更加清晰,可读性较强便于维护(倒逼组件或者模块拆的更加合理)。 > redux可当成全局内存库来用,当没有更新state时,无论何时何地都能到一样的数据,便于通用组件的开发 > redux可以减少数据的传递,不用依次往下传,随用随取,特别是针对组件层级比较深的情况 - 统一格式化插件(如果要用格式化插件) - 比如VSCode的prettier或者beauty,千万避免多人用多套格式化插件的情况 ## JS开发规范 ## ## 对象 * 不要使用关键字作为key或者属性 ``` // bad var user = { private: true }; // good var user = { hidden: true }; ``` ## 数组 * 如果你不知道数组的长度,使用push ``` var userArr = []; // bad userArr[index] = 'gamehu'; // good userArr.push('gamehu'); ``` * 当你需要拷贝数组时 ``` var 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); ``` * 将类数组的对象转成数组. ``` let args = [].slice.apply(users); // ES6 let args=Array.from(users); ``` ## 字符串 * 对字符串使用单引号 '' ``` // bad var name = "Gamehu"; // good var name = 'Gamehu'; // bad var fullName = "Gamehu " + this.lastName; // good var fullName = 'Gamehu ' + this.lastName; ``` ## 函数 * 绝对不要把参数命名为 arguments, 将覆盖作用域内传过来的 arguments 对象. ``` // bad function nope(name, options, arguments) { // ...stuff... } // good function yup(name, options, args) { // ...stuff... } ``` * 当函数的参数特别多的时候用对象封装起来再传递. ``` //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) ={ } ``` * 函数应该只在一个抽象层次上做一件事. ``` // 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)) } ``` * 更高层次的函数在低层次函数的前面,便于阅读. ``` // 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}` } ``` * 声明函数时最好设置默认值. ``` //es6 function (a=1, b=1) { // function code } ``` * 如果想避免var变量造成的命名冲突,不存在特殊场景时可考虑使用立即执行函数 ``` 立即执行函数,即Immediately Invoked Function Expression (IIFE),正如它的名字, 就是创建函数的同时立即执行。它没有绑定任何事件,也无需等待任何异步操作: (function() { // 代码 // ... })(); function(){…}是一个匿名函数,包围它的一对括号将其转换为一个表达式, 紧跟其后的一对括号调用了这个函数。立即执行函数也可以理解为立即调用一个匿名函数。 立即执行函数最常见的应用场景就是:将var变量的作用域限制于你们函数内,这样可以避免命名冲突。 ``` * 当有场景需要使用私有属性时,使用闭包定义私有变量 ``` function 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来声明变量,如果不这么做将导致产生全局变量,我们要避免污染全局命名空间。 ``` // bad superPower = new SuperPower(); // good var superPower = new SuperPower(); ``` * 使用一个 let 以及新行声明多个变量,缩进4个空格。 ``` // bad let items = getItems(); let goSportsTeam = true; let dragonball = 'z'; // good let items = getItems(), goSportsTeam = true, dragonball = 'z'; ``` * 最后再声明未赋值的变量,当你想引用之前已赋值变量的时候很有用。 ``` // bad var i, len,hidden = true, items = getItems(); // bad var i, items = getItems(), hidden = true, len; // good var items = getItems(), hidden = true, length, i; ``` * 在作用域顶部声明变量,避免变量声明和赋值引起的相关问题。 ``` // 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; } ``` * 在声明变量时初始化变量。 ``` var firstName = "", lastName = "", price = 0, discount = 0, fullPrice = 0, myArray = [], myObject = {}; ``` * 在声明变量时别用对象。 ``` Use {} 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() ``` * 用===代替==,因为==会在比较之前进行类型转换。 ``` 0 == ""; // true 1 == "1"; // true 1 == true; // true 0 === ""; // false 1 === "1"; // false 1 === true; // false ``` * 注意数字和字符串之间的转换。 ``` var 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读取数组的长度。 将长度值存储在另一个变量中。 ``` //bad var names = ['George', 'Ringo', 'Paul', 'John']; for(var i=0;i 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; ``` ## 块 * 给所有多行的块使用大括号 ``` // bad if (test) return false; // good if (test) return false; // good if (test) { return false; } // bad function() { return false; } // good function() { return false; } ``` ## 注释 * 使用 /** ... */ 进行多行注释,包括描述,指定类型以及参数值和返回值 ``` // bad // make() returns a new element // based on the passed in tag name // // @param tag // @return element function make(tag) { // ...stuff... return element; } // good /** * make() returns a new element * based on the passed in tag name * * @param tag * @return element */ function make(tag) { // ...stuff... return element; } ``` * 使用 // 进行单行注释,在评论对象的上面进行单行注释,注释前放一个空行. ``` // 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 帮助其他人迅速理解 ``` function Calculator() { // FIXME: shouldn't use a global here total = 0; return this; } ``` ``` function Calculator() { // TODO: total should be configurable by an options param this.total = 0; return this; } ``` ## 空白 * 缩进、格式化能帮助团队更快得定位修复代码BUG. * 将tab设为4个空格 ``` // bad function() { ∙∙var name; } // bad function() { ∙var name; } // good function() { ∙∙∙∙var name; } ``` * 大括号前放一个空格 ``` { // 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' }); } ``` * 在做长方法链时使用缩进. ``` // 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); ``` ## 逗号 * 不要将逗号放前面 ``` // 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的实现会计算多数组的长度。 ``` // bad var hero = { firstName: 'Kevin', lastName: 'Flynn', }; var heroes = [ 'Batman', 'Superman', ]; // good var hero = { firstName: 'Kevin', lastName: 'Flynn' }; var heroes = [ 'Batman', 'Superman' ]; ``` ## 分号 * 语句结束一定要加分号 ``` // bad (function() { var name = 'Skywalker' return name })() // good (function() { var name = 'Skywalker'; return name; })(); // good ;(function() { var name = 'Skywalker'; return name; })(); ``` ## 类型转换 * 在语句的开始执行类型转换. * 字符串 ``` // => 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) ``` var 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; ``` * 布尔值 ``` var age = 0; // bad var hasAge = new Boolean(age); // good var hasAge = Boolean(age); // good var hasAge = !!age; ``` ## 命名约定 * 避免单个字符名,让你的变量名有描述意义。 ``` // bad function q() { // ...stuff... } // good function query() { // ..stuff.. } ``` * 当命名对象、函数和实例时使用驼峰命名规则 ``` // 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' }); ``` * 当命名构造函数或类时使用驼峰式大写 ``` // 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' }); ``` * 命名私有属性时前面加个下划线 _ ``` // bad this.__firstName__ = 'Panda'; this.firstName_ = 'Panda'; // good this._firstName = 'Panda'; ``` * 模块开发的常量定义必须包含“完整的模块名称“ ``` // bad export const GET_ASSET_LIST = 'GET_ASSET_LIST'; // good export const GET_ASSET_LIST = 'ALARM_GET_ASSET_LIST'; ``` * 望名知意,建议驼峰命名(函数也适用)。 ``` // 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() ``` // bad if (!dragon.age()) { return false; } // good if (!dragon.hasAge()) { return false; } ``` * 可以创建get()和set()函数,但是要保持一致 ``` function 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]; }; ``` ## 构造器 * 给对象原型分配方法,而不是用一个新的对象覆盖原型,覆盖原型会使继承出现问题。 ``` function 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 帮助方法可链。 ``` // 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()方法,但是确保它工作正常并且不会有副作用。 ``` function 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(); }; ``` ## 事件 * 当给事件附加数据时,传入一个哈希而不是原始值,这可以让后面维护时加入更多数据到事件数据里而不用找出并更新那个事件的事件处理器 ``` // bad $(this).trigger('listingUpdated', listing.id); ... $(this).on('listingUpdated', function(e, listingId) { // do something with listingId }); ``` * 更好: ``` // good $(this).trigger('listingUpdated', { listingId : listing.id }); ... $(this).on('listingUpdated', function(e, data) { // do something with data.listingId }); ``` ## 模块 * 文件应该以驼峰命名,并在同名文件夹下,同时导出的时候名字一致 ## 参考 ## {% blockquote András Tóth (@tthndrs) https://blog.risingstack.com/javascript-clean-coding-best-practices-node-js-at-scale/ JavaScript Clean Coding Best Practices %} {% endblockquote %} {% blockquote w3schools https://www.w3schools.com/js/js_mistakes.asp JavaScript Common Mistakess %} {% endblockquote %} {% blockquote thinkful https://www.thinkful.com/learn/javascript-best-practices-1/Summary javascript-best-practices %} {% endblockquote %}