如何写出优雅耐看的JavaScript代码

开发 前端
在我们平时的工作开发中,大多数都是大人协同开发的公共项目;在我们平时开发中代码codeing的时候我们考虑代码的可读性、复用性和扩展性。干净的代码,既在质量上较为可靠,也为后期维护、升级奠定了良好基础。

前言

在我们平时的工作开发中,大多数都是大人协同开发的公共项目;在我们平时开发中代码codeing的时候我们考虑代码的可读性、复用性和扩展性。

干净的代码,既在质量上较为可靠,也为后期维护、升级奠定了良好基础。

[[277405]]

我们从以下几个方面进行探讨:

变量

1、变量命名

一般我们在定义变量是要使用有意义的词汇命令,要做到见面知义

 

//bad code 
const yyyymmdstr = moment().format('YYYY/MM/DD'); 
//better code 
const currentDate = moment().format('YYYY/MM/DD'); 
  • 1.
  • 2.
  • 3.
  • 4.

2、可描述

通过一个变量生成了一个新变量,也需要为这个新变量命名,也就是说每个变量当你看到他第一眼你就知道他是干什么的。

 

//bad code 
const ADDRESS = 'One Infinite Loop, Cupertino 95014'
const CITY_ZIP_CODE_REGEX = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/; 
saveCityZipCode(ADDRESS.match(CITY_ZIP_CODE_REGEX)[1], ADDRESS.match(CITY_ZIP_CODE_REGEX)[2]); 
 
//better code 
const ADDRESS = 'One Infinite Loop, Cupertino 95014'
const CITY_ZIP_CODE_REGEX = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/; 
const [, city, zipCode] = ADDRESS.match(CITY_ZIP_CODE_REGEX) || []; 
saveCityZipCode(city, zipCode); 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

3、形参命名

在for、forEach、map的循环中我们在命名时要直接

 

//bad code 
const locations = ['Austin''New York''San Francisco']; 
locations.map((l) => { 
  doStuff(); 
  doSomeOtherStuff(); 
  // ... 
  // ... 
  // ... 
  // 需要看其他代码才能确定 'l' 是干什么的。 
  dispatch(l); 
}); 
 
//better code 
const locations = ['Austin''New York''San Francisco']; 
locations.forEach((location) => { 
  doStuff(); 
  doSomeOtherStuff(); 
  // ... 
  // ... 
  // ... 
  dispatch(location); 
}); 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

4、避免无意义的前缀

例如我们只创建一个对象是,没有必要再把每个对象的属性上再加上对象名

 

//bad code 
const car = { 
  carMake: 'Honda'
  carModel: 'Accord'
  carColor: 'Blue' 
}; 
 
