谈谈为什么你的 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通过select列表选择框对表格数据进行过滤示例
May 07 Javascript
Jquery 实现grid绑定模板
Jan 28 Javascript
微信小程序 http请求的session管理
Jun 07 Javascript
angularjs实现搜索的关键字在正文中高亮出来
Jun 13 Javascript
VUE利用vuex模拟实现新闻点赞功能实例
Jun 28 Javascript
addeventlistener监听scroll跟touch(实例讲解)
Aug 04 Javascript
不使用JavaScript实现菜单的打开和关闭效果demo
May 01 Javascript
快速解决Vue项目在IE浏览器中显示空白的问题
Sep 04 Javascript
jQuery实现鼠标移入移出事件切换功能示例
Sep 06 jQuery
深入理解与使用keep-alive(配合router-view缓存整个路由页面)
Sep 25 Javascript
JS实现的雪花飘落特效示例
Dec 03 Javascript
Vue初始化中的选项合并之initInternalComponent详解
Jun 11 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
PHP setcookie() cannot modify header information 的解决方法
2009/01/09 PHP
使用php来实现网络服务
2009/09/15 PHP
PHP 获取目录下的图片并随机显示的代码
2009/12/28 PHP
PHP APC缓存配置、使用详解
2014/03/06 PHP
PHP回调函数概念与用法实例分析
2017/11/03 PHP
php实现分页功能的详细实例方法
2019/09/29 PHP
控制打印时页眉角的代码
2007/02/08 Javascript
javascript 表单的友好用户体现
2009/01/07 Javascript
ExtJS TabPanel beforeremove beforeclose使用说明
2010/03/31 Javascript
extjs ColumnChart设置不同的颜色实现代码
2013/05/17 Javascript
解决jQuery动态获取手机屏幕高和宽的问题
2014/05/07 Javascript
jQuery实现仿新浪微博浮动的消息提示框(可智能定位)
2015/10/10 Javascript
JS实现仿腾讯微博无刷新删除微博效果代码
2015/10/16 Javascript
jQuery手动点击实现图片轮播特效
2020/04/20 Javascript
基于CSS3和jQuery实现跟随鼠标方位的Hover特效
2016/07/25 Javascript
微信小程序 SocketIO 实例讲解
2016/10/13 Javascript
一个非常好用的文字滚动的案例,鼠标悬浮可暂停[两种方案任选]
2016/12/01 Javascript
Three.js实现绘制字体模型示例代码
2017/09/26 Javascript
Vue 递归多级菜单的实例代码
2019/05/05 Javascript
vue-cli3使用mock数据的方法分析
2020/03/16 Javascript
[01:07]DOTA2次级职业联赛 - Fpb战队宣传片
2014/12/01 DOTA
[01:33:25]DOTA2-DPC中国联赛 正赛 Elephant vs IG BO3 第一场 1月24日
2021/03/11 DOTA
pip 错误unused-command-line-argument-hard-error-in-future解决办法
2014/06/01 Python
Python循环中else,break和continue的用法实例详解
2019/07/11 Python
python opencv进行图像拼接
2020/03/27 Python
Python super()函数使用及多重继承
2020/05/06 Python
使用CSS实现弹性视频html5案例实践
2012/12/26 HTML / CSS
什么是索引指示器
2012/08/20 面试题
信息管理专业学生自荐信格式
2013/09/22 职场文书
仓库主管的岗位职责
2013/12/04 职场文书
私用公车造成事故检讨书
2014/11/16 职场文书
大学生村官个人总结
2015/02/15 职场文书
离职证明格式样本
2015/06/12 职场文书
JavaScript实现优先级队列
2021/12/06 Javascript
【DOTA2】当街暴打?PSG LGD vs VG - DPC 2022 WINTER TOUR CN
2022/04/02 DOTA
Springboot-cli 开发脚手架,权限认证,附demo演示
2022/04/28 Java/Android