关于ECMAScript 2019规范的新增特性
关于ECMAScript 2019规范的新增特性
String.prototype
上的match()
方法仅返回完全匹配,但是没有返回关于特定正则组的任意信息。感谢Jordan Harband关于String.prototype.matchAll
的提案,可以返回比match()
多很多的信息。返回的迭代器除了精确匹配外还给了我们访问所有的正则匹配捕获组。你还记得Gorkem Yakin和Daniel Ehrenberg添加到ECMAScript 2018的具名捕获组吗?matchAll()
方法和此能很好的协调。通过下面例子来解释一下。
1 | const text = "From 2019.01.29 to 2019.01.30"; |
1 | const text = "From 2019.01.29 to 2019.01.30"; |
不同于ECMAScript 2015中介绍的静态模块,Domenic Denicola提案的动态导入
可以实现按需加载。这个类似函数的格式(不是继承自Function .prototype
)返回一个很强大的promise。使用场景比如: 按需导入,在一个脚本中计算模块名并加载执行变得可能。
1 | const modulePage = 'page.js'; |
1 | (async () => { |
感谢Daniel Ehrenberg, Number.MAX_SAFE_INTEGER
不再是JavaScript中的一个限制。BigInt
是一个能表示任意精度整数的新基础类型。你可以通过使用BigInt
方法或者在一个数字后添加n
后缀来把一个数字转换为一个新的bigint
类型。
1 | Number.MAX_SAFE_INTERGER |
自从ECMAScript 2015以来,JavaScript仅支持两种promise组合: Promise.all()
和Promise.race()
。感谢Jason Williams, Robert Pamely and Mathias Bynens,现在我们可以使用Promise.allSettled()
。用这个方法来处理所有promise都解决时的场景(不管成功或失败)。看看下面的例子,并没有使用catch捕获异常!
1 | Promise.allSettled([ |
还有Promise.any()
有潜力很快进入ECMAScript规范中,在文章“Promise组合解释”中介绍了相关内容。
那么在JavaScript中什么是全局的this
?是在浏览器中的window
,在worker中的self
,在Nodejs中的global
或者其他… 这种混乱结束了!感谢Jordan Harband,我们现在可以使用globalThis
关键字了。
ECMAScript遗留了一个关于for-in循环顺序的详细描述。感谢Kevin Gibbons所付出的努力,为for-in机制定义了一系列规则。(原文: Thanks to Kevin Gibbons who finally put some TLC and defined a set in stone set of rules for for-in mechanics.)
读取层次很深的对象属性时通常是容易出错并且对应代码也不易阅读。感谢Gabriel Isenberg, Claude Pache and Dustin Savery,这件事情现在变得简单了。如果你是一个TypeScript用户,那么你不会发现什么新的特性,因为在3.7版本中TypeScript已经实现了这个特性。喜欢!
1 | // 之前 |
空值联合添加了一个新的短路原则操作符来处理默认值。Gabriel Isenberg做了很棒的工作。这个特性结合optional chanining特性使用。不同于||
操作符,空值联合操作符??
仅在左边的值为严格的null
或undefined
时起左右。
1 | "" || "default value" |
1 | const title = data?.article?.title ?? "What's new in ECMAScript 2020" |
Domenic Denicola提出的import.meta提案添加一个host相关的元数据对象到当前执行的模块中。
1 | console.log(import.meta.url) |
这是一个添加到规范中的有用特性,可以让开发者导出其他模块命名空间下的对象到一个新的名称下。
1 | export * as ns from "mod" |
面试中遇到关于JavaScript中new关键字背后的实现原理,了解大概的原理,但是表达出来不是很清楚,表示掌握得不够完全,这里查了一些资料,做一下整理。
例如我们做了new的调用操作,
1 | new ConstructorFunction(arg1, arg2); |
背后实际上发生了这些步骤:
__proto__
进行访问,ES5开始可通过Object.getPrototypeOf(obj)
取得)为构造函数的prototype
属性(每个函数对象都拥有一个prototype
属性)this
变量指向这个新创建的对象这里涉及到几个概念:
[[prototype]]
表示,在部分浏览器中使用__proto__
(非标准的,不建议使用)来表示,ES5开始可使用Object.getPrototypeOf()
读取,ES6开始可使用Object.setPrototypeOf()
方法进行设置(仅支持完全替换对象或者设为null)prototype
属性比较难理解的是[[prototype]]
这个属性,每个对象都拥有一个内部的[[prototype]]
属性。这个对象是创建对象的时候设置的,创建包括new
、通过Object.create()
或者用文本字面量,并且只能通过Object.getPrototypeOf()
和Object.setPrototypeOf()
方法进行操作。
说明
一旦通过new操作实例化一个对象后,如果这个实例上查找某个属性并不存在,脚本会通过[[prototype]]
对象向上一级继续查找,也就是通过原型链的方式进行往上查找。这种方式和在传统的类继承方式是类似的,在JavaScript中通过原型链的形式来继承父类的属性和方法。
函数中,除了拥有隐藏的[[prototype]]
属性,还有一个prototype
属性,这个属性可以访问、修改和添加希望给实例继承的属性和方法。
原型链实例
1 | ObjMaker = function() { this.a = 'first'; } |
继承实例
1 | SubObjMaker = function() {}; |
1 | function newOperator(ConStr, args) { |
欢迎访问我的博客 https://blog.bookcell.org
模块是通过不同于脚本的形式进行加载的JavaScript文件。区别在于:
this
是undefined
<!-- comments -->
使用export
关键字可以从模块中导出变量、函数和类:
1 | // export data |
导出说明:
default
关键字外无法使用上面的语法导出匿名函数和类multiply()
函数,你可以不导出声明,支持导出引用substract()
函数,外部不可访问,没有显式导出的变量、函数和类对于模块是私有的基本格式:
1 | import { identifier1, identifier2 } from "./example.js"; |
说明: 从一个模块中导入一个绑定,就类似定义一个const
的内容,也就是无法定义重名变量、导入前使用和修改值。
1 | // import just one |
尽管example.js
中定义了多个导出,你可以只导入其中一个;为了在浏览器和Node.js中保持兼容性,尽量在文件名前使用相对路径。
1 | // import multiple |
特殊的应用场景支持导入整个模块作为一个对象,类似导入一个库文件,所有的模块导出均作为对象属性。
1 | // import everything |
不管多少次使用import
对一个模块进行导入操作,模块只会执行一次;在导入模块的代码执行后,实例化的模块在保存在内存中并在其他import
语句使用时复用。
1 | import { sum } from "./example.js"; |
代码中example.js
只会执行一次.
模块语法不支持在语句或者函数中使用,也就是说不支持动态的导入和导出。ECMAScript dynamic-import Stage3
对导入的内容是无法修改的,但是在模块内部可以。
模块导出:
1 | export var name = "Nicholas"; |
模块导入:
1 | import { name, setName } from "./example.js"; |
setName
函数回到模块内执行并修改变量name
的内容,随后这个修改会反映到导入的name
这个绑定。
有时候,你可能不想使用变量、函数、类在模块中的原始名字,你可以在导入和导出的时候使用as
关键字来指定新的名字
导出时重命名
1 | function sum(num1, num2) { |
导入
1 | import { add } from "./example.js"; |
导入时重命名
1 | import { add as sum } from "./example.js"; |
模块中的默认值是指使用default
指定的一个单独变量、函数或者类,每个模块中只能设定一个默认导出,指定多个默认导出会报语法错误。
直接导出
1 | export default function(num1, num2) { |
函数可以不写名字,因为模块本身代表这个函数。
导出引用
1 | function sum(num1, num2) { |
重命名导出
1 | function sum(num1, num2) { |
javascript
// import the default
import sum from “./example.js”;
console.log(sum(1, 2)); // 3
1 |
|
1 | import sum, { color } from "./example.js"; |
默认值要在非默认值前面导入。
重命名默认值
1 | // equivalent to previous example |
几种重新导出的方式
1 | import { sum } from "./example.js"; |
1 | export { sum } from "./example.js"; |
1 | export { sum as add } from "./example.js"; |
全部导出
1 | export * from "./example.js"; |
这里只能导出所有的具名导出,不包括默认导出。如果要处理默认导出,你需要显式的导入后再导出。
有些模块并没有导出任何内容,仅仅是在全局作用域中做了些修改;虽然模块无法直接修改全局作用域的变量、函数和类,但是对于內建对象的修改可以反映到其他模块中。
1 | // module code without exports or imports |
这个模块对Array
原型增加一个pushAll
方法.
1 | import "./example.js"; |
这段代码导入和执行模块代码,添加pushAll
方法到array原型中。
没有绑定的导入通常用于创建polyfill或者shim。
使用打包工具和Babel转译为ES5
使用script
标签,并设置type
为module
(IE11不兼容,使用前查看兼容性)
默认使用defer
属性,按照代码出现的顺序进行加载,执行需要在文档解析完成后,执行顺序也是按照代码出现的顺序。
1 | <!-- this will execute first --> |
如果模块代码中又引入其他的模块,执行的顺序会变得复杂一些:
加载顺序:
执行顺序:
1 | // load module.js as a module |
对比CommonJS和AMD
在Node.js中的主要实现方式,特性如下:
主要的实现是RequireJS
,特性如下:
目标:
优势:
八卦: ES6 Module的主要设计者Dave Herman和Sam Tobin Hochstadt。
组件化参考张云龙-前端工程
这里主要是针对ES6 Module技术出现的讨论,对比历史的一些模块化解决方案。
简单的讲,随着前端应用的日益庞大和复杂,对于代码的拆分复用要求更高,也就出现了对代码模块化的需求。
在ES6 Module出现前,社区中出现的两个方案CommonJS和AMD:
在Node.js中的主要实现方式,特性如下:
主要的实现是RequireJS
,特性如下:
这里提一句,ES6 Module的主要设计者Dave Herman 和 Sam Tobin Hochstadt。
这里讲的ES6 Module的设计初衷,在ES6 Module出现前社区已经在模块化上有一些令人印象深刻的变通方案,以CommonJS和AMD为代表,这两者各有优缺点并且不兼容,因此ES6 Module的设计初衷是为了吸取这两者的优点实现ECMAScript的标准。
ES6 Module的目标是出一个让CommonJS和AMD社区都能接受的方案:
ES6 Module实际达成的方案特性如下:
适用于浏览器端和服务端(Nodejs),目前(2019年)主流的浏览器大部分都不支持ES6 Module的特性,因此使用的话还得使用Babel来转译。
ES6 Module分两部分:
<script>
进行加载通过type
属性值为module
来标识模块进行加载,支持外部文件和内联的方式,默认使用defer
的行为,也可以指定async
1 | <!-- load a module JavaScript file --> |
Worker
进行加载1 | // load module.js as a module |
使用webpack
或者browserify
等打包工具进行打包后使用。
声明式语法分为两种类型:具名导出(每个模块有多个)和默认导出(每个模块一个)。
通过关键字export
前缀可以导出多个内容,并有名称来进行区分。
1 | //------ lib.js ------ |
一个模块只有一个默认导出,并且默认导出尤其容易进行导入。
1 | //------ myFunc.js ------ |
对于class的使用
1 | //------ MyClass.js ------ |
默认导出其实可以理解为特殊的具名导出
导入:
1 | import { default as foo } from 'lib'; |
导出:
1 | //------ module1.js ------ |
The answer is that you can’t enforce a static structure via objects and lose all of the associated advantages (described in the next section).
如果通过对象进行导出会丢失静态结构,从而失去原先的优势。
针对ES6 Module的编程式实现,目前规范处于stage 3。
使用编程式语法,可以做到:
1 | <!DOCTYPE html> |
有关模块化的进程,前端模块化详解一句讲解得比较详细了,可以参考。
记录JavaScript中创建对象的几种方法。
工厂模式是一种设计模式,抽象创建具体对象的过程,用函数来封装特定接口并创建对象的细节。
实例:
1 | /* 工厂模式 */ |
优点:
解决创建多个相似对象。
缺点:
没有解决对象识别的问题。
Web前端中有时会需要将数据通过图表的形式展示,而选择一个JavaScript的可视化库是比较重要的。
选择需要多个因素进行权衡,项目的需求、开发周期、人员的技术、文档、问题的解决、功能,下面是针对
github上星数较多的数据可视化库针对各角度做一下对比,方便后续的选型有所帮助。