function paintCar(car) { 
  car.carColor = 'Red'

 
//better code 
const car = { 
  make: 'Honda'
  model: 'Accord'
  color: 'Blue' 
}; 
 
function paintCar(car) { 
  car.color = 'Red'

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

5、默认值

 

//bad code 
function createMicrobrewery(name) { 
  const breweryName = name || 'Hipster Brew Co.'
  // ... 

 
//better code 
function createMicrobrewery(name = 'Hipster Brew Co.') { 
  // ... 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

函数

1、参数

一般参数多的话要使用ES6的解构传参的方式

 

//bad code 
function createMenu(title, body, buttonText, cancellable) { 
  // ... 

 
//better code 
function createMenu({ title, body, buttonText, cancellable }) { 
  // ... 

 
//better code 
createMenu({ 
  title: 'Foo'
  body: 'Bar'
  buttonText: 'Baz'
  cancellable: true 
}); 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

2、单一化处理

一个方法里面最好只做一件事,不要过多的处理,这样代码的可读性非常高

 

//bad code 
function emailClients(clients) { 
  clients.forEach((client) => { 
    const clientRecord = database.lookup(client); 
    if (clientRecord.isActive()) { 
      email(client); 
    } 
  }); 

 
//better code 
function emailActiveClients(clients) { 
  clients 
    .filter(isActiveClient) 
    .forEach(email); 

function isActiveClient(client) { 
  const clientRecord = database.lookup(client);     
  return clientRecord.isActive(); 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

3、对象设置默认属性

 

//bad code 
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); 
 
 
//better code 
const menuConfig = { 
  title: 'Order'
  // 'body' key 缺失 
  buttonText: 'Send'
  cancellable: true 
}; 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

4、避免副作用

函数接收一个值返回一个新值,除此之外的行为我们都称之为副作用,比如修改全局变量、对文件进行 IO 操作等。

当函数确实需要副作用时,比如对文件进行 IO 操作时,请不要用多个函数/类进行文件操作,有且仅用一个函数/类来处理。也就是说副作用需要在唯一的地方处理。

副作用的三大天坑:随意修改可变数据类型、随意分享没有数据结构的状态、没有在统一地方处理副作用。

 

//bad code 
// 全局变量被一个函数引用 
// 现在这个变量从字符串变成了数组,如果有其他的函数引用,会发生无法预见的错误。 
var name = 'Ryan McDermott'
function splitIntoFirstAndLastName() { 
  name = name.split(' '); 

splitIntoFirstAndLastName(); 
console.log(name); // ['Ryan''McDermott']; 
 
 
//better code 
var name = 'Ryan McDermott'
var newName = splitIntoFirstAndLastName(name
 
function splitIntoFirstAndLastName(name) { 
  return name.split(' '); 

 
console.log(name); // 'Ryan McDermott'
console.log(newName); // ['Ryan''McDermott']; 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

在 JavaScript 中,基本类型通过赋值传递,对象和数组通过引用传递。以引用传递为例:

假如我们写一个购物车,通过 addItemToCart()方法添加商品到购物车,修改 购物车数组。此时调用 purchase()方法购买,由于引用传递,获取的 购物车数组正好是最新的数据。

看起来没问题对不对?

如果当用户点击购买时,网络出现故障, purchase()方法一直在重复调用,与此同时用户又添加了新的商品,这时网络又恢复了。那么 purchase()方法获取到 购物车数组就是错误的。

为了避免这种问题,我们需要在每次新增商品时,克隆 购物车数组并返回新的数组。

 

//bad code 
const addItemToCart = (cart, item) => { 
  cart.push({ item, dateDate.now() }); 
}; 
 
//better code 
const addItemToCart = (cart, item) => { 
  return [...cart, {item, dateDate.now()}] 
}; 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

5、全局方法

在 JavaScript 中,永远不要污染全局,会在生产环境中产生难以预料的 bug。举个例子,比如你在 Array.prototype上新增一个 diff方法来判断两个数组的不同。而你同事也打算做类似的事情,不过他的 diff方法是用来判断两个数组首位元素的不同。很明显你们方法会产生冲突,遇到这类问题我们可以用 ES2015/ES6 的语法来对 Array进行扩展。

 

//bad code 
Array.prototype.diff = function diff(comparisonArray) { 
  const hash = new Set(comparisonArray); 
  return this.filter(elem => !hash.has(elem)); 
}; 
 
//better code 
class SuperArray extends Array { 
  diff(comparisonArray) { 
    const hash = new Set(comparisonArray); 
    return this.filter(elem => !hash.has(elem));         
  } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

6、避免类型检查

JavaScript 是无类型的,意味着你可以传任意类型参数,这种自由度很容易让人困扰,不自觉的就会去检查类型。仔细想想是你真的需要检查类型还是你的 API 设计有问题?

 

//bad code 
function travelToTexas(vehicle) { 
  if (vehicle instanceof Bicycle) { 
    vehicle.pedal(this.currentLocation, new Location('texas')); 
  } else if (vehicle instanceof Car) { 
    vehicle.drive(this.currentLocation, new Location('texas')); 
  } 

 
//better code 
function travelToTexas(vehicle) { 
  vehicle.move(this.currentLocation, new Location('texas')); 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

如果你需要做静态类型检查,比如字符串、整数等,推荐使用 TypeScript,不然你的代码会变得又臭又长。

 

//bad code 
function combine(val1, val2) { 
  if (typeof val1 === 'number' && typeof val2 === 'number' || 
      typeof val1 === 'string' && typeof val2 === 'string') { 
    return val1 + val2; 
  } 
 
  throw new Error('Must be of type String or Number'); 

 
//better code 
function combine(val1, val2) { 
  return val1 + val2; 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

复杂条件判断

我们编写js代码时经常遇到复杂逻辑判断的情况,通常大家可以用if/else或者switch来实现多个条件判断,但这样会有个问题,随着逻辑复杂度的增加,代码中的if/else/switch会变得越来越臃肿,越来越看不懂,那么如何更优雅的写判断逻辑

1、if/else

点击列表按钮事件

 

/** 
 * 按钮点击事件 
 * @param {number} status 活动状态:1 开团进行中 2 开团失败 3 商品售罄 4 开团成功 5 系统取消 
 */ 
const onButtonClick = (status)=>{ 
  if(status == 1){ 
    sendLog('processing'
    jumpTo('IndexPage'
  }else if(status == 2){ 
    sendLog('fail'
    jumpTo('FailPage'
  }else if(status == 3){ 
    sendLog('fail'
    jumpTo('FailPage'
  }else if(status == 4){ 
    sendLog('success'
    jumpTo('SuccessPage'
  }else if(status == 5){ 
    sendLog('cancel'
    jumpTo('CancelPage'
  }else { 
    sendLog('other'
    jumpTo('Index'
  } 

  • 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.

从上面我们可以看到的是通过不同的状态来做不同的事情,代码看起来非常不好看,大家可以很轻易的提出这段代码的改写方案,switch出场:

2、switch/case

 

/** 
 * 按钮点击事件 
 * @param {number} status 活动状态:1 开团进行中 2 开团失败 3 商品售罄 4 开团成功 5 系统取消 
 */ 
const onButtonClick = (status)=>{ 
  switch (status){ 
    case 1: 
      sendLog('processing'
      jumpTo('IndexPage'
      break 
    case 2: 
    case 3: 
      sendLog('fail'
      jumpTo('FailPage'
      break   
    case 4: 
      sendLog('success'
      jumpTo('SuccessPage'
      break 
    case 5: 
      sendLog('cancel'
      jumpTo('CancelPage'
      break 
    default
      sendLog('other'
      jumpTo('Index'
      break 
  } 

  • 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.

这样看起来比if/else清晰多了,细心的同学也发现了小技巧,case 2和case 3逻辑一样的时候,可以省去执行语句和break,则case 2的情况自动执行case 3的逻辑。

3、存放到Object

将判断条件作为对象的属性名,将处理逻辑作为对象的属性值,在按钮点击的时候,通过对象属性查找的方式来进行逻辑判断,这种写法特别适合一元条件判断的情况。

 

const actions = { 
  '1': ['processing','IndexPage'], 
  '2': ['fail','FailPage'], 
  '3': ['fail','FailPage'], 
  '4': ['success','SuccessPage'], 
  '5': ['cancel','CancelPage'], 
  'default': ['other','Index'], 

/** 
 * 按钮点击事件 
 * @param {number} status 活动状态:1开团进行中 2开团失败 3 商品售罄 4 开团成功 5 系统取消 
 */ 
const onButtonClick = (status)=>{ 
  let action = actions[status] || actions['default'], 
      logName = action[0], 
      pageName = action[1] 
  sendLog(logName) 
  jumpTo(pageName) 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

4、存放到Map

 

const actions = new Map([ 
  [1, ['processing','IndexPage']], 
  [2, ['fail','FailPage']], 
  [3, ['fail','FailPage']], 
  [4, ['success','SuccessPage']], 
  [5, ['cancel','CancelPage']], 
  ['default', ['other','Index']] 
]) 
/** 
 * 按钮点击事件 
 * @param {number} status 活动状态:1 开团进行中 2 开团失败 3 商品售罄 4 开团成功 5 系统取消 
 */ 
const onButtonClick = (status)=>{ 
  let action = actions.get(status) || actions.get('default'
  sendLog(action[0]) 
  jumpTo(action[1]) 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

这样写用到了es6里的Map对象,是不是更爽了?Map对象和Object对象有什么区别呢?

  • 一个对象通常都有自己的原型,所以一个对象总有一个"prototype"键。
  • 一个对象的键只能是字符串或者Symbols,但一个Map的键可以是任意值。

你可以通过size属性很容易地得到一个Map的键值对个数,而对象的键值对个数只能手动确认。

代码风格

常量大写

//bad code 
const DAYS_IN_WEEK = 7; 
const daysInMonth = 30; 
 
const songs = ['Back In Black''Stairway to Heaven''Hey Jude']; 
const Artists = ['ACDC''Led Zeppelin''The Beatles']; 
 
function eraseDatabase() {} 
function restore_database() {} 
 
class animal {} 
class Alpaca {} 
 
//better code 
const DAYS_IN_WEEK = 7; 
const DAYS_IN_MONTH = 30; 
 
const SONGS = ['Back In Black''Stairway to Heaven''Hey Jude']; 
const ARTISTS = ['ACDC''Led Zeppelin''The Beatles']; 
 
function eraseDatabase() {} 
function restoreDatabase() {} 
 
class Animal {} 
class Alpaca {} 
  • 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 code 
class PerformanceReview { 
  constructor(employee) { 
    this.employee = employee; 
  } 
 
  lookupPeers() { 
    return db.lookup(this.employee, 'peers'); 
  } 
 
  lookupManager() { 
    return db.lookup(this.employee, 'manager'); 
  } 
 
  getPeerReviews() { 
    const peers = this.lookupPeers(); 
    // ... 
  } 
 
  perfReview() { 
    this.getPeerReviews(); 
    this.getManagerReview(); 
    this.getSelfReview(); 
  } 
 
  getManagerReview() { 
    const manager = this.lookupManager(); 
  } 
 
  getSelfReview() { 
    // ... 
  } 

 
const review = new PerformanceReview(employee); 
review.perfReview(); 
 
//better code 
class PerformanceReview { 
  constructor(employee) { 
    this.employee = employee; 
  } 
 
  perfReview() { 
    this.getPeerReviews(); 
    this.getManagerReview(); 
    this.getSelfReview(); 
  } 
 
  getPeerReviews() { 
    const peers = this.lookupPeers(); 
    // ... 
  } 
 
  lookupPeers() { 
    return db.lookup(this.employee, 'peers'); 
  } 
 
  getManagerReview() { 
    const manager = this.lookupManager(); 
  } 
 
  lookupManager() { 
    return db.lookup(this.employee, 'manager'); 
  } 
 
  getSelfReview() { 
    // ... 
  } 

 
const review = new PerformanceReview(employee); 
review.perfReview(); 
  • 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.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.

 

责任编辑:未丽燕 来源: SegmentFault.com
相关推荐

2021-01-04 07:57:07

C++工具代码

2022-03-11 12:14:43

CSS代码前端

2021-09-01 08:55:20

JavaScript代码开发

2021-11-30 10:20:24

JavaScript代码前端

2020-05-14 09:15:52

设计模式SOLID 原则JS

2021-12-07 08:16:34

React 前端 组件

2020-07-15 08:17:16

代码

2020-05-08 14:45:00

JS代码变量

2013-06-07 14:00:23

代码维护

2020-05-11 15:23:58

CQRS代码命令

2022-02-08 19:33:13

技巧代码格式

2022-02-17 10:05:21

CSS代码前端

2024-03-28 14:29:46

JavaScript编程

2024-12-04 15:10:21

2020-12-19 10:45:08

Python代码开发

2020-05-19 15:00:26

Bug代码语言

2021-12-13 14:37:37

React组件前端

2022-10-24 08:10:21

SQL代码业务

2019-06-24 10:26:15

代码程序注释

2015-09-28 10:49:59

代码程序员
点赞
收藏

51CTO技术栈公众号