谈谈为什么你的 JavaScript 代码如此冗长


Posted in Javascript onJanuary 30, 2019

又一年过去了,JavaScript发生了许多变化。但是,即使是2019年了,还是需要给一些帮助你编写干净、整洁、有效、且具有扩展性的代码建议。

下面是让你成为更好的开发者的9条建议。

1. async / await

如果你还在为回调陷阱烦恼不已,那么就应该赶快扔掉这些2014年的代码了。除非绝对必要(比如某个库要求回调,或者出于性能的原因),否则不要再用回调了。Promise也不错,但当代码规模越来越大时,它们总是有些别扭。

我的解决方案就是async / await,能让阅读代码变得更容易,代码变得更整洁。实际上,Javascript中的任何Promise都可以await,只要你用的库能返回Promise,就可以await它。实际上,async/await只不过是promise的语法糖而已。为了让代码正确运行,你只需在函数前面加上async即可。

下面是个例子:

async function getData() {
  const result = await axios.get('https://dube.io/service/ping')
  const data = result.data

  console.log('data', data)

  return data
}

getData()

注意在顶层代码是无法await的,await只能在async函数中使用。此外,async / await是在ES2017中引入的,所以务必要对代码进行编译(transpile)。

2. 异步控制流

许多时候需要获取多个数据集并在每个数据集上做一些处理,或者在所有异步调用都返回之后执行某项任务。

for...of

假设网页上有一些精灵宝可梦,我们需要获取每一只的详细信息。我们不能等待所有调用结束,因为我们不知道一共有多少只。我们希望能在获取一部分数据之后立即更新数据集,这时候就可以使用for...of在一个数组上进行循环,然后在内部加入async的代码块,但这样做会造成阻塞,直到所有调用结束。一定要注意,这样做有可能会造成性能瓶颈,但这样做也不失为一种办法。

例子如下:

import axios from 'axios'

let myData = [{id: 0}, {id: 1}, {id: 2}, {id: 3}]

async function fetchData(dataSet) {
  for(entry of dataSet) {
    const result = await axios.get(`https://ironhack-pokeapi.herokuapp.com/pokemon/${entry.id}`)
    const newData = result.data
    updateData(newData)

    console.log(myData)
  }
}

function updateData(newData) {
  myData = myData.map(el => {
    if(el.id === newData.id) return newData
    return el
  })
}

fetchData(myData)

这些例子实际上都能运行,可以自行复制粘贴到你喜欢的代码沙盒工具中。

Promise.all

怎样才能并行获取所有宝可梦呢?我们可以await所有的promise,只需用Promise.all即可:

import axios from 'axios' 

let myData = [{id: 0}, {id: 1}, {id: 2}, {id: 3}]

async function fetchData(dataSet) {
  const pokemonPromises = dataSet.map(entry => {
    return axios.get(`https://ironhack-pokeapi.herokuapp.com/pokemon/${entry.id}`)
  })

  const results = await Promise.all(pokemonPromises)

  results.forEach(result => {
    updateData(result.data)
  })

  console.log(myData) 
}

function updateData(newData) {
  myData = myData.map(el => {
    if(el.id === newData.id) return newData
    return el
  })
}

fetchData(myData)

for...of和Promise.all都是在ES6+中引用的,所以代码需要编译。

3. 解构和默认值

我们现在回到前面的例子:

const result = axios.get(`https://ironhack-pokeapi.herokuapp.com/pokemon/${entry.id}`)
const data = result.data

这段代码有个更简单的写法。我们可以使用解构来从一个数组或对象中获取一个或多个值。可以这样写:

const { data } = await axios.get(...)

这样就能节省一行代码!还可以进行重命名:

const { data: newData } = await axios.get(...)

另一个小技巧就是在解构时制定默认值。这样能保证变量永远不会为undefine,因此就不需要手工检查变量了。

const { id = 5 } = {}
console.log(id) // 5

