大厂技术 坚持周更 精选好文
原文地址:ES2020 Optional Chaining and Dynamic Imports Are Game-Changers. Here’s Why.
原文作者:Mahdhi Rezvi
译文出自:掘金翻译计划
本文永久链接:https://github.com/xitu/gold-miner/blob/master/article/2020/es2020-optional-chaining-and-dynamic-imports-are-game-changers-heres-why.md
译者:JohnieXu、jaredliw
校对者:ghost、wuyanan、Yang Mao
自从 2015 年发布 ES6 语法规范以来,JavaScript 又进行了几次升级。之后,大量的语法特性提案也被纳入到 ES2020 语法规范中。其中有不少值得一提的新特性,我在此文将重点介绍可选链(Optional Chaining)和模块动态导入(Dynamic Imports)这两个特性。如果能在以前的项目中使用上这两大特性,代码量将会大大缩减。
下面我们一起来看一看这两大特性。
可选链
借助可选链特性我们可以通过 ?. 操作符在访问对象嵌套的属性而不需要校验这些属性是否存在。?.
操作符与传统的.
操作符十分相似,但是在操作符前面的值为undefined
或null
时该语法不会导致程序报错,并且其获取到的属性值为undefined
。
使用可选链特性可以使代码变得更简洁的同时确保在访问对象中不确定是否存在的属性时不会报错。
语法
参考官方文档,可选链的语法包含以下几种,分别为:访问对象属性、访问数组元素及函数调用。
obj.val?.prop
obj.val?.[expr]
obj.arr?.[index]
obj.func?.(args)
用法示例
如上文所述,可选链的语法分为访问对象属性、访问数组元素和函数调用这 3 类。下面一起看下他们的用法示例。
通过属性访问对象值
let book = {
name: "Harry Potter 1",
price: {
value: 50,
currency: "EUR"
},
ISBN: "978-7-7058-9615-2"
};
console.log(book.price.value);
// 50
console.log(book.weight);
// undefined
console.log(book.weight.value);
// 报错
// 使用可选链
console.log(book?.weight?.value);
// undefined
通过表达式访问对象值
let book = {
name: "Harry Potter 1",
price: {
value: 50,
currency: "EUR"
},
ISBN: "978-7-7058-9615-2"
};
let userInputProperty = document.getElementById('inputProperty').value;
let userInputNestedProperty = document.getElementById('inputPropertyNested').value;
console.log(book[userInputProperty][userInputNestedProperty]);
// 如果用户在 input 上输入的属性在对象上不存在,JavaScript 将会报错
// 使用可选链
console.log(book?.[userInputProperty]?.[userInputNestedProperty]);
// 如果用户在 input 上输入的属性在对象上不存在,将返回 undefined
// 否则返回属性对应的值
数组元素访问
let books = [{
name: "Harry Potter 1",
price: {
value: 50,
currency: "EUR"
},
ISBN: "978-7-7058-9615-2"
},
{
name: "Harry Potter 2",
price: {
value: 60,
currency: "EUR"
},
ISBN: "978-3-2560-1878-4"
}
];
console.log(books[1].price.value);
// 50
console.log(books[2].price.value);
// 报错
console.log(books?.[2]?.price?.value);
// undefined
函数访问
let bookModule = {
getBooks: function() {
console.log("Books returned");
return true;
}
};
bookModule.getBooks();
// "Books returned"
bookModule.getBook();
// 报错
bookModule?.getBook?.();
// undefined
注意事项
可选链只在赋值表达式的右侧生效。
let book = {
name: "Harry Potter 1",
price: {
value: 50,
currency: "EUR"
},
ISBN: "978-7-7058-9615-2"
};
book?.weight?.value = 650;
// 语法错误
book.weight.value = 650;
// 报错:TypeError
book.weight = {
value: 650
};
// 正常
使用可选链前后对比
在引入可选链特性之前,通常需要对每一层级的属性值是否存在做校验以避免报错。这会导致随着嵌套层级的增加,需要校验的属性值也随之增加。这就意味着要避免出现获取undefined
或null
的属性值导致程序崩溃,你需要对每一层级的属性值获取都进行校验。
下面看一下示例。
let book = {
name: "Harry Potter 1",
price: {
value: 50,
currency: "EUR"
},
ISBN: "978-7-7058-9615-2",
weight: {
version1: {
value: 550,
unit: 'g'
},
version2: {
value: 690,
unit: 'g'
}
}
}
if (book && book.weight && book.weight.version2 && book.weight.version2.value) {
// 操作 book.weight.version2.value
console.log(book.weight.version2.value);
}
if (book && book.weight && book.weight.version3 && book.weight.version3.value) {
// 由于 book.weight 上没有属性 version3,所以以下代码块将不会执行,同时还会报错
console.log(book.weight.version3.value);
}
// 直接访问 book.weight.version3 属性,不做校验
console.log(book.weight.version3.value);
// 报错
下面是使用可选链的示例,用更少的代码实现了同样的功能。
let book = {
name: "Harry Potter 1",
price: {
value: 50,
currency: "EUR"
},
ISBN: "978-7-7058-9615-2",
weight: {
version1: {
value: 550,
unit: 'g'
},
version2: {
value: 690,
unit: 'g'
}
}
}
// 操作 book.weight.version2.value
console.log(book?.weight?.version2?.value);
// 690
console.log(book?.weight?.version3?.value);
// undefined
即使book.weight
上不存在version3
这个属性,并且我们也没有显式的校验book.weight.version3
是否是null
或undefined
,但是上述代码还是正常执行没有报错,这是主要是因为可选链在访问属性之前会校验是否是undefined
或null
。
浏览器兼容性
动态导入
动态导入使得应用可以以原生的方式(译者注:之前的动态导入都是由打包工具,如 webpack 在本地编译打包的方式实现的)把 .js 文件作为模块动态地进行导入。在 ES2020 之前,模块无论是否被使用到了都会被导入进来。这一特性,将极大的提升网页应用的性能。
为何需要动态导入
之前的模块导入是基于静态声明的,而import
语句只能放置在文件的顶部。并且,import
要求传入的模块描述符必须是字符串字面量,不能包含变量。这些限制使得现在的动态导入访问远比静态声明的模块导入方案灵活。
但是静态模块导入也有其优点,比如它支持模块静态分析、打包构建工具、Tree-Shaking 优化等。
不过静态模块导入语法不支持以下特性:
模块按需导入;
在模块描述符中使用变量,具体值在运行时才确定;
使用
script
标签导入模块。
使用动态导入特性就能解决上述几个问题。
接下来看一下使用模块动态导入特性后对项目有何改变。
使用模块动态导入前后对比
假设我们的应用有一个导出为 XML 或 CSV 格式文档的功能,团队对实现这两种格式导出功能分别开发了一个模块。按之前的导入方式,模块代码量会很大并且导致页面加载缓慢。
静态模块导入
import { exportAsCSV } from './export-as-csv.js';
import { exportAsXML } from './export-as-xml.js';
const dataBlock = getData();
const exportCSVButton = document.querySelector('.exportCSVBtn');
const exportXMLButton = document.querySelector('.exportXMLBtn');
exportCSVButton.addEventListener('click', () => { exportAsCSV(dataBlock) });
exportXMLButton.addEventListener('click', () => { exportAsXML(dataBlock) });
可以看到,exportAsCSV
和exportAsXML
模块不管有没有被实际用到都会先导入到应用中。实际上,并非所有用户都会用到这些功能,这会给浏览器增加不必要的负担。
这种额外的负担可以通过模块的推迟加载来避免,目前已经可以借助 webpack 等模块打包器的代码分割功能的来实现。
但是在 ES2020 标准规范中,已经原生支持这一特性,就不需要使用模块打包器的代码分割功能了。
动态模块导入
const dataBlock = getData();
const exportCSVButton = document.querySelector('.exportCSVBtn');
const exportXMLButton = document.querySelector('.exportXMLBtn');
exportCSVButton.addEventListener('click', () => {
import('./export-as-csv.js')
.then(module => {
module.exportAsCSV(dataBlock);
})
.catch(err => {
// 处理模块加载失败的错误
})
});
exportXMLButton.addEventListener('click', () => {
import('./export-as-xml.js')
.then(module => {
module.exportAsXML(dataBlock);
})
.catch(err => {
// 处理模块加载失败的错误
})
});
上面代码中,使用动态导入语法实现了两个模块的动态导入。只有在对应模块对使用到时,其模块代码才会被加载,这样就减少了页面加载资源的大小并缩短了页面加载时间。
浏览器兼容性
以上两个备受期待的特性能极大地帮助你在不牺牲整体性能的情况下编写更简洁、干净的代码。如果有任何建议和看法,欢迎到评论区留言。
参考文章
MDN Docs
ES2020 Proposal
V8 Docs
❤️ 谢谢支持
以上便是本次分享的全部内容,希望对你有所帮助^_^
喜欢的话别忘了 分享、点赞、收藏 三连哦~。
欢迎关注公众号 趣谈前端 收获大厂一手好文章~
LowCode可视化低代码社区介绍
LowCode低代码社区(http://lowcode.dooring.cn)是由在一线互联网公司深耕技术多年的技术专家创办,意在为企业技术人员提供低代码可视化相关的技术交流和分享,并且鼓励国内拥有相关业务的企业积极推荐自身产品,为国内B端技术领域积累知识资产。同时我们还欢迎开源大牛们分享自己的开源项目和技术视频。
如需入驻请加下方小编微信: lowcode-dooring