JavaScript 2019 新特性和使用技巧

JavaScript 作为最流行的编程语言,也是web开发的主要内容。每次迭代都会出现新颖、便捷的特性。下面介绍2019新特性及其在一些场景下更简便的使用技巧。

# Array.flat()

flat()将嵌套数组递归展平到指定的深度。默认参数值为1,如果要进行全深度,参数设成Infinity。此方法不修改原始数组,而是创建一个新数组。

const arr1 = [1, 2, [3, 4]]
arr1.flat() // [1, 2, 3, 4]

const arr2 = [1, 2, [3, 4, [5, 6]]]
arr2.flat() // [1, 2, 3, 4, [5, 6]]

const arr3 = [1, 2, [3, 4, [5, 6]]]
arr3.flat(2) // [1, 2, 3, 4, 5, 6]

const arr4 = [1, 2, [3, 4, [5, 6, [7, 8]]]]
arr4.flat(Infinity) // [1, 2, 3, 4, 5, 6, 7, 8]

如果原数组有空位,flat()方法会跳过空位。

const arr5 = [1, 2, , 4, 5]
arr5.flat() // [1, 2, 4, 5]

Usage

将下列数组中所有students中的项整合成一个扁平化一维数组

const courses = [
  {
    subject: "math",
    numberOfStudents: 3,
    waitlistStudents: 2,
    students: ['Janet', 'Martha', 'Bob', ['Phil', 'Candace']]
  },
  {
    subject: "english",
    numberOfStudents: 2,
    students: ['Wilson', 'Taylor']
  },
  {
    subject: "history",
    numberOfStudents: 4,
    students: ['Edith', 'Jacob', 'Peter', 'Betty']
  }
]
1. 以前的实现方式:
const courseStudents = courses.map(course => course.student)
// [
//   [ 'Janet', 'Martha', 'Bob', [ 'Phil', 'Candace' ] ],
//   [ 'Wilson', 'Taylor' ],
//   [ 'Edith', 'Jacob', 'Peter', 'Betty' ]
// ]
const tempArr = [].concat.apply([], courseStudents)

;[].concat(...tempArr)

以上方式仅仅是二维数组实现起来都已经比较复杂了,对于多维数组通过递归的方式更复杂了。而通过 flat() 一行代码即可搞定。

2. 通过 flat 实现
courses.map(course => course.student).flat(Infinity)
// [
//   'Janet',   'Martha',
//   'Bob',     'Phil',
//   'Candace', 'Wilson',
//   'Taylor',  'Edith',
//   'Jacob',   'Peter',
//   'Betty'
// ]

# Array.flatMap()

flatMap()方法对原数组的每个成员执行一个函数(相当于执行map()),然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组。

const arr1 = [1, 2, 3];

arr1.map(x => [x * 4]); // [[4], [8], [12]]
arr1.flatMap(x => [x * 4]); // [4, 8, 12]

flatMap()只能展开一层数组。

[1, 2, 3, 4].flatMap(x => [[x * 2]]) 
// [[2], [4], [6], [8]]

flatMap()方法的参数是一个遍历函数,该函数可以接受三个参数,分别是当前数组成员、当前数组成员的位置(从零开始)、原数组。
flatMap()方法还可以有第二个参数,用来绑定遍历函数里面的this。

Usage

将下列数组中每一项加7,然后插入当前元素之后。

const grades = [78, 62, 80, 64]
实现方式:
  1. map/concat
const curved = grades.map(grade => [grade, grade + 7])
// [ [ 78, 85 ], [ 62, 69 ], [ 80, 87 ], [ 64, 71 ] ]

;[].concat.apply([], curved)
// [ 78, 85, 62, 69, 80, 87, 64, 71 ]
  1. map/flat
grades.map(grade => [grade, grade + 7]).flat()
  1. flatMap (相当于先map, 再flat(1))
grades.flatMap(grade => [grade, grade + 7])

如果将加7之后的结果放入数组,然后插入当前项之后。

grades.flatMap(grade => [grade, [grade + 7]])
// [
//   78, [ 85 ],
//   62, [ 69 ],
//   80, [ 87 ],
//   64, [ 71 ]
// ]

# String.trimStart() 和 String.trimEnd()

trim() 是删除字符串两边的空格,而 trimStart() 和 trimEnd() 删除字符串开头和末尾由空格键、tab 键、换行符和不可见的空白符号产生的空格。

const test = " hello "

test.trim()       // "hello"
test.trimStart()  // "hello "
test.trimEnd()    // " hello"

# Object.fromEntries

Object.fromEntries()是Object.entries()的逆操作,用于将一个键值对数组转为对象。

1. Object.entries()