这些技巧也可以用在函数参数上,例如:

function calculate({operands = [1, 2], type = 'addition'} = {}) {
  return operands.reduce((acc, val) => {
    switch(type) {
      case 'addition':
        return acc + val
      case 'subtraction':
        return acc - val
      case 'multiplication':
        return acc * val
      case 'division':
        return acc / val
    }
  }, ['addition', 'subtraction'].includes(type) ? 0 : 1)
}

console.log(calculate()) // 3
console.log(calculate({type: 'division'})) // 0.5
console.log(calculate({operands: [2, 3, 4], type: 'multiplication'})) // 24

第一眼看上去这个例子可能不太容易理解,但多花些时间研究下是有好处的。当我们不给函数传递参数时,就会使用默认值。如果给函数传递参数,那么不存在的参数就会使用默认值。

解构和默认值是在ES6+中引入的,所以代码需要编译。

4. 真值和假值

在使用默认值时,经常需要检查存在的值。但是,你还可以直接使用真值和假值。这样能改善代码并节省好多字符,使代码更加流畅。我经常看到人们这样写:

if(myBool === true) {
 console.log(...)
}
// OR
if(myString.length > 0) {
 console.log(...)
}
// OR
if(isNaN(myNumber)) {
 console.log(...)
}

这些代码可以缩写成:

if(myBool) {
 console.log(...)
}
// OR
if(myString) {
 console.log(...)
}
// OR
if(!myNumber) {
 console.log(...)
}

要真正理解这些语句的好处,你必须要理解真值和假值都是什么。下面是部分摘要:

假值

  • 长度为0的字符串
  • 数字0
  • false
  • undefined
  • null
  • NaN

真值

  • 空数组
  • 空对象
  • 任何其他东西

当检查真值或假值时,不需要明确写出比较,这相当于使用双等号 == 而不是三等号 ===。一般来说,这种用法的行为与预想是一致的,但有可能会遇到bug。比如,我最常遇到但就是有关数字0的bug。

5. 逻辑运算符和三元运算符

这些运算符也是用来缩减代码的,节省下宝贵的代码行数。经常有许多工具可以保持代码干净整洁,但这些工具也会造成混乱,特别是在改变它们时。

逻辑运算符

逻辑运算符可以组合两个表达式,并返回true或false,或者匹配的值。常用的有&&,意思是“与”,还有 || 意思是“或”。我们来看看:

console.log(true && true) // true
console.log(false && true) // false
console.log(true && false) // false
console.log(false && false) // false
console.log(true || true) // true
console.log(true || false) // true
console.log(false || true) // true
console.log(false || false) // false

根据上一部分关于真值和假值的知识,我们可以将逻辑运算符组合起来。在使用逻辑运算符时,会使用以下规则:

  • && :返回第一个值为假的表达式的值。如果不存在,则返回最后一个值为真的值。
  • || :返回第一个值为假的表达式的值。如果不存在,则返回最后一个值为假的值。
console.log(0 && {a: 1}) // 0
console.log(false && 'a') // false
console.log('2' && 5) // 5
console.log([] || false) // []
console.log(NaN || null) // null
console.log(true || 'a') // true

三元运算符

三元运算符很像逻辑表达式,但它由三个部分组成:

  • 比较部分,返回假值或真值;
  • 第一个值,如果比较为真;
  • 第二个值,如果比较为假。

下面是例子:

const lang = 'German'
console.log(lang === 'German' ? 'Hallo' : 'Hello') // Hallo
console.log(lang ? 'Ja' : 'Yes') // Ja
console.log(lang === 'French' ? 'Bon soir' : 'Good evening') // Good evening

6. 链式操作

你遇到过这个问题吗?在访问嵌套对象的属性时,无法事先确定对象的属性是否存在?可能不得不写这样的代码:

