|
|
51CTO旗下网站
|
|
移动端

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

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

作者:浅夏晴空来源:SegmentFault.com|2019-09-20 15:47

【大咖·来了 第7期】10月24日晚8点观看《智能导购对话机器人实践》

前言

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

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

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

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

变量

1、变量命名

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

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

2、可描述

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

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

3、形参命名

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

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

4、避免无意义的前缀

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

  1. //bad code 
  2. const car = { 
  3.   carMake: 'Honda'
  4.   carModel: 'Accord'
  5.   carColor: 'Blue' 
  6. }; 
  7.  
  8. function paintCar(car) { 
  9.   car.carColor = 'Red'
  10.  
  11. //better code 
  12. const car = { 
  13.   make: 'Honda'
  14.   model: 'Accord'
  15.   color: 'Blue' 
  16. }; 
  17.  
  18. function paintCar(car) { 
  19.   car.color = 'Red'

5、默认值

  1. //bad code 
  2. function createMicrobrewery(name) { 
  3.   const breweryName = name || 'Hipster Brew Co.'
  4.   // ... 
  5.  
  6. //better code 
  7. function createMicrobrewery(name = 'Hipster Brew Co.') { 
  8.   // ... 

函数

1、参数

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

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

2、单一化处理

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

  1. //bad code 
  2. function emailClients(clients) { 
  3.   clients.forEach((client) => { 
  4.     const clientRecord = database.lookup(client); 
  5.     if (clientRecord.isActive()) { 
  6.       email(client); 
  7.     } 
  8.   }); 
  9.  
  10. //better code 
  11. function emailActiveClients(clients) { 
  12.   clients 
  13.     .filter(isActiveClient) 
  14.     .forEach(email); 
  15. function isActiveClient(client) { 
  16.   const clientRecord = database.lookup(client);     
  17.   return clientRecord.isActive(); 

3、对象设置默认属性

  1. //bad code 
  2. const menuConfig = { 
  3.   title: null
  4.   body: 'Bar'
  5.   buttonText: null
  6.   cancellable: true 
  7. }; 
  8. function createMenu(config) { 
  9.   config.title = config.title || 'Foo'
  10.   config.body = config.body || 'Bar'
  11.   config.buttonText = config.buttonText || 'Baz'
  12.   config.cancellable = config.cancellable !== undefined ? config.cancellable : true
  13. createMenu(menuConfig); 
  14.  
  15.  
  16. //better code 
  17. const menuConfig = { 
  18.   title: 'Order'
  19.   // 'body' key 缺失 
  20.   buttonText: 'Send'
  21.   cancellable: true 
  22. }; 

4、避免副作用

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

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

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

  1. //bad code 
  2. // 全局变量被一个函数引用 
  3. // 现在这个变量从字符串变成了数组,如果有其他的函数引用,会发生无法预见的错误。 
  4. var name = 'Ryan McDermott'
  5. function splitIntoFirstAndLastName() { 
  6.   name = name.split(' '); 
  7. splitIntoFirstAndLastName(); 
  8. console.log(name); // ['Ryan''McDermott']; 
  9.  
  10.  
  11. //better code 
  12. var name = 'Ryan McDermott'
  13. var newName = splitIntoFirstAndLastName(name
  14.  
  15. function splitIntoFirstAndLastName(name) { 
  16.   return name.split(' '); 
  17.  
  18. console.log(name); // 'Ryan McDermott'
  19. console.log(newName); // ['Ryan''McDermott']; 

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

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

看起来没问题对不对?

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

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

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

5、全局方法

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

  1. //bad code 
  2. Array.prototype.diff = function diff(comparisonArray) { 
  3.   const hash = new Set(comparisonArray); 
  4.   return this.filter(elem => !hash.has(elem)); 
  5. }; 
  6.  
  7. //better code 
  8. class SuperArray extends Array { 
  9.   diff(comparisonArray) { 
  10.     const hash = new Set(comparisonArray); 
  11.     return this.filter(elem => !hash.has(elem));         
  12.   } 

6、避免类型检查

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

  1. //bad code 
  2. function travelToTexas(vehicle) { 
  3.   if (vehicle instanceof Bicycle) { 
  4.     vehicle.pedal(this.currentLocation, new Location('texas')); 
  5.   } else if (vehicle instanceof Car) { 
  6.     vehicle.drive(this.currentLocation, new Location('texas')); 
  7.   } 
  8.  
  9. //better code 
  10. function travelToTexas(vehicle) { 
  11.   vehicle.move(this.currentLocation, new Location('texas')); 

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

  1. //bad code 
  2. function combine(val1, val2) { 
  3.   if (typeof val1 === 'number' && typeof val2 === 'number' || 
  4.       typeof val1 === 'string' && typeof val2 === 'string') { 
  5.     return val1 + val2; 
  6.   } 
  7.  
  8.   throw new Error('Must be of type String or Number'); 
  9.  
  10. //better code 
  11. function combine(val1, val2) { 
  12.   return val1 + val2; 

复杂条件判断

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

1、if/else

点击列表按钮事件

  1. /** 
  2.  * 按钮点击事件 
  3.  * @param {number} status 活动状态:1 开团进行中 2 开团失败 3 商品售罄 4 开团成功 5 系统取消 
  4.  */ 
  5. const onButtonClick = (status)=>{ 
  6.   if(status == 1){ 
  7.     sendLog('processing'
  8.     jumpTo('IndexPage'
  9.   }else if(status == 2){ 
  10.     sendLog('fail'
  11.     jumpTo('FailPage'
  12.   }else if(status == 3){ 
  13.     sendLog('fail'
  14.     jumpTo('FailPage'
  15.   }else if(status == 4){ 
  16.     sendLog('success'
  17.     jumpTo('SuccessPage'
  18.   }else if(status == 5){ 
  19.     sendLog('cancel'
  20.     jumpTo('CancelPage'
  21.   }else { 
  22.     sendLog('other'
  23.     jumpTo('Index'
  24.   } 

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

2、switch/case

  1. /** 
  2.  * 按钮点击事件 
  3.  * @param {number} status 活动状态:1 开团进行中 2 开团失败 3 商品售罄 4 开团成功 5 系统取消 
  4.  */ 
  5. const onButtonClick = (status)=>{ 
  6.   switch (status){ 
  7.     case 1: 
  8.       sendLog('processing'
  9.       jumpTo('IndexPage'
  10.       break 
  11.     case 2: 
  12.     case 3: 
  13.       sendLog('fail'
  14.       jumpTo('FailPage'
  15.       break   
  16.     case 4: 
  17.       sendLog('success'
  18.       jumpTo('SuccessPage'
  19.       break 
  20.     case 5: 
  21.       sendLog('cancel'
  22.       jumpTo('CancelPage'
  23.       break 
  24.     default
  25.       sendLog('other'
  26.       jumpTo('Index'
  27.       break 
  28.   } 

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

3、存放到Object

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

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

4、存放到Map

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

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

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

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

代码风格

常量大写

  1. //bad code 
  2. const DAYS_IN_WEEK = 7; 
  3. const daysInMonth = 30; 
  4.  
  5. const songs = ['Back In Black''Stairway to Heaven''Hey Jude']; 
  6. const Artists = ['ACDC''Led Zeppelin''The Beatles']; 
  7.  
  8. function eraseDatabase() {} 
  9. function restore_database() {} 
  10.  
  11. class animal {} 
  12. class Alpaca {} 
  13.  
  14. //better code 
  15. const DAYS_IN_WEEK = 7; 
  16. const DAYS_IN_MONTH = 30; 
  17.  
  18. const SONGS = ['Back In Black''Stairway to Heaven''Hey Jude']; 
  19. const ARTISTS = ['ACDC''Led Zeppelin''The Beatles']; 
  20.  
  21. function eraseDatabase() {} 
  22. function restoreDatabase() {} 
  23.  
  24. class Animal {} 
  25. class Alpaca {} 

先声明后调用

  1. //bad code 
  2. class PerformanceReview { 
  3.   constructor(employee) { 
  4.     this.employee = employee; 
  5.   } 
  6.  
  7.   lookupPeers() { 
  8.     return db.lookup(this.employee, 'peers'); 
  9.   } 
  10.  
  11.   lookupManager() { 
  12.     return db.lookup(this.employee, 'manager'); 
  13.   } 
  14.  
  15.   getPeerReviews() { 
  16.     const peers = this.lookupPeers(); 
  17.     // ... 
  18.   } 
  19.  
  20.   perfReview() { 
  21.     this.getPeerReviews(); 
  22.     this.getManagerReview(); 
  23.     this.getSelfReview(); 
  24.   } 
  25.  
  26.   getManagerReview() { 
  27.     const manager = this.lookupManager(); 
  28.   } 
  29.  
  30.   getSelfReview() { 
  31.     // ... 
  32.   } 
  33.  
  34. const review = new PerformanceReview(employee); 
  35. review.perfReview(); 
  36.  
  37. //better code 
  38. class PerformanceReview { 
  39.   constructor(employee) { 
  40.     this.employee = employee; 
  41.   } 
  42.  
  43.   perfReview() { 
  44.     this.getPeerReviews(); 
  45.     this.getManagerReview(); 
  46.     this.getSelfReview(); 
  47.   } 
  48.  
  49.   getPeerReviews() { 
  50.     const peers = this.lookupPeers(); 
  51.     // ... 
  52.   } 
  53.  
  54.   lookupPeers() { 
  55.     return db.lookup(this.employee, 'peers'); 
  56.   } 
  57.  
  58.   getManagerReview() { 
  59.     const manager = this.lookupManager(); 
  60.   } 
  61.  
  62.   lookupManager() { 
  63.     return db.lookup(this.employee, 'manager'); 
  64.   } 
  65.  
  66.   getSelfReview() { 
  67.     // ... 
  68.   } 
  69.  
  70. const review = new PerformanceReview(employee); 
  71. review.perfReview(); 

【编辑推荐】

  1. TypeScript VS JavaScript 深度对比
  2. JavaScript为什么这么难?
  3. HTML5的JavaScript 客户端PDF解决方案——jsPDF
  4. 面向对象之三个基本特征(JavaScript)
  5. 面向对象之七大基本原则(JavaScript)
【责任编辑:未丽燕 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

订阅专栏+更多

用Python玩转excel

用Python玩转excel

让重复操作傻瓜化
共3章 | DE8UG

187人订阅学习

AI入门级算法

AI入门级算法

算法常识
共22章 | 周萝卜123

164人订阅学习

这就是5G

这就是5G

5G那些事儿
共15章 | armmay

132人订阅学习

读 书 +更多

鸟哥的Linux私房菜——服务器架设篇(第二版)

本书是对连续三年蝉联畅销书排行榜前10名的《Linux鸟哥私房菜——服务器架设篇》的升级版,新版本根据目前服务器与网络环境做了大幅度修订...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊

51CTO服务号

51CTO官微