const obj = { bar: 'bar', foo: 'foo' }
const array = Object.entries(obj);
// [["prop1", 2], ["prop2", 10], ["prop3", 15]]

2. Object.fromEntries()

Object.fromEntries([
  ['foo', 'bar'],
  ['baz', 42]
])
// { foo: "bar", baz: 42 }

该方法的主要目的,是将键值对的数据结构还原为对象,因此特别适合将 Map 结构转为对象。

const entries = new Map([
  ['foo', 'bar'],
  ['baz', 42]
]);

Object.fromEntries(entries)
// { foo: "bar", baz: 42 }

Usage

将属性值大于20的项组成一个新对象

let students = {
  amelia: 20,
  beatrice: 22,
  cece: 20,
  deirdre: 19,
  eloise: 21
}
1. 传统方式
const overTwenty = Object.entries(students).filter(([name, age]) => age > 20) 
const DrinkingAgeStudents = {}

for (const [name, age] of overTwentyOne) {
  DrinkingAgeStudents[name] = age
}
2. Object.fromEntries()
Object.fromEntries(overTwenty)

# catch 参数可选

新特性允许忽略catch() 参数,因为在很多情况下并不会用到。

try {
  //...
} catch {
  //handle error without parameter
}

# Symbol.description

为了区分Symbol,通过给参数添加描述,读取描述时需要讲Symbol转成字符串:

const symbol = Symbol('foo');

String(symbol) // "Symbol(foo)"
symbol.toString() // "Symbol(foo)"

ES2019 提供了一个实例属性description,直接返回 Symbol 的描述。

const symbol = Symbol('foo');

symbol.description // "foo"

# Function.toString()

以前toString()方法会省略注释和空格。

function /* foo comment */ foo () {}

foo.toString()
// function foo() {}

ES2019 返回一模一样的原始代码。

function /* foo comment */ foo () {}
foo.toString(); // "function /* foo comment */ foo () {}"

# JSON.parse() 改进

行分隔符 (\u2028) 和段落分隔符 (\u2029),现在被正确解析,而不是报一个语法错误。

const str = '{"name":"Bottle\u2028AnGe"}'
JSON.parse(str)
/* {name: "Bottle
AnGe"}
/*

# JavaScript optional chaining

我们在开发中经常会得到缺少预期属性的对象,甚至整个对象都是null。因此,如果我们的某个对象没有所需的属性,并且我们尝试访问其属性的属性,则会报错:

const user = {
  address: {
    // city: {
    //   name: "London",
    //   code: 20
    // }
  }
};

const cityCode = user.address.city.code;
// Uncaught TypeError: Cannot read property code of undefined
通常的解决方式:
  1. 检查对象中某个属性是否存在:
const cityCode1 = user.address && user.address.city && user.address.city.code;

const cityCode2 = (((user || {}).address || {}).city || {}).code;

try {
  const cityCode3 = user.address.city.code;
} catch (e) {}

或者使用类似于lodash的三方工具库:

import _ from "lodash";

_.get(cityCode, "user.address.city.code");
  1. 新增特性 可选链式运算符 ?

使用可选的链接运算符来检测左侧的属性是否有值,如果值为null 或 undefined,则短路并返回undefined。这样就可以安全放心的使用深层嵌套对象的属性,即便它们可能不存在,我们将 ?链接到我们想要访问的属性即可。

const user = {
  address: {
    city: {
      name: "London"
      //code: 20
    }
  },
  firstName: "Jane",
  lastName: "Doe",
  getFullName() {
    return `${this.firstName} ${this.lastName}`;
  }
};

user?.address?.city?.code; // undefined

使用括号表示,获取动态添加的属性

let prop = "address";

user?.[prop]?.city?.name; // “London”

甚至可以调用可能不存在的函数:

user?.getFullName?.(); // "Jane Doe"

还可访问数组中的元素

const arr = ["Jane", "Mark", "Joan"];
arr?.[1] // 'Mark'

或者删除属性

delete user?.address?.city?.code; 
  1. Babel插件

该功能长期以来一直是C#,Groovy和Swift等许多语言的一部分,它最终在不久的将来会出现在JavaScript中。但是,如果您现在想要开始使用它,那么Babel的人们会为您提供他们的插件。只需确保使用Babel7并安装插件:

npm install --save-dev @babel/plugin-proposal-optional-chaining

然后将其添加到.babelrc配置中:

{
  "plugins": ["@babel/plugin-proposal-optional-chaining"]
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,640评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,254评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,011评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,755评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,774评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,610评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,352评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,257评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,717评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,894评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,021评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,735评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,354评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,936评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,054评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,224评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,974评论 2 355