let data
if(myObj && myObj.firstProp && myObj.firstProp.secondProp && myObj.firstProp.secondProp.actualData) data = myObj.firstProp.secondProp.actualData

这段代码很荒谬,我们还有更好的办法,至少是在建议中的办法(下面说了怎样启用该办法)。这个办法称为optional chaining,用法如下:

const data = myObj?.firstProp?.secondProp?.actualData

用这个方法检查嵌套属性非常流畅,代码也能变得更干净。

目前,optional chaining还不是官方标准的一部分,但它是个stage-1的实验性功能。需要在babelrc中加入@babel/plugin-proposal-optional-chaining来启用它。

7. 类属性和绑定

JavaScript中的函数绑定是个非常常见的任务。由于ES6标准引入了箭头函数,我们现在可以自动地用定义的形式绑定函数——这方法非常好用,现在的JavaScript开发者都在用它。之前类刚刚出现时是没办法使用箭头函数的,因为类需要用某种特殊的方式来定义。我们需要在某个地方进行绑定,例如在构造函数里(在React.js中最好这样做)。

我很讨厌需要先定义类方法再绑定方法的流程,不过现在可以通过箭头函数进行自动绑定。箭头函数现在可以直接在类中使用。

下面是个例子,其中的_increaseCount被绑定了:

class Counter extends React.Component {
  constructor(props) {
    super(props)
    this.state = { count: 0 }
  }

  render() {
    return(
      <div>
        <h1>{this.state.count}</h1> 
        <button onClick={this._increaseCount}>Increase Count</button>
      </div>
    )
  }

  _increaseCount = () => {
    this.setState({ count: this.state.count + 1 })
  }
}

目前,类属性不是官方标准的一部分,但是个stage-3的实验性功能。必须在babelrc中添加@babel/plugin-proposal-class-properties才能使用它。

8. 使用parcel

作为前端开发者,你肯定会遇到打包和编译代码的问题。

长时间以来,实践中的标准是webpack。我最初用的是webpack版本1,当时用起来很痛苦,需要不断修改尝试各种配置选项,我在上面花了无数个小时想办法让它工作。一旦弄好我就绝不会再碰它,以免不小心破坏什么。几个月之后我遇到了parcel,总算松了口气。它几乎可以不加任何配置拿来即用,但你依然可以在需要的时候进行改变。它还支持插件,类似于webpack和babel,但非常快。

如果你不知道parcel,我建议你一定要试试。

9. 自己写更多代码

这一条很有意思,这个话题我已经讨论过很多次了。

即使是CSS,许多人也喜欢用现成的库,比如bootstrap。至于JavaScript,现在还有很多人在用jQuery以及各种小型库进行表单验证、跑马灯等等。虽然使用库天经地义,但我强烈建议你自己写更多的代码,而不是依赖于安装各种npm包。当然,大型的库(甚至框架)需要整个团队去构建,如moment.js或react-dateicker,自己写是不现实的。

但是,其他的大部分东西都可以自己写。这样能带来三个好处:

你清楚地知道代码的内容;

在某个点上你开始真正理解编程,知道内部的工作原理;

可以防止代码膨胀。

