ES6新特性(2015)
1. let 和 const 命令
let
命令用于声明变量,const
命令用来声明常量。
相比于 ES5 的 var
命令而言,let
和 const
有以下不同点:
-
不存在变量声明提升
我们都知道,var
存在变量声明提升,如下:console.log(num); //undefined console.log(num2); //ReferenceError console.log(num3); //ReferenceError var num = 1; let num2 = 2; const num3 = 3;
代码中所谓的变量声明提升是指
var num
变量声明语句提升,而num = 1
赋值不会提升,所以打印结果为undefined
。
而let
不存在变量声明提升,所以let num2
不会提升,所以打印报错。
const
用于声明常量,也不存在声明提升,所以打印也报错。 -
块级作用域
ES5 只有全局作用域和函数作用域,没有块级作用域。
一般而言,使用大括号{}
包裹的这部分代码,就是一个块级作用域。{ let num = 0; //块级作用域 }
而
let
和const
只在声明它的块级作用域有效,在其他块级作用域无法访问。 -
暂时性死区
了解了什么是块级作用域后,那么我们知道,let
声明的变量和const
声明的常量只在自己的块级作用域生效。
但是是不是在本作用域任何位置都可以访问到它们定义的变量或常量呢?
答案是否定的~
看下面这段代码:{ num = 0; console.log(num) //ReferenceError let num; }
打印会报错,因为
let
声明的变量不提升。而在这个变量声明之前,这个变量都是不可用的
,这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)
。 -
不允许重复声明
let
和const
不允许在相同作用域内,重复声明同一个变量或常量。{ let num = 0; let num = 1; console.log(num); //SyntaxError: Identifier 'num' has already been declared } { const num = 0; const num = 1; console.log(num); //SyntaxError: Identifier 'num' has already been declared }
如上代码,使用
let
或者const
重复声明会报错。 -
使用 const 赋值后的值不可修改
const
声明定义的是一个常量,常量通常都是不可变的。let num2 = 2; num2 = 3; console.log(num2); //3 const num = 0; num = 1; console.log(num); //TypeError: Assignment to constant variable.
如上代码,使用
const
修改一个值会报错,而let
和var
可以随意修改。
2. 解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
- 数组的结构赋值
E5 中,要给变量赋值是直接使用如下:
现在可以使用数组对变量进行赋值:var foo = 0;
除了可以进行赋值外,也方便变量的赋值转换。什么意思呢?就是改变两个变量的值。var [a, b] = [1, 2]; console.log(a, b); // 1 2
如果解构不成功,变量的值就等于//ES5 var a = 1; var b = 2; //如何让变量 a b值交换呢? 声明一个中间变量 var c = a; a = b; b = c; console.log(a, b); // 2 1 //上述方法太过麻烦,使用数组的结构赋值一步到位 var [a , b] = [2, 1] console.log(a, b); // 2 1
undefined
。
还有一个使用扩展运算符赋值结构的,在第三点扩展运算符详细讲解。let [foo] = []; //foo:undefined let [bar, foo] = [1]; //foo:undefined
- 对象的结构赋值
let obj = { person:{ name: 'zs', age: 18 } } let { person } = obj; console.log(person); //{ name: 'zs', age: 18 }
3. 扩展运算符(spread)
-
数组的扩展运算符
let [head, ...tail] = [1, 2, 3, 4]; head // 1 tail // [2, 3, 4]
-
函数参数的扩展运算符
function foo(...tail){ console.log(tail); } console.log(foo(1,2,3)); //[ 1, 2, 3 ]
4. 箭头函数
- 箭头函数与普通函数的四大区别:
- 箭头函数中的
this
指向其父作用域中的this
- 箭头函数不能使用argumnts对象,使用
...
扩展运算符代替 - 箭头函数不能用作构造函数
- 箭头函数不可以使用
yeild
表达式,因此不可用用作Generator函数
- 箭头函数中的
这里主要介绍一下第二点
和第四点
。
- 函数的
arguments
对象
普通函数内部,拥有类数组对象arguments
,用来存储传入函数的参数。其中length
属性代表传入函数中参数的个数,callee
指向拥有这个arguments
的函数。
特别的,箭头函数中没有function foo(){ console.log(arguments); //[Arguments] { '0': 1, '1': 2, '2': 3 } console.log(arguments.length); //3 console.log(arguments.callee); //[Function: foo] } console.log(foo(1,2,3));
arguments
,若是可以打印出来,那也是属于父作用域的 - Generator函数
Generator 函数是 ES6 提供的一种异步编程解决方案,可以暂缓执行,分段使用。
形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态。
第一次调用function* foo(){ yield 1; yield 2; return 3; } let fun = foo(); console.log(fun.next()) //{ value: 1, done: false } console.log(fun.next()) //{ value: 2, done: false } console.log(fun.next()) //{ value: 3, done: true } console.log(fun.next()) //{ value: undefined, done: true }
next
方法,在碰到的第一个yield
停止,next
方法返回一个对象,它的value
属性就是当前yield表达式的值1,done属性的值false,表示遍历还没有结束。
第二次如第一次。
第三次调用,Generator 函数从上次yield
表达式停下的地方,一直执行到return
语句(如果没有return
语句,就执行到函数结束)。next
方法返回的对象的value
属性,就是紧跟在return
语句后面的表达式的值(如果没有return
语句,则value
属性的值为undefined),done
属性的值true,表示遍历已经结束。
第四次调用,此时 Generator 函数已经运行完毕,next
方法返回对象的value
属性为undefined,done
属性为true。以后再调用next
方法,返回的都是这个值。
5. 函数参数默认值
ES6支持在定义函数的时候为其设置默认值:
function foo(a, b, c=3, d){
return {a,b,c,d}
}
console.log(foo(1,2,0,4));
console.log(foo.length); //2
函数的 length
属性,将返回没有指定默认值的参数个数,遇到有默认值的参数就会停止。
6. 模板字符串
使用反引号将变量放在${}
中。
- 不使用模板字符串
var name = 'Your name is ' + first + ' ' + last + '.'
- 使用模板字符串
var name = `Your name is ${first} ${last}.`
7. 对象属性和方法的简写
- 不使用ES6
let name = 'tom', age = 18; let obj = { name:name, age:age, fun:function(){ console.log('hello'); } } console.log(obj); //{ name: 'tom', age: 18, fun: [Function: fun] }
- 使用ES6
let name = 'tom', age = 18; let obj = { name, age, fun(){ console.log('hello'); } } console.log(obj); //{ name: 'tom', age: 18, fun: [Function: fun] }
8. 类(class)
JavaScript中的构造函数跟传统的面向对象语言(比如 C++ 和 Java)差异很大,很容易让新学习这门语言的程序员感到困惑。
ES6 提供了更接近传统语言的写法,引入了 class(类)
这个概念,作为对象的模板。通过class
关键字,可以定义类。
基本上,ES6 的class
可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class
写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
class Animal{
constructor(name,color){
this.name = name;
this.color = color;
}
sayHi(){
console.log('HI~');
}
static sayStatic(){
console.log('static');
}
}
Animal.age = 18;
class Dog extends Animal{
constructor(name,color,gender){
super(name,color);
this.gender = gender;
}
}
let dog = new Dog('hh','black','female');
console.log(dog);
console.log(dog.sayHi());
console.log(Dog.sayStatic());
console.log(Dog.age);
// 静态属性方法的继承,表示构造函数的继承,子类的__proto__属性,总是指向父类
console.log(Dog.__proto__ === Animal);
// 实例属性方法的继承,子类prototype属性的__proto__属性,总是指向父类的prototype属性
console.log(Dog.prototype.__proto__===Animal.prototype);
// prototype代表构造函数的原型对象,子类构造函数的原型对象有一个指针及__proto__,总是指向父类构造函数的原型对象
// X.prototype表示的是X类的实例,X表示X类及构造函数X
注意:
constructor
方法,这就是构造方法,而this
关键字则代表实例对象。- 类的静态方法使用
static
关键字定义,静态属性在类的外部自行定义,子类可以继承父类的静态方法和静态方法。 super
虽然代表了父类的构造函数,但是返回的是子类的实例,即super
内部的this
指的是的子类实例,相当于调用的父类的constructor
方法。- 子类必须在
constructor
方法中调用super
方法,否则新建实例时会报错。这是因为子类自己的this
对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super
方法,子类就得不到this
对象。
9. 模块化(module)
历史上,JavaScript 一直没有模块(module
)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 Ruby 的require
、Python 的import
,甚至就连 CSS 都有@import
,但是 JavaScript 任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。
在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS
和 AMD
两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS
和 AMD
规范,成为浏览器和服务器通用的模块解决方案。
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。
export
命令
用于规范模块的对外接口。
导出内容供其他模块使用,一个模块中,export
可以调用多次。
export
必须与模块内部变量建立一对一的关系,所以不能直接导出一个值。可以使用export default
默认导出一个值。export let a = 1; let b = 2; export {b}; let c = 3; export {c as d} //更改变量名
export default
命令
用于指定模块的默认导出。
一个模块只能有一个默认导出。//1. 导出一个匿名函数 export default function(){} //2. 导出一个字符串 export default 'test' //3. 导出一个变量 let a = 1; export default a; //4. 导出一个对象 let obj = {}; export default obj
import
命令//1. 解构导入 import {a, b, test} from './module'; //2. 重命名变量 import {a as b} from './module'; //3. 模块的整体加载 import * as obj from './module'; //4. 默认模块的导入 import test from './module';
10. Promise 对象
Promise 是异步编程的一种解决方案,比传统的解决方案callback更加的优雅。它最早由社区提出和实现的,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
- 是一个容器,存放着某个未来才会结束的事件结果(通常是一个异步操作)
- 是一个对象,可以获取到异步操作的消息。
参数是一个异步处理函数,如果异步执行成功,调用resolve
函数,如果异步执行失败,调用reject
函数
let p = new Promise((resolve,reject)=>{//异步操作});
内部异步执行成功的回调函数 p.then((response)=>{});
内部异步执行失败的回调函数 p.catch((error)=>{});
Promise.prototype.catch
方法是.then(null, rejection)
的别名,用于指定发生错误时的回调函数。
有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。
嵌套两个setTimeout回调函数:
//不使用ES6
setTimeout(function()
{
console.log('ONE'); // 1秒后输出"ONE"
setTimeout(function()
{
console.log('TWO'); // 2秒后输出"TWO"
}, 1000);
}, 1000);
//使用ES6
let p = new Promise((resolve,reject)=>{
setTimeout(resolve,1000)
})
let p2 = new Promise((resolve,reject)=>{
setTimeout(resolve,2000)
})
p.then(()=>{
console.log('ONE');
return p2
})
.then(()=>{
console.log('TWO');
})
12. Set 和 Map 数据结构
- Set 数据结构
类似于数组,但成员的值是唯一的,没有重复的值
去除数组中的重复成员[...new Set(arr)]
- Set原型对象的属性
constructor 返回当前实例的构造函数,默认就是Set
size 返回Set实例的成员总数 - Set原型对象的方法
add(value) 添加某个值,返回Set结构本身
delete(value) 删除某个值,返回一个布尔值,表示删除是否成功
has(value) 判断该值是否是Set成员,返回一个布尔值
clear() 清除所有成员,没有返回值
keys() 返回键名的Iterator遍历器对象
values() 返回键值的Iterator遍历器对象
entries() 返回键值对的Iterator遍历器对象
forEach() 使用回调函数遍历每一个成员
- Set原型对象的属性
- Map 数据结构
类似于对象,是键值对的集合,但是键的范围包括字符串及其他各种类型的数据(包括对象) 也可接受一个二维数组作为参数,该数组的成员是一个表示键值对的数组- Map原型对象的属性:
constructor 构造函数,默认为Map
size 返回Map结构的成员总数 - Map原型对象的方法:
set(key,value) 设置键名及键值,返回整个Map结构。如果键名key存在,键值会被更新,否则就新生成该键
get(key) 读取对于的键值,如果找不到,返回undefined
has(key) 判断该键是否是Map对象成员,返回一个布尔值
delete(key) 删除某个键,返回一个布尔值
clear() 清除所有成员,没有返回值
keys() 返回键名的Iterator迭代器对象
values() 返回键值的Iterator迭代器对象
entries() 返回键值对的Iterator迭代器对象
forEach() 使用回调函数遍历每一个成员
- Map原型对象的属性:
13. Iterator 遍历器/迭代器
是一种接口,为各种不同的数据结构提供统一的访问机制,即for-of循环。 任何数据结构,只要部署了Iterator接口,就可以完成遍历操作。 一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是可遍历的(iterable)。
- 作用:
- 为各种数据结构,提供一个统一的,简便的访问接口。
- 使得数据结构的成员能够按照某种次序排列。
- Iterator主要供ES6新提出的遍历命令for-of循环使用
next()
方法
第一次调用Iterator的next()方法,可以将指针指向数据结构的第一个成员。
第二次调用next()方法,指针就指向数据结构的第二个成员。
不断调用Iterator的next()方法,直到它指向数据结构的结束位置。
console.log(values.next());//{ value: 1, done: false }
console.log(values.next());//{ value: 'hello', done: false }
console.log(values.next());//{ value: undefined, done: true }
可以使用for-of循环来遍历Iterator对象,或者在while语句中使用done属性来遍历。
原生JS具备Iterator接口的数据结构如下:
4. Set
5. Map
6. Array
7. String
8. 函数的arguments对象
9. NodeList对象
注意:for-of
不能用来遍历对象
14. Symbol
ES5 的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin 模式),新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是 ES6 引入Symbol
的原因。
ES6 引入了一种新的原始数据类型Symbol
,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:undefined
、null
、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。
Symbol
值通过Symbol
函数生成。
凡是属性名属于 Symbol
类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
// 没有参数的情况
let s1 = Symbol();
let s2 = Symbol();
s1 === s2 // false
// 有参数的情况
let s1 = Symbol('foo');
let s2 = Symbol('foo');
s1 === s2 // false
console.log(typeof s1); //symbol
ES7新特性(2016)
1. Array.prototype.includes()
includes()
函数用来判断一个数组是否包含一个指定的值,如果包含则返回 true
,否则返回false
。
let arr = [1,2,3,4]
console.log(arr.includes(1)); //true
console.log(arr.includes(5)); //false
2. 指数操作符 **
在ES7中引入了指数运算符**
,**
具有与Math.pow(..)
等效的计算结果。
console.log(Math.pow(2,3)); //8
console.log(2**3); //8
ES8新特性(2017)
1. async函数
ES2017 标准引入了 async
函数,使得异步操作变得更加方便。
async
函数是什么?一句话,它就是 Generator 函数的语法糖。
async
函数对 Generator 函数的改进,体现在以下四点:
- 内置执行器
async
函数的执行,与普通函数一模一样,只要一行。
这完全不像 Generator 函数,需要调用next方法。 - 更好的语义
async
和await
,比起星号
和yield
,语义更清楚了。async
表示函数里有异步操作,await
表示紧跟在后面的表达式需要等待结果。 - 更广的适用性
async
函数的await
命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。 - 返回值是 Promise
async
函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。
进一步说,async
函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await
命令就是内部then命令的语法糖。
//Generator函数
let gen = function*(){
yield 1;
yield 2;
}
let g = gen();
console.log(g.next());//{ value: 1, done: false }
console.log(g.next());//{ value: 2, done: false }
console.log(g.next());//{ value: undefined, done: true }
//async函数
let gen2 = async function(){
let g1 = await 1;
let g2 = await 2;
console.log(g1);
console.log(g2);
return {
g1,
g2
}
}
gen2() // 1 2
//then()方法内返回的是async函数返回的结果
gen2().then((res)=>{
console.log(res); //{g1:1,g2:2}
})
2. Object.values()
Object.values()
是一个与Object.keys()
类似的新函数,但返回的是Object自身属性的所有值,不包括继承的值。
ES5中只有 Object.keys() 方法。
let obj = {
name: 'zs',
age: 18
}
console.log(Object.keys(obj)); //[ 'name', 'age' ]
console.log(Object.values(obj)); //[ 'zs', 18 ]
//不使用 Object.values(obj)
function getValues(obj){
return Object.keys(obj).map((item)=>{
return obj[item]
})
}
console.log(getValues(obj)); //[ 'zs', 18 ]
3. Object.entries()
Object.entries()
函数返回一个对象自身可枚举属性的键值对的数组。
let obj = {
name: 'zs',
age: 18
}
console.log(Object.entries(obj)); //[ [ 'name', 'zs' ], [ 'age', 18 ] ]
for(let [key,value] of Object.entries(obj)){
console.log(`key: ${key}, value: ${value}`);
//key: name, value: zs
//key: age, value: 18
}
4. String.prototype.padStart 和 String.prototype.padEnd
在ES8中String新增了两个实例函数String.prototype.padStart
和String.prototype.padEnd
,允许将空字符串或其他字符串添加到原始字符串的开头或结尾。
String.padStart(targetLength,[padString])
- targetLength:当前字符串需要填充到的目标长度。如果这个数值小于当前字符串的长度,则返回当前字符串本身。
- padString:(可选)填充字符串。此参数的缺省值为
" "
空字符串。
String.padEnd(targetLength,padString])
- targetLength:当前字符串需要填充到的目标长度。如果这个数值小于当前字符串的长度,则返回当前字符串本身。
- padString:(可选) 填充字符串。此参数的缺省值为
" "
空字符串。
console.log('100'.padEnd(6,'.00')); // 100.00
console.log('100'.padStart(4,'e')); // e100
5. 函数参数列表结尾允许逗号
主要作用是方便使用git进行多人协作开发时修改同一个函数减少不必要的行变更。
function foo(a,b,){
console.log(a,b);
}
foo(1,2)
6. Object.getOwnPropertyDescriptors()
Object.getOwnPropertyDescriptors()
函数用来获取一个对象的所有自身属性的描述符,如果没有任何自身属性,则返回空对象。
Object.getOwnPropertyDescriptors(obj, ‘属性名’)
let obj = {
name: 'zs',
getName(){
console.log(this.name);
}
}
console.log(Object.getOwnPropertyDescriptor(obj,'name'));
// { value: 'zs', writable: true, enumerable: true, configurable: true }
- value
属性值 - writable
是否可以修改值 - enumerable
是否可枚举 - configurable
能否通过delete删除属性从而重新定义属性
7. SharedArrayBuffer对象
SharedArrayBuffer 对象用来表示一个通用的,固定长度的原始二进制数据缓冲区,类似于 ArrayBuffer 对象,它们都可以用来在共享内存(shared memory)上创建视图。与 ArrayBuffer 不同的是,SharedArrayBuffer
不能被分离。
// create a SharedArrayBuffer with a size in bytes
const buffer = new SharedArrayBuffer(8);
console.log(buffer.byteLength);
// expected output: 8
8. Atomics对象
Atomics 对象提供了一组静态方法用来对 SharedArrayBuffer 对象进行原子操作。
这些原子操作属于 Atomics 模块。与一般的全局对象不同,Atomics 不是构造函数,因此不能使用 new 操作符调用,也不能将其当作函数直接调用。Atomics 的所有属性和方法都是静态的(与 Math 对象一样)。
ES9新特性(2018)
1. Rest(剩余)/Spread(展开) 属性
- 对象的扩展运算符
let obj = { name: 'zs', age: 18 } let obj2 = { ...obj, gender: 'male', name: 'tom' } console.log(obj2); //{ name: 'tom', age: 18, gender: 'male' }
2. Asynchronous iteration (异步迭代)
在async/await
的某些时刻,你可能尝试在同步循环中调用异步函数。例如:
async function process(array) {
for (let i of array) {
await doSomething(i);
}
}
这段代码中,循环本身保持同步,并在在内部异步函数之前全部调用完成。
新的 for-await-of 构造允许你使用异步可迭代对象作为循环迭代:
async function process(array) {
for await (let i of array) {
doSomething(i);
}
}
3. Promise.prototype.finally()
finally()
允许您运行一些代码,无论 promise 的执行成功或失败:
new Promise((resolve,reject)=>{})
.then(data => data.json())
.catch(error => console.error(error))
.finally(() => console.log('finished'))
4. 正则表达式之后行断言(lookbehind)
正则表达式后行断言(lookbehind)
:?<=
代表字符串中的一个位置,紧接该位置之前的字符序列能够匹配。/(?<=Roger) Waters/ /(?<=Roger) Waters/.test('Pink Waters is my dog') //false /(?<=Roger) Waters/.test('Roger is my dog and Roger Waters is a famous musician') //true
正则表达式后行断言逆操作
:?<!
代表字符串中的一个位置,紧接该位置之前的字符序列不能够匹配。/(?<!Roger) Waters/ /(?<!Roger) Waters/.test('Pink Waters is my dog') //true /(?<!Roger) Waters/.test('Roger is my dog and Roger Waters is a famous musician') //false
正则表达式的先行断言?=
:匹配一个字符串,该字符串后面跟着一个特定的子字符串。/Roger(?=Waters)/ /Roger(?= Waters)/.test('Roger is my dog') //false /Roger(?= Waters)/.test('Roger is my dog and Roger Waters is a famous musician')
正则表达式的先行断言逆操作?!
:匹配一个字符串,该字符串后面没有一个特定的子字符串。/Roger(?!Waters)/ /Roger(?! Waters)/.test('Roger is my dog') //true /Roger(?! Waters)/.test('Roger Waters is a famous musician') //false
5. 正则表达式之命名捕获组
ES2018允许命名捕获组使用符号?<name>
const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
const result = re.exec('2015-01-02')
// result.groups.year === '2015';
// result.groups.month === '01';
// result.groups.day === '02';
6. 正则表达式之dotAll模式
正则表达式中点.
匹配除回车外的任何单字符,标记s改变这种行为,允许行终止符的出现,例如:
/hello.world/.test('hello\nworld'); // false
/hello.world/s.test('hello\nworld'); // true
7. 正则表达式之Unicode 属性转义 \p{…} 和 \P{…}
ES2018添加了 Unicode 属性转义——形式为\p{...}
和\P{...}
,在正则表达式中使用标记 u
(unicode) 设置。
\p{}
匹配所有 Unicode 字符,否定为 \P{}
。
任何 unicode 字符都有一组属性。 例如,Script
确定语言系列,ASCII
是一个布尔值, 对于 ASCII
字符,值为 true,依此类推。 您可以将此属性放在花括号中,正则表达式将检查是否为真:
/^\p{ASCII}+$/u.test('abc') //true
/^\p{ASCII}+$/u.test('ABC@') //true
/^\p{ASCII}+$/u.test('ABC🙃') //false
ASCII_Hex_Digit
是另一个布尔属性,用于检查字符串是否仅包含有效的十六进制数字:
/^\p{ASCII_Hex_Digit}+$/u.test('0123456789ABCDEF') //true
/^\p{ASCII_Hex_Digit}+$/u.test('h') //false
还有许多其他布尔属性,您只需通过在花括号中添加它们的名称来检查它们,包括 Uppercase
, Lowercase
, White_Space
, Alphabetic
, Emoji
等:
/^\p{Lowercase}$/u.test('h') //true
/^\p{Uppercase}$/u.test('H') //true
/^\p{Emoji}+$/u.test('H') //false
/^\p{Emoji}+$/u.test('🙃🙃') //true
除了这些二进制属性之外,您还可以检查任何 unicode 字符属性以匹配特定值。在这个例子中,我检查字符串是用希腊语还是拉丁字母写的:
/^\p{Script=Greek}+$/u.test('ελληνικ?') //true
/^\p{Script=Latin}+$/u.test('hey') //true
ES10新特性(2019)
1. 可选的 catch 绑定
在 ECMAScript2019 最新提案中,支持我们在使用 try catch
错误异常处理时,选择性的给 catch
传入参数,即我们可以不传入 catch
参数。
- 正常使用 try catch:
try { // todo } catch (err){ console.log('err:',err) }
- ES10中:
try { // todo } catch { // todo }
2. JSON Superset 超集
之前如果JSON字符串中包含有行分隔符(\u2028)
和段落分隔符(\u2029)
,那么在解析过程中会报错。
JSON.parse('"\u2028"');
// SyntaxError
现在ES2019对它们提供了支持。
JSON.parse('"\u2028"');
// ''
3. Symbol.prototype.description
这次 ES10 中,为 Symbol 类型增加 Symbol.prototype.description
的一个访问器属性,用来获取 Symbol 类型数据的描述信息(description)。
console.log(Symbol('pingan8787').description);
// expected output: "pingan8787"
4. Function.prototype.toString
在 ES10 之前,我们对一个函数调用 toString()
方法,返回的结果中会将注释信息去除。
在 ES10 之后,函数再调用 toString()
方法,将准确返回原有内容,包括空格和注释等:
let foo = function(){
// do something
console.log('foo')
}
foo.toString();
/**
"function(){
// do something
console.log('foo')
}"
*/
5. Object.fromEntries
Object.fromEntries
是 ES10 中新的静态方法,用于将键值对列表转换为对象。
Object.fromEntries()
方法接收一个键值对的列表参数,并返回一个带有这些键值对的新对象。
Object.fromEntries()
是 Object.entries
的反转。
let leo = { name: 'zs', age: 10};
let arr = Object.entries(leo);
console.log(arr);// [["name", "zs"],["age", 10]]
let obj = Object.fromEntries(arr);
console.log(obj);// {name: "zs", age: 10}
6. JSON.stringify() 加强格式转化
更友好的 JSON.stringify
,对于一些超出范围的 Unicode 字符串,为其输出转义序列,使其成为有效 Unicode 字符串。
JSON.stringify('\uDF06\uD834')
// → '"\\udf06\\ud834"'
JSON.stringify('\uDEAD')
// → '"\\udead"'
7. String.prototype.trimStart() / String.prototype.trimEnd()
自ES5来,String.prototype.trim()
被用于去除头尾上的空格、换行符等,现在通过trimStart()
,trimEnd()
来头和尾进行单独控制。trimLeft()
、trimRight()
是他们的别名。
const string = ' Hello! ';
string.trimStart();
// 'Hello! '
string.trimEnd();
// ' Hello!'
- 若想去除字符串内部的空格:
var str = ' ada dsa '; str = str.split(' ').join('') console.log(str); //adadsa
8. Array.prototype.flat() / Array.prototype.flatMap()
- Array.prototype.flat()
把数组展平是ES10给我们带来的新特性,通过传入层级深度参数(默认为1),来为下层数组提升层级。如果想提升所有层级可以写一个比较大的数字甚至是Infinity,当然不推荐这么做。let arr = [1,2,3,4,[5,6]] console.log(arr.flat()); //[ 1, 2, 3, 4, 5, 6 ] let arr2 = [1,2,[3,4,[5,6]]] console.log(arr2.flat(3)); //[ 1, 2, 3, 4, 5, 6 ]
- Array.prototype.flatMap()
在 ES10 中,官方还增加了Array.prototype.flatMap
方法,其实就是 flat 和 map 一起组合操作:let arr = [1,2,3] arr = arr.map((x)=>[x * x]) console.log(arr); //[ [ 1 ], [ 4 ], [ 9 ] ] arr = arr.flat() console.log(arr); //[ 1, 4, 9 ] let arr2 = [1,2,3] arr2 = arr2.flatMap((x)=>[x * x]) console.log(arr2); //[ 1, 4, 9 ]
ES11新特性(2020)
1. 空值合并运算符 ??
由于 JavaScript 的类型时动态的,在分配变量时,我们不得不去记住哪些值会被判断为真,哪些会被判断为假。当我们在创建一个对象时,通常会初始化属性为假的值,比如一个空字符串
或者是 0
。
let person = {
profile: {
name: "",
age: 0
}
};
console.log(person.profile.name || "Anonymous"); // Anonymous
console.log(person.profile.age || 18); // 18
上述例子中,可能结果并不是我们想要的,我们想名字为空
或者 0
岁也是合理的,应该不被代替,我们可以用 ??
运算符替换掉 ||
运算符,因为 ??
运算符的类型检测会更严格一点,它只会把 null
和 undefined
判断为 false
。
console.log(person.profile.name ?? "Anonymous"); // ""
console.log(person.profile.age ?? 18); // 0
2. 可选链运算符?.
与空值合并运算符类似,JavaScript 在处理虚假值时,可能无法按照我们的意愿进行操作。当属性值未定义时,我们可以返回一个默认值,但是如果属性的路径未被定义呢?
答案是,可以通过在点符号前添加问号,我们可以将属性路径的任何部分设置为可选,以便操作。
let person = {};
console.log(person.profile.name ?? "Anonymous"); // person.profile is undefined
console.log(person?.profile?.name ?? "Anonymous"); // Anonymous
console.log(person?.profile?.age ?? 18); // 18
3. 使用 #
号快速创建类的私有变量
类的主要目的之一是把我们的代码复用到更多模块中去,但是我们又不希望把所有的属性和方法都暴露出来,现在,有了这个新特性,我们就可以很轻易的实现这个想法了,只需要在我们的私有属性或者方法前面加一个 #
号即可:
class Message {
#message = "Howdy"
greet() { console.log(this.#message) }
}
const greeting = new Message()
greeting.greet() // Howdy
console.log(greeting.#message) // Private name #message is not defined
4. Promise.allSelected
当我们在使用多个 promise,尤其是这些 promise 有依赖关系时,打印每个 promise 的日志显得尤为重要,这可以帮助我们调试错误。有了 Promise.allSelected,我们可以创建一个新的 promise,这个 promise 会在包含的所有 promise 都执行完之后才返回,这将使我们能够访问一个数组,其中包含每个 promise 的返回值。
之前的Promise.all只有内部所有的promise都为resolved时,才返回每个promise返回值组成的数组。不然返回第一个被reject的实例的返回值
const p1 = new Promise((res, rej) => setTimeout(res, 1000));
const p2 = new Promise((res, rej) => setTimeout(rej, 1000));
Promise.allSettled([p1, p2]).then(data => console.log(data));
// [
// Object { status: "fulfilled", value: undefined},
// Object { status: "rejected", reason: undefined}
// ]
5. BigInt
JavaScript可以处理的最大数字是2 ^ 53,我们可以在MAX_SAFE_INTEGER中看到:
const max = Number.MAX_SAFE_INTEGER;
console.log(max); // 9007199254740991
超过这个数字就会变得有点奇怪~
console.log(max + 1); // 9007199254740992
console.log(max + 2); // 9007199254740992
console.log(max + 3); // 9007199254740994
console.log(Math.pow(2, 53) == Math.pow(2, 53) + 1); // true
我们可以使用新的 BigInt数据类型来解决这个问题。通过把字母n放在末尾,我们可以与大得离谱的数字进行交互。我们无法将标准数字与BigInt数字混合在一起,因此任何数学运算都需要使用BigInt来完成。
const bigNum = 100000000000000000000000000000n;
console.log(bigNum * 2n); // 200000000000000000000000000000n
6. 使用 async/await 动态导入模块
如果我们有很多的功能函数,有些是很少用的,那么一起导入会使资源浪费,现在我们可以使用 async/await
来动态导入这些依赖了,但是这个方法仅适用于 node.js 环境。
// math.js
const add = (num1, num2) => num1 + num2;
export { add };
const doMath = async (num1, num2) => {
if (num1 && num2) {
const math = await import('./math.js');
console.log(math.add(5, 10));
};
};
doMath(4, 2);