如何提高代码的可读性、复用性、扩展性。我们将从以下四个方面讨论:
- 变量
- 函数
- 类
- 异步
一、变量用有意义且常用的单词命名
1 2 3 4 | // Bad:const yyyymmdstr = moment().format('YYYY/MM/DD');// Good:const currentDate = moment().format('YYYY/MM/DD'); |
保持统一对同一类型的变量使用相同的命名保持统一:
1 2 3 4 5 6 | // Bad:getUserInfo();getClientData();getCustomerRecord();// Good:getUser() |
每个常量(全大写)都该命名可以用 ESLint 检测代码中未命名的常量。
1 2 3 4 5 6 | // Bad:// 其他人知道 86400000 的意思吗?setTimeout( blastOff, 86400000 );// Good:const MILLISECOND_IN_A_DAY = 86400000;setTimeout( blastOff, MILLISECOND_IN_A_DAY ); |
避免无意义的命名既然创建了一个 car 对象,就没有必要把它的颜色命名为 carColor。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 | // Bad:const car = { carMake: 'Honda', carModel: 'Accord', carColor: 'Blue'};function paintCar( car ) { car.carColor = 'Red';}// Good:const car = { make: 'Honda', model: 'Accord', color: 'Blue'};function paintCar( car ) { car.color = 'Red';} |
传参使用默认值
1 2 3 4 5 6 7 8 9 | // Bad:function createMicrobrewery( name ) { const breweryName = name || 'Hipster Brew Co.'; // ...}// Good:function createMicrobrewery( name = 'Hipster Brew Co.' ) { // ...} |
二、函数函数参数( 最好 2 个或更少 )如果参数超过两个,建议使用 ES6 的解构语法,不用考虑参数的顺序。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 | // Bad:function createMenu( title, body, buttonText, cancellable ) { // ...}// Good:function createMenu( { title, body, buttonText, cancellable } ) { // ...}createMenu({ title: 'Foo', body: 'Bar', buttonText: 'Baz', cancellable: true}); |
一个方法只做一件事情这是一条在软件工程领域流传久远的规则。严格遵守这条规则会让你的代码可读性更好,也更容易重构。如果违反这个规则,那么代码会很难被测试或者重用。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 | // Bad:function emailClients( clients ) { clients.forEach( client => { const clientRecord = database.lookup( client ); if ( clientRecord.isActive() ) { email( client ); } });}// Good:function emailActiveClients( clients ) { clients .filter( isActiveClient ) .forEach( email );}function isActiveClient( client ) { const clientRecord = database.lookup( client ); return clientRecord.isActive();} |
函数名上体现它的作用
01 02 03 04 05 06 07 08 09 10 11 12 13 | // Bad:function addToDate( date, month ) { // ...}const date = new Date();// 很难知道是把什么加到日期中addToDate( date, 1 );// Good:function addMonthToDate( month, date ) { // ...}const date = new Date();addMonthToDate( 1, date ); |
删除重复代码,合并相似函数很多时候虽然是同一个功能,但由于一两个不同点,让你不得不写两个几乎相同的函数。
01 02 03 04 05 06 07 08 09 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 | // Bad:function showDeveloperList(developers) { developers.forEach((developer) => { const expectedSalary = developer.calculateExpectedSalary(); const experience = developer.getExperience(); const githubLink = developer.getGithubLink(); const data = { expectedSalary, experience, githubLink }; render(data); });}function showManagerList(managers) { managers.forEach((manager) => { const expectedSalary = manager.calculateExpectedSalary(); const experience = manager.getExperience(); const portfolio = manager.getMBAProjects(); const data = { expectedSalary, experience, portfolio }; render(data); });}// Good:function showEmployeeList(employees) { employees.forEach(employee => { const expectedSalary = employee.calculateExpectedSalary(); const experience = employee.getExperience(); const data = { expectedSalary, experience, }; switch(employee.type) { case 'develop': data.githubLink = employee.getGithubLink(); break case 'manager': data.portfolio = employee.getMBAProjects(); break } render(data); })} |
使用 Object.assign 设置默认属性
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | // Bad:const menuConfig = { title: null, body: 'Bar', buttonText: null, cancellable: true};function createMenu(config) { config.title = config.title || 'Foo'; config.body = config.body || 'Bar'; config.buttonText = config.buttonText || 'Baz'; config.cancellable = config.cancellable !== undefined ? config.cancellable : true;}createMenu(menuConfig);// Good:const menuConfig = { title: 'Order', // 不包含 body buttonText: 'Send', cancellable: true};function createMenu(config) { config = Object.assign({ title: 'Foo', body: 'Bar', buttonText: 'Baz', cancellable: true }, config); // config : {title: "Order", body: "Bar", buttonText: "Send", cancellable: true} // ...}createMenu(menuConfig); |
尽量不要写全局方法在 JavaScript 中,永远不要污染全局,会在生产环境中产生难以预料的 bug。举个例子,比如你在 Array.prototype 上新增一个 diff 方法来判断两个数组的不同。而你同事也打算做类似的事情,不过他的 diff 方法是用来判断两个数组首位元素的不同。很明显你们方法会产生冲突,遇到这类问题我们可以用 ES2015/ES6 的语法来对 Array 进行扩展。
01 02 03 04 05 06 07 08 09 10 11 12 | // Bad:Array.prototype.diff = function diff(comparisonArray) { const hash = new Set(comparisonArray); return this.filter(elem => !hash.has(elem));};// Good:class SuperArray extends Array { diff(comparisonArray) { const hash = new Set(comparisonArray); return this.filter(elem => !hash.has(elem)); }} |
尽量别用“非”条件句
01 02 03 04 05 06 07 08 09 10 11 12 13 14 | // Bad:function isDOMNodeNotPresent(node) { // ...}if (!isDOMNodeNotPresent(node)) { // ...}// Good:function isDOMNodePresent(node) { // ...}if (isDOMNodePresent(node)) { // ...} |
不要过度优化现代浏览器已经在底层做了很多优化,过去的很多优化方案都是无效的,会浪费你的时间。
1 2 3 4 5 6 7 8 9 | // Bad:// 现代浏览器已对此( 缓存 list.length )做了优化。for (let i = 0, len = list.length; i < len; i++) { // ...}// Good:for (let i = 0; i < list.length; i++) { // ...} |
删除弃用代码这里没有实例代码,删除就对了
三、类使用 ES6 的 class在 ES6 之前,没有类的语法,只能用构造函数的方式模拟类,可读性非常差。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // Good:// 动物class Animal { constructor(age) { this.age = age }; move() {};}// 哺乳动物class Mammal extends Animal{ constructor(age, furColor) { super(age); this.furColor = furColor; }; liveBirth() {};}// 人类class Human extends Mammal{ constructor(age, furColor, languageSpoken) { super(age, furColor); this.languageSpoken = languageSpoken; }; speak() {};} |
使用链式调用这种模式相当有用,可以在很多库中都有使用。它让你的代码简洁优雅。
01 02 03 04 05 06 07 08 09 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 54 55 56 57 58 59 60 61 62 | class Car { constructor(make, model, color) { this.make = make; this.model = model; this.color = color; } setMake(make) { this.make = make; } setModel(model) { this.model = model; } setColor(color) { this.color = color; } save() { console.log(this.make, this.model, this.color); }}// Bad:const car = new Car('Ford','F-150','red');car.setColor('pink');car.save();// Good: class Car { constructor(make, model, color) { this.make = make; this.model = model; this.color = color; } setMake(make) { this.make = make; // NOTE: Returning this for chaining return this; } setModel(model) { this.model = model; // NOTE: Returning this for chaining return this; } setColor(color) { this.color = color; // NOTE: Returning this for chaining return this; } save() { console.log(this.make, this.model, this.color); // NOTE: Returning this for chaining return this; }}const car = new Car("Ford", "F-150", "red").setColor("pink").save(); |
四、异步使用 promise 或者 Async/Await 代替回调
01 02 03 04 05 06 07 08 09 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 | // Bad:get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', (requestErr, response) => { if (requestErr) { console.error(requestErr); } else { writeFile('article.html', response.body, (writeErr) => { if (writeErr) { console.error(writeErr); } else { console.log('File written'); } }); }});// Good:get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin') .then((response) => { return writeFile('article.html', response); }) .then(() => { console.log('File written'); }) .catch((err) => { console.error(err); });// perfect: |