最初直接使用npm包很方便。自己实现一些功能会花很多时间。但是,如果安装的包并不能正常工作,而需要换别的方法,就得花更多的时间去阅读其API。而在自己实现时,你可以为项目100%地量身定做。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
jQuery 第二课 操作包装集元素代码
Mar 14 Javascript
JavaScript高级程序设计 读书笔记之十 本地对象Date日期
Feb 27 Javascript
原生javascript实现获取指定元素下所有后代元素的方法
Oct 28 Javascript
jquery图片滚动放大代码分享(2)
Aug 28 Javascript
JavaScript操作XML/HTML比较常用的对象属性集锦
Oct 30 Javascript
jQuery+ajax实现文章点赞功能的方法
Dec 31 Javascript
JavaScript核心语法总结(推荐)
Jun 02 Javascript
js替换字符串中所有指定的字符(实现代码)
Aug 17 Javascript
AngularJS extend用法详解及实例代码
Nov 15 Javascript
通过修改360抢票的刷新频率和突破8车次限制实现方法
Jan 04 Javascript
对比分析Django的Q查询及AngularJS的Datatables分页插件
Feb 07 Javascript
解决vue打包后vendor.js文件过大问题
Jul 03 Javascript
JS实现头条新闻的经典轮播图效果示例
Jan 30 #Javascript
AJAX在JQuery中的应用详解
Jan 30 #jQuery
JS实现换肤功能的方法实例详解
Jan 30 #Javascript
js实现ATM机存取款功能
Oct 27 #Javascript
详解基于electron制作一个node压缩图片的桌面应用
Jan 29 #Javascript
JS实现鼠标拖拽盒子移动及右键点击盒子消失效果示例
Jan 29 #Javascript
JS实现盒子跟着鼠标移动及键盘方向键控制盒子移动效果示例
Jan 29 #Javascript
You might like
虫族 Zerg 魔法科技
2020/03/14 星际争霸
改进的IP计数器
2006/10/09 PHP
smarty+adodb+部分自定义类的php开发模式
2006/12/31 PHP
mysql_fetch_assoc和mysql_fetch_row的功能加起来就是mysql_fetch_array
2007/01/15 PHP
php多个文件及图片上传实例详解
2014/11/10 PHP
PHP使用redis实现统计缓存mysql压力的方法
2015/11/14 PHP
CodeIgniter多语言实现方法详解
2016/01/20 PHP
Thinkphp 框架扩展之驱动扩展实例分析
2020/04/27 PHP
PHP+Redis事务解决高并发下商品超卖问题(推荐)
2020/08/03 PHP
经典的带阴影的可拖动的浮动层
2006/06/26 Javascript
php上传图片并给图片打上透明水印的代码
2010/06/07 Javascript
Angular 4依赖注入学习教程之组件服务注入(二)
2017/06/04 Javascript
windows下create-react-app 升级至3.3.1版本踩坑记
2020/02/17 Javascript
VUE table表格动态添加一列数据,新增的这些数据不可以编辑(v-model绑定的数据不能实时更新)
2020/04/03 Javascript
Vue 打包体积优化方案小结
2020/05/20 Javascript
js实现3D旋转相册
2020/08/02 Javascript
[40:05]LGD vs Winstrike 2018国际邀请赛小组赛BO2 第二场 8.17
2018/08/18 DOTA
Python设计模式之组合模式原理与用法实例分析
2019/01/11 Python
python3实现绘制二维点图
2019/12/04 Python
基于python requests selenium爬取excel vba过程解析
2020/08/12 Python
Django中日期时间型字段进行年月日时分秒分组统计
2020/11/27 Python
方太官方网上商城:销售方太抽油烟机、燃气灶、消毒柜等
2017/01/17 全球购物
Michael Kors澳大利亚官网:世界知名的奢侈饰品和成衣设计师
2020/02/13 全球购物
俄语翻译实习生的自我评价分享
2013/11/06 职场文书
见习期自我鉴定
2013/11/07 职场文书
蛋糕店的商业计划书范文
2014/01/27 职场文书
幼儿园新年寄语
2014/04/03 职场文书
父母对孩子说的话
2014/04/12 职场文书
主题教育活动总结
2014/05/05 职场文书
运动会400米加油稿(8篇)
2014/09/22 职场文书
文明礼仪倡议书
2015/04/28 职场文书
王亚平太空授课观后感
2015/06/12 职场文书
趣味运动会新闻稿
2015/07/17 职场文书
同学聚会开幕词
2019/04/02 职场文书
《我在为谁工作》:工作的质量往往决定生活的质量
2019/12/27 职场文书
python 制作一个gui界面的翻译工具
2021/05/14 Python