现代 JavaScript 你不可不了解的 10 件事

可能你对JavaScript全然陌生,可能多年来你只是偶尔用用。但有一点你应该谨记心中——随着时间的变化,现代JavaScript的一些特性变得很有用。

1. 扩展运算符

在对象或数组之前标注...,对象或数组就可以转换为以逗号分隔的列表。示例如下:

展开数组:

let firstHalf = [ 'one''two'];
let secondHalf = ['three''four', ...firstHalf];

这种编写方式很紧凑。不这样做就意味着需要编写如下代码:

不展开数组:

let firstHalf = [ 'one''two'];


let secondHalf = ['three''four'];
for(var i=0, i <firstHalf.length; i++ ) {
  secondHalf.push(firstHalf[i]);
}

这也可以用于对象作为合并属性的一种方式:

展开对象:

const hero = {
  name: 'Xena - Warrior Princess',
  realName: 'Lucy Lawless'
}


const heroWithSword = {
 ...hero,
 weapon: 'sword'
}

下面这样做会很麻烦,因为将遍历对象上的所有属性:

不展开对象:

let keys = Object.keys(hero);
let obj = {};

for(var i=0; i< keys.length; i++) {
   obj[keys[i]] = keys[props[i]];
}

说老实话,我们也可以使用Object.assign()来实现,如下代码:

const heroWithSword = Object.assign({}, hero, {weapon:"sword"})

但我仍然认为使用展开符的代码可读性更好:

const heroWithSword = {
 ...hero,
 weapon: 'sword'
}

2. rest参数

rest参数表示将剩余参数接收到一个数组中。JavaScript能够灵活地设置你输入参数的数量。arguments变量通常用来接收这些参数。请看代码:

function add(firstsecond, ...remaining) {
  return first + second;
}

上面只使用了参数firstsecond。这意味着使用add(1,2)add(1,2,3, 4)调用会产生相同的结果。要解决此问题,用如下代码即可:

function add(firstsecond, ...remaining) {
  return first + second + remaining.reduce((acc, curr) => acc + curr, 0);
}

以上代码使用了所有输入参数。

如前所述,使用rest参数,即添加前缀...作为接收剩余参数的一种方式,可以让我们在定义方法时更明确地表达方法所代表的含义。

3. 字符串插值

你见过这样的声明吗?

class Product {
 constructor(name, description, price) {
   this.name = name;
   this.description = description;
   this.price = price;
 }

 getDescription() {
   return " Full description \n" + 
   " name: " + this.name + 
   " description: " + this.description

 }
}

这里的getDescription()方法,语句既长又难以阅读。现实中,大多数编程语言中这样的方法比比皆是。幸运的是,JavaScript可以使用字符串插值。我们可以把getDescription()方法变成:

getDescription() {
   return `Full description \n: 
   name: ${this.name}
   description ${this.description}
   `;
 }

在这里,双反引号是我们用来定义多行字符串的。而${}则用于插值。

4. 简写属性

你可能在不知情的时候已经用过了简写属性。在ES5中,你必须这样编写代码:

function createCoord(x, y) {
  return {
    x: x,
    y: y
  }
}

而在ES6及更高版本中,如果名称相同,则可以省略:右侧的内容,如下所示:

function createCoord(x, y) {
  return {
    x,
    y
  }
}

看起来不那么杂乱了对吧?

5. 方法属性

用于定义指向对象中方法的属性。请看以下ES5示例:

const math = {
  add: function(a,b) { return a + b; },
  sub: function(a,b) { return a - b; }, 
  multiply: function(a,b) { return a * b; }
}

在ES6和之后的版本中,实际上并不需要添加完整的add:。你可以像这样写:

const math = {
  add(a,b) { return a + b; },
  sub(a,b) { return a - b; },
  multiply(a,b) { return a * b; }
}

6. 解构

对象解构

考虑以下代码:

function handle(req, res) {
 const name = req.body.name;
 const description = req.body.description;
 const url = req.url;

 log('url endpoint', url);

 // lots of logic happening
 dbService.createPerson( name, description )
}

虽然上面的代码并不完美,但它确实表示了我们想要从不同级别的对象中挖掘数据的情况。这时,如果你说你不想声明所有这些变量,想少写一些代码,怎么办?可以这样做:

function handle(req, res) {
 const { body: { name, description }, url }, = req;

 log('url endpoint', url);

 // lots of logic happening
 dbService.createPerson( name, description )

看到没?三行如何变成一行了。

数组解构

解构不限于对象。也可以在数组上完成。请看以下代码:

const array = [1,2,3,4,5,6];

const a = array[0];

const c = array[2];

还可以更优雅,如下所示:

const array = [1,2,3,4,5,6];
const [a, ,c, ...remaining] = arr;

// remaining = [4,5,6]

我们可以通过上述模式匹配从数组中取出值。如果想跳过一些内容,可以键入, ,,然后额外添加一个REST语句就能获取剩余的数组项。

参数匹配

我们也可以对函数及其参数执行此操作。当函数中有超过 2-3个参数时,接收对象中的所有参数已成为实际操作中的标准做法,并得到如下所示的函数:

function doSomething(config) {
  if(config.a) { ... }
  if(config.b) { ... }
  if(config.c) { ... }
}

更好的方法是:

function doSomething({ a, b, c }) {
  if(a) { ... }
  if(b) { ... }
  if(c) { ... }
}

7. 数组方法

ES6带来了一大堆可用的数组方法,例如:

  • find():在列表中查找一个项目,否则为null
  • findIndex():查找项目的索引
  • some():列表中至少一项为真
  • includes():判断是否是列表的项目

请看以下代码以便于了解用法:

const array = [{ id: 1, checked: true }, { id: 2 }];
arr.find(item => item.id === 2) // { id: 2 }
arr.findIndex(item => item.id === 2) // 1
arr.some(item => item.checked) // true

const numberArray = [1,2,3,4];
numberArray.includes(2) // true

8. promises+async/await

promises

我们知道回调,如下所示:

function doSomething(cb) {
  setTimeout(() =>  {
    cb('done')
  }, 3000)
}

doSomething((arg) => {
 console.log('done here', arg);
})

这样就可以处理一些异步且只需要时间来完成的操作。然后大家开始使用promise库,并且最终得到了该语言的原生支持。所以现在我们可以做这样的事情:

function doSomething() {
  return new Promise((resolve, reject) => {
    setTimeout(() =>  {
      resolve('done')
    }, 3000)
  })
}

doSomething().then(arg => {
 console.log('done here', arg);
})

我们甚至可以进行链式调用,类似下面的这种调用方式:

getUser()
  .then(getOrderByUser)
  .then(getOrderItemsByOrder)
  .then(orderItems => {
    // do something with order items
  })

async/await

接着我们迎来了async/await,工作变得更美好起来。还是上面的例子,现在Promises变成了这样:

async function getItems() {
  try {
    const user = await getUser();
    const order = await getOrderByUser(user);
    const items = await getOrderItemsByOrder(order);
    return items;
  } catch(err) {
    // 异常处理程序
  }
}

getItems().then(items => {
  // 排序操作
})

我们得到了看起来同步的异步代码。

9. 模块

几乎任何编码语言都支持模块的概念。请看以下代码:

// math.js

export function add(a,b) { return a + b; }
export function sub(a,b) { return a - b; }

export default (a,b) => a * b;

// main.js
import mult, { add, sub } from './math';

mult(24// 8
add(1,1)   // 2
sub(1,2)   // -1

上面我们使用export关键字来表示这些构造,addsub可用于任何导入此模块的模块。如果我们只导入,则可以得到export default关键字。在main.js中,我们将默认值导入为具有名称mult,并且我们还特别选择了方法add()sub()

10. 箭头函数 + this

箭头函数是另一种函数符号。过去我们只能这样写函数:

function printArray(arr) {
 // do something
}

现在我们可以这样定义:

const printArray = (arr) => {
 // do something
}

单行函数

我们还可以通过一行代码定义函数:

const add = (a,b) => a + b

这将自动执行操作并返回结果。要想做同样的事情并返回一个对象,语法就变成了:

const create = (a,b) = > ({ x: a, y: b })

this关键字

很多程序员曾经面临的问题是不知道this是什么。考虑以下问题:

let array = [1,2,3];

function sum() {
  this.total = 0;

  arr.forEach(function(item) {
    this.total+= item;  // `this` is the inner functions `this`, BAD
  })
  return total;
} 

在上述情况下,thisforEach内部是错误的。我们过去解决这个问题的方法是:

function sum() {
  this.total = 0;
  var self = this;

  arr.forEach(function(item) {
    self.total+= item;  // now we are using `self`, it solves it but feels hacky
  })
  return total;
}

箭头函数解决了这个问题,不再是self,所以现在代码如下所示:

function sum() {
  this.total = 0;

  arr.forEach((item) => {
    this.total+= item;  // all is well `this` points to outer function
  })
  return total;
}

最后,文章中提到的这10个特性,是不是特别棒!