ECMAScript 6
变量新增
变量的定义
let
- 在全局定义的变量不在属于全局对象的属性
var a = {};
console.log(this.a === a); //true
let b = {};
console.log(this.b === b); //false
- 具有块级作用域
let i = 0;
{
console.log(i); //0
i++;
}
console.log(i); //1
//相当于
let i = 0;
{
let iCopy = i;
console.log(iCopy); //0,使用内层的
i++; //修改外层的
}
console.log(i); //1
/*由上述就可得知循环中的块级作用域*/
var a = [];
for (let i = 0;i < 2; i++) {
a[i] = function () {
console.log(i);
};
}
a[1](); //1
a[0](); //0
//相当于
for (let i = 0;i < 2;) {
let iCopy = i;
a[iCopy] = function () {
console.log(iCopy); //使用内层的
};
i++; //修改外层的
}
a[1](); //1
a[0](); //0
- 块级作用域必须有大括号
/*像一些条件循环语句中若只有一条语句可以省略大括号,这种情况下会若使用let则必须声明大括号,否则会报错*/
for(let i = 0; i < 1; i++)
let a = 10; //SyntaxError
if(true)
let a = 10; //SyntaxError
- for循环中的父子级作用域
for (let i = 0; i < 3; i++) { //小括号中的父作用域
let i = 1; //大括号是子作用域
console.log(i); //1,三次循环都输出1
}
- 不存在变量提升
console.log(a); //undefined
var a = 2;
console.log(b); //ReferenceError
let b = 2;
- 不能在同一作用域下重复声明同一个变量
let a;
let a = 0; //SyntaxError
let a;
var a = 0; //SyntaxError
function func(arg) { //只有运行时才会发现是否重复定义了同名变量,所以函数不调用是不会报错的
let arg; //SyntaxError
}
func();
- 拥有暂时性死区(temporal dead zone):指的是使用let关键字定义变量时,会在该语句到该作用域的开始这部分无法使用该变量
//TDZ开始
tmp = 'abc'; //ReferenceError
console.log(tmp); //ReferenceError
let tmp;
//TDZ结束
console.log(tmp); //undefined
tmp = 1;
console.log(tmp); //1
/*
隐藏的暂时性死区
对于一些像赋值语句表达式是右结合性的,所以会先执行右部
*/
let a = a; //ReferenceError
//相当于
//TDZ开始
a; //ReferenceError
let a = a;
//TDZ结束
let a = b, b = 1;
//相当于
//TDZ开始
b; //ReferenceError
let a = b;
let b = 1;
//TDZ结束
let a = 1 ,b = a;
//相当于,所以不会报错
//TDZ开始
let a = 1;
//TDZ结束
let b = a;
const
同样具有let命令所有的性质:
- 在全局定义的常量不在属于全局对象的属性
- 具有块级作用域
- 块级作用域必须有大括号
- 不存在变量提升
- 不能在同一作用域下重复声明同一个变量
- 拥有存在暂时性死区
只不过比let多了一条
- 使用该关键字声明的变量值无法改变,所以声明时必须赋值(同其他语言若常量是引用数据类型则至少引用地址不可改变)
变量的解构赋值
数组解构赋值
基本用法:[...变量] = 数组
- 从数组对应位置为变量数组中的相应变量赋值
- 若为匹配到的模式则会返回
undefined
或空数组 - 可以为每个解构的变量赋予默认值,若未匹配到或这个位置的值是
undefined
则采用默认值
/*有了解构赋值就又多了一种方式交换两个变量的值*/
let a = 1;
let b = 2;
[a, b] = [b, a];
console.log(a, b); //2 1
/*定义同时解构*/
let [a,b] = [1,2];
console.log(a,b); //1 2
/*先定义后解构*/
let a, b;
[a, b] = [1, 2];
console.log(a, b); //1 2
/*嵌套解构*/
let [a, [b]] = [1, [2]];
console.log(a, b); //1 2
/*部分解构*/
let [a] = [1, 2];
console.log(a); //1
let [,b] = [1, 2];
console.log(b); //2
/*可变参数解构*/
let [a, ...b] = [1, 2, 3];
console.log(a, b); //1 [ 2, 3 ]
/*未匹配到的*/
let [a, b, ...c] = [1];
console.log(a, b, c); //1 undefined []
/*默认值*/
let [a ,b = 1] = [1]; //对为匹配到的采取默认值
console.log(a ,b); //1 1
let [a ,b = 1] = [1, undefined]; //对undefined采取默认值
console.log(a ,b); //1 1
let [a ,b = 1] = [1, null]; //只对undefined采取默认值
console.log(a ,b); //1 null
let [a = 1 ,b = a] = []; //以a的值作为b的默认值
console.log(a ,b); //1 1
let [a = 1 ,b = a] = [2]; //此时b的默认值是拿到的a已经解构的值,而不是a定义的默认值
console.log(a ,b); //2 2
let [a = 1 ,b = a] = [2 ,3]; //赋值后此时不会采用默认值
console.log(a ,b); //2 3
对象的解构赋值
基本用法:{...属性: 变量} = 对象
,当属性名和变量名同名时{...变量} = 对象
- 由于对象没有次序,所以依靠对象的属性名来确定为那个变量赋值相应的属性,可以解构继承的属性
- 若为匹配到的模式则会返回
undefined
或空对象 - 可以为每个解构的变量赋予默认值,若未匹配到或这个位置的值是
undefined
则采用默认值
/*定义同时解构*/
let { a: a, b: b } = { a: 1, b: 2 };
console.log(a, b); //1 2
//可以简写为
let { a, b } = { a: 1, b: 2 };
console.log(a, b); //1 2
//若属性名与变量名不同时不能简写,否则会匹配不到
let { a: a1, b: b1 } = { a: 1, b: 2 };
console.log(a1, b1); //1 2
//不是按照位置,而是按照属性,所以匹配不到返回undefined
let { a1, b1 } = { a: 1, b: 2 };
console.log(a1, b1); //undefined undefined
/*先定义后解构*/
let a, b;
{ a, b } = { a: 1, b: 2 }; //报错,SyntaxError,会当作代码块来解析
({ a, b } = { a: 1, b: 2 }); //使用小括号包裹,不要将大括号开头即可
/*嵌套解构*/
let { a, n: { b } } = { a: 2, n: { b: 3 } }; //此时n是匹配模式,而不是变量
console.log(a, b); //2 3
let { a, p: { b } } = { a: 2, n: { b: 3 } }; //此时p匹配模式不存在,则会报错 TypeError
console.log(a, b); //2 3
/*解构继承属性*/
let { toString } = { }; //可以解构继承的属性
console.log(toString); //[Function: toString]
/*部分解构*/
let { b } = { a: 1, b: 2 };
console.log(b);
/*可变参数解构*/
let { a, ...b } = { a: 1, b: 2 };
console.log(a, b); //1 { b: 2 }
/*未匹配到的*/
let { a, b, ...c } = { a: 1 };
console.log(a, b, c); //1 undefined {}
/*默认值*/
let { a, b = 1 } = { a: 1 }; //对为匹配到的采取默认值
console.log(a, b); //1 1
let { a, b = 1 } = { a: 1 ,b: undefined}; //对undefined采取默认值
console.log(a, b); //1 1
let { a, b = 1 } = { a: 1 ,b: null}; //只对undefined采取默认值
console.log(a, b); //1 null
let { a = 1, b = a } = {}; //以a的值作为b的默认值
console.log(a, b); //1 1
let { a = 1, b = a } = { a: 2 }; //此时b的默认值是拿到的a已经解构的值,而不是a定义的默认值
console.log(a, b); //2 2
let { a = 1, b = a } = { a: 2 ,b: 3 }; //赋值后此时不会采用默认值
console.log(a, b); //2 3
原始数据类型的解构赋值
原始数据类型会先转化为对应的包装对象后进行解构操作
- 字符串既可以使用数组方式解构单个字符,也可以使用对象方式解构字符串上的方法和属性
let [a, b] = "ab";
console.log(a, b); //a b
let { length } = "ab";
console.log(length); //2
- 数值和布尔类型的只能使用对象解构他们转化成包装对象上的方法
let { toString } = 123;
console.log(toString === Number.prototype.toString); //true
let { toString } = true;
console.log(toString === Boolean.prototype.toString); //true
函数参数解构赋值
/*为函数参数指定默认值*/
function add(a = 1, b = 1) {
console.log(a + b);
}
add(); //2
add(1, 2); //3
- 数组解构
let arr = [1, 2];
/*对传入的数组解构*/
function add([a, b]) {
console.log(a + b);
}
add(); //TypeError,为传入参数则是对undefined解构,则无法解构就会报错
add(1, 2); //TypeError,传入非数组无法解构就会报错
add(arr); //3
/*对传入的数组解构,并指定解构默认值*/
function add([a, b=100]) {
console.log(a + b);
}
add([1]); //101
add(arr); //3
/*对传入的数组解构,若未传入参数则解构默认数组*/
function add([a, b] = [1, 1]) {
console.log(a + b);
}
add(); //2
add(arr); //3
- 对象解构
let obj = { a: 1, b: 2 };
/*传入对象进行解构*/
function show({ a, b }) {
console.log(a, b);
}
show(); //TypeError,为传入参数则是对undefined解构,则无法解构就会报错
show(1); //undefined undefined 不会像解构数组那样无法解构就报错,而是将原始数据类型转化成包装对象解构,匹配不到就是undefined
show(obj); //1 2
/*对传入的对象解构,并指定解构默认值*/
function show({ a = 100, b = 100 }) {
console.log(a, b);
}
show({}); //100 100 解构对象中没有该属性就会使用默认值
show(obj); //1 2
/*对传入对象解构,若未传入参数则解构默认对象*/
function show({ a, b } = { a: 100, b: 100 }) {
console.log(a, b);
}
show(); //100 100 未传入参数解构默认对象
show({}); //undefined undefined 匹配不到就是undefined
show(obj); //1 2
新语法
注意
使用这些新增的指定函数默认值、解构赋值和剩余参数的形式就无法在函数内部使用严格模式;原因是解析引擎会优先解析函数参数部分后解析函数体,若函数参数中出现了违背严格模式时还为发现函数体是否开启严格模式,索性若使用这些形式在开启严格模式就报错
换句话说就是当函数参数部分使用了新语法就会报错
为函数形参指定默认值
- 新增可以通过解构赋值的方式指定默认值:一般指定函数尾部形参,这样复合规范
- 指定默认值后该函数的
length
属性将返回未指定默认值的形参个数 - 指定默认值的函数默认无法再声明同名函数将前面的覆盖
- 指定默认值后该形参会像使用
let
关键字声明的变量一样,就无法用function(x = x){}
的形式了 - 函数形参括号属于父作用域,函数体属于子作用域
- 指定默认值后该函数的
模板字符串
基本用法:反引号进行包裹,该字符串会保留换行和缩进,使用${js代码}
插值语法可以插入内容
let str = "str";
console.log(`Hello ${str} World`); //插入变量,Hello str World
// console.log(`Hello ${str1} World`); //插入变量,若变量不存在会报错
console.log(`Hello ${1 + 1} World`); //表达式,Hello 2 World
console.log(`Hello ${(function () { return "str"; })()} World`); //插入函数返回值,Hello str World
console.log(`Hello ${{}} World`); //插入对象类型默认调用toString转化为相应的字符串,Hello [object Object] World
console.log(`Hello ${`hello`} World`); //甚至可以嵌套内部的模板字符串,Hello hello World
与函数连用,也称为标签模板:函数后不再使用圆括号进行调用,而是直接跟一个模板字符串,该模板字符串会按照一定规则当作函数的参数
参数规则:第一个参数是模板字符串未被替换部分的分隔数组形式,之后的就是该模板字符串中的替换部分作为函数的可变参数,所以为了使用这种方式调用函数则函数的形式参数一般这样定义function fun(strArr,...value){}
console.log`hello`; //就算只有一个单独的模板字符串也会存放到一个数组中
//同等于
console.log(["hello"]);
console.log`hello${1 + 1}world`; //被插值分隔的字符串会存放到数组中当第一个参数
//同等于
console.log(["hello", "world"], 2);
console.log`hello${1 + 1}`; //被插值分隔的肯定会有两部分,后面若没有内容则是空字符串
//同等于
console.log(["hello", ""], 2);
箭头函数
- 新增使用
(参数列表)=>{函数体}
符号定义函数- 若参数是一个则可以省略圆括号
- 若函数体只有一条
return
语句则可以省略大括号,必须省略return
关键字 - 若此时返回的一个对象,为了对象的大括号与函数体的大括号区分所以必须为对象包裹一个圆括号
- 注意点
- 箭头函数中没有
this
,this
引用的是父非箭头函数的this
,所以在定义对象的方法时就不要使用箭头函数,在使用回调函数时使用箭头函数从而不必担心会指向全局对象了 - 箭头函数不能当作构造函数来使用,也就是不能
new
- 箭头函数中不能使用
arguments
对象,使用可变参数代替
- 箭头函数中没有
var p = "函数外部"; //不使用let的原因:var声明的变量属于全局对象,而let不是
(function () {
let p = "函数内部";
var obj = {
p: "对象内部",
arrowfun: () => {
console.log('箭头函数:', this.p); //父函数的this相同
},
fun: function () {
console.log('普通函数', this.p); //指向该对象
}
};
obj.arrowfun(); //箭头函数: 指定的对象
obj.fun(); //普通函数 对象内部
console.log(this.p); //箭头函数: 指定的对象
}).call({ p: "指定的对象" }); //修改父函数的this指向
扩展运算符
剩余参数
- 新增可以通过
...形参
的方式指定剩余参数(可变参数):实质是将剩余的参数装到一个数组中- 使用剩余参数后该函数的
length
属性将返回不包括剩余参数的形参个数 - 可以使用数组中的任意方法
- 只能出现在函数形参中的最后
- 使用剩余参数后该函数的
数组对象解构合并
- 新增可以通过
...数组|对象
的方式将数组元素以逗号分隔的形式:要注意的是会将空位转化为undefined
- 可以作用于函数参数圆括号上
- 可以作用于数组方括号中
- 可以作用与对象属性中,作为对象的属性是拷贝,并不是引用,所以修改合并的对象属性不会影响原来的属性
console.log(...[1, 2, 3]); //1 2 3
//相当于
console.log(1, 2, 3); //1 2 3
console.log([1, ...[2, 3]]); //[ 1, 2, 3 ]
//相当于
console.log([1, 2, 3]); //[ 1, 2, 3 ]
console.log(...[1, , 3]); //1 undefined 3
console.log({ a: 1, ...{ b: 2, c: 3 } }); //{ a: 1, b: 2, c: 3 }
//相当于
console.log({ a: 1, b: 2, c: 3 }); //{ a: 1, b: 2, c: 3 }
剩余属性
- 可以通过
...变量
的方式将数组或对象的剩余属性以数组或对象形式赋值给该变量
/*可变参数解构数组*/
let [a, ...b] = [1, 2, 3];
console.log(a, b); //1 [ 2, 3 ]
/*可变参数解构对象*/
let { a, ...b } = { a: 1, b: 2 };
console.log(a, b); //1 { b: 2 }
字符串解构
- 可以通过
...字符串
的形式将字符串转化成字符数组
console.log([..."hello"]); // [ "h", "e", "l", "l", "o" ]
对象属性的扩展写法
简洁属性和属性表达式不能同时使用,否则会报错
- 简洁属性:字面量方式创建对象时,可以直接写入变量和函数,此时变量名和函数命名就的该对象的属性名和方法名
- 简洁属性若是方法,该方法能使用
new
关键字,这也是与非简洁属性方法的唯一区别
- 简洁属性若是方法,该方法能使用
let id = "id";
let obj1 = {
id,
fun() { }
};
//同等于
let obj2 = {
id: "id",
fun: function () { }
}
- 属性表达式:字面量方式创建对象时,属性名允许使用方括号,方括号内可以使用表达式、变量等JavaScript代码,最终方括号中的内容会被转化为字符串
let obj = {
["p"+"1"]:1,
["f"+"n"](){}
}
console.log(obj); //{ p1: 1, fn: [Function: fn] }
//let obj = {
// "p"+"1":1, //报错
// "f"+"n" //报错
//}
迭代器
若想让一个对象可以使用for...of
增强型for循环、解构赋值、扩展运算符和Array.form()
方法转化为数组,就必须实现迭代器接口,也就是说该对象中能直接调用[Symbol.iterator]()
方法,他可以是继承的也可以是自己独有的
[Symbol.iterator]()
方法应该返回一个迭代器对象,该迭代器对象中应该有一个至少unext()
方法,next()
方法中需要返回一个状态对象{value:value,done:true|false}
:value代表当前迭代要返回的值,done代表是否结束迭代
let obj = {
data: [1, 2, 3],
[Symbol.iterator]() {
let index = 0; //游标
function next() {
if (index < this.data.length) { //还没到最后一个元素
return {
value: this.data[index++], //返回当前值,并且指针后移
done: false //状态是未结束
};
} else { //到最后一个
return { value: undefined, done: true }; //返回undefuned,状态是结束
}
}
return {
next:next.bind(this) //必须返回一个迭代器对象,并且有next方法
};
}
};
for (const val of obj) {
console.log(val);
}
// 1
// 2
// 3
console.log([...obj]); // [ 1, 2, 3 ]
可以选择增加一个return()
方法,该方法在for...of
时碰到brack
或出错时触发,该方法返回一个对象,应该有done
属性表示迭代结束
let obj = {
data: [1, 2, 3],
[Symbol.iterator]() {
let index = 0; //游标
function next() {
if (index < this.data.length) { //还没到最后一个元素
return {
value: this.data[index++], //返回当前值,并且指针后移
done: false //状态是未结束
};
} else { //到最后一个
return { value: undefined, done: true }; //返回undefuned,状态是结束
}
}
return {
next: next.bind(this), //必须返回一个迭代器对象,并且有next方法
return() { //可以选择是否有一个return方法
console.log("brack 触发了 或 抛出了异常");
return { done: true };
//必须返回一个对象
//此方法定义在方法中会与方法中的return关键字有冲突,必须定义在对象上
}
};
}
};
for (const val of obj) {
console.log(val);
if (val === 2) {
break;
// throw new Error();
}
}
// 1
// 2
// brack 触发了 或 抛出了异常
Generator函数
也称为生成器函数
声明
- 于普通的函数类似,只不过使用星号标记方法名,但是不能作为构造函数那样使用
- 若执行Generator函数,函数不会立刻执行,而是返回一个迭代器对象,所以使用该函数可以定义迭代器接口
- 返回的迭代器对象中有
[Symbol.iterator]()
方法,并且该方法返回值就是迭代器本身,所以可以对该迭代对象使用for...of...
和扩展运算符 - 使用该返回的迭代器对象可以像代码调试断点那样暂停和恢复执行函数
function *fun(){ //只要函数名前面有星号即可,不必在意空格
yield 1; //可以理解为断点
yield 2;
yield 3;
}
let gen = fun();
console.log(gen); //Object [Generator] {}
console.log(gen.next()); //{ value: 1, done: false } //断点表达式的值和还未调试完
console.log(gen.next()); //{ value: 2, done: false }
console.log(gen.next()); //{ value: 3, done: fasle }
console.log(gen.next()); //{ value: undefined, done: true } //函数执行结束返回值是undefined,调试完
console.log(gen === gen[Symbol.iterator]());
//对Generator直接使用for...of增强型for循环、解构赋值、扩展运算符和Array.form()方法转化为数组的前提
for (const val of fun()) {
console.log(val);
}
// 1
// 2
// 3
console.log([...fun()]); //[ 1, 2, 3 ]
yield表达式
- 只能用于Generator函数中,所以在Generator函数中使用回调时特别要注意不能使用
- 代表函数执行暂停位置,并返回包含暂停位置表达式的值和迭代器状态的对象,若
yield
后没有跟着任何表达式则相当于undefined
yield
表达式参与计算时需要用圆括号包裹,否则会报错,但是作为赋值语句的右侧或函数的参数时无需加圆括号yield
与return
的区别return
会终止整个Generator函数的执行,并返回retuen
后的表达式的值和迭代器状态是true
的对象;若没有return
语句,则函数一直会执行到最后,返回undefined
和迭代器状态是true
yield
不会终止Generator函数的执行,并返回yield
后的表达式的值和迭代器的状态是false
的对象
function* fun() {
yield; //返回对象的value值为undefined
yield 2;
return 3;
yield 4; //永远不会执行到,因为return会终止掉整个函数
}
let gen = fun();
console.log(gen.next()); //{ value: undefined, done: false }
console.log(gen.next()); //{ value: 2, done: false }
console.log(gen.next()); //{ value: undefined, done: true }
yieid*表达式
- 用来简化在一个Generator函数中执行另一个Generator函数
function *innerGen() { //另一个Generator函数
yield 'a';
yield 'b';
}
function *outerGen() {
yield 'x';
for (let val of innerGen()) { //执行另一个Generator函数
yield val;
}
yield 'y';
}
console.log([...outerGen()]); //[ 'x', 'a', 'b', 'y' ]
// 相当于
function *fun(){
yield "x";
yield "a";
yield "b";
yield "y";
}
console.log([...fun()]); //[ 'x', 'a', 'b', 'y' ]
//简写方式
function *fn(){
yield "x";
yield* innerGen();
yield "y"
}
console.log([...fn()]); //[ 'x', 'a', 'b', 'y' ]
- 也可以执行另一个只要是实现迭代器接口的对象
function *gen() {
yield* [1, 2, 3];
}
//相当于
function *gn() {
yield 1;
yield 2;
yield 3;
}
console.log([...gen()]); //[ 1, 2, 3 ]
console.log([...gn()]); //[ 1, 2, 3 ]
function *gen() {
yield* "123";
}
//相当于
function *gn() {
yield "1";
yield "2";
yield "3";
}
console.log([...gen()]); //[ '1', '2', '3' ]
console.log([...gn()]); //[ '1', '2', '3' ]
next方法
要注意的是,在第一次调用
next()
方法时只是启动Generator函数执行,没有yield表达式,所以传入任何参数都是无意义的
参数 | 说明 |
---|---|
无参 | 使用undefined 替换Generator函数中上一次yield 表达式的值,供向下执行时使用 |
一个参数 | 使用该参数替换Generator函数中上一次的yield 表达式的值,供向下执行时使用 |
function *fun(){
let temp;
temp = yield 1;
console.log("Generator中的yield替换值是:"+temp);
temp = yield 2;
console.log("Generator中的yield替换值是:"+temp);
}
let gen = fun();
gen.next();
gen.next();
gen.next();
// Generator中的yield替换值是:undefined
// Generator中的yield替换值是:undefined
gen = fun();
gen.next("第一次的参数"); //第一次执行时只是启动,还没有yield表达式,所以传入任何参数都是无意义的
gen.next("第二次的参数"); //从第二次执行开始,将此开始的yield表达式替换为传入的参数
gen.next("第三次的参数");
// Generator中的yield替换值是:第二个yield的参数
// Generator中的yield替换值是:第三个yield的参数
throw方法
由于next方法只会将参数只是替换为表达式的值,无法替换为一条语句,若想将
yield
替换为一条抛出错误的语句时就必须依赖于throw()
方法
- 若内部没有处理该错误,则会抛给调用
throw()
方法处 - 执行
throw()
方法后也会像next()
方法执行后指向下一个断点
function* fun() {
try {
yield; //相当于在这里 throw new Error;
} catch (e) {
console.log("捕捉到了错误");
}
};
let gen = fun();
gen.next(); //先将函数启动,若启动函数就抛出错误则相当于在外部直接抛出错误
gen.throw(new Error()); //将yield替换为 throw new Error;语句
//捕捉到了错误
return方法
同样的,若想将
yield
替换为一条return语句时就必须依赖return()
方法
- 若内部有finally语句块,就像平常函数一样会进入finally语句块中之后在结束函数执行
function* fun() {
yield 1;
yield 2; //相当于 return "结束"
yield 3;
};
let gen = fun();
console.log(gen.next()); //{ value: 1, done: false }
console.log(gen.return("结束")); //{ value: '结束', done: true }
console.log(gen.next()); //{ value: undefined, done: true }
链判断运算符
- 可以通过
变量?.属性或操作
的形式,若左侧变量存在则执行右侧操作,若左侧变量不存在则返回undefined
let outerObj = {
innerObj:{
fun(){return "fun"}
}
}
console.log(outerObj?.innerObj?.fun?.()?.length); //3
//依次的从左到右,outerObj存在则会向内寻找innerObj
//innerObj存在则会向内寻找fun
//fun存在则会调用该函数
//该函数返回值不是undefined或null则会获取length属性
//若这之中有一个不存在则都会返回undefined
空判断运算符
- 可以通过
变量??默认值
的方式指定若变量不存在时会返回默认值
a = false;
console.log( a || "default"); //default
a = "";
console.log( a || "default"); //default
a = 0;
console.log( a || "default"); //default
//用判空运算符解决
a = false;
console.log( a ?? "default"); //false
a = "";
console.log( a ?? "default"); //""
a = 0;
console.log( a ?? "default"); //0
新增数据类型和结构
BigInt
- 内部以字符串的形式存储,可以表示任意大的数字,但是不能是小数,所以做除法运算时会像其他语言中的
int
类型数据一样只保留整数部分 typeof
返回结果是bigint
- 转化成字符串或数值类型时后缀
n
会消失 - 不能自动转化,所以不能与普通数值直接计算,但是在作为逻辑运算时可以自动转化成布尔值
- 不能使用
>>>
运算符运算
console.log(0n == 0); //true
console.log(0n === 0); //false
console.log(5n / 2n); //2n
console.log(typeof 0n); //bigint
console.log(String(0n)); //0
console.log(Number(0n)); //0
console.log(!0n); //true
创建方式
字面量方式
- 必须添加
n
后缀 - 可以有使用
-
号做前缀,但是不能使用+
号做前缀,否则会报错 - 同样可以使用各种进制的前缀
函数和构造函数
构造函数与函数形式的用法和返回结果是一样的,只不过是语义不同,函数代表转化,而构造函数代表新生
- 必须有参数
- 数字只能是整数,不能是小数,可以使用科学计数法
- 忽略前后空格,若是全数字字符串转化为数字(支持正负号),有一个非数字字符就会报错,要注意带
n
后缀的字符串和科学计数法的字符串,也是会报错的 - 空字符串转化为0
false
转化为0true
转化为1undefined
和unll
都会报错
静态方法
方法名 | 描述 |
---|---|
BigInt.asUintN(width, BigInt) |
以的精度无符号数存储的BigInt |
BigInt.asIntN(width, BigInt) |
以的精度有符号数存储的BigInt |
BigInt.parseInt(string) |
将字符串转化成BigInt ,用法与Number.parseInt() 一致,但浏览器还未支持 |
let max = 3n;
console.log(BigInt.asIntN(3, max)); //3n
console.log(BigInt.asIntN(3, max + 1n)); //-4n
max = 3n;
console.log(BigInt.asUintN(2, max)); //3n
console.log(BigInt.asUintN(2, max + 1n)); //0n
Symbol
属于原始数据类型,可以理解为一种特殊的字符串,主要解决为对象添加属性保证不会覆盖掉之前的属性,也就是说该原始数据类型可以作为对象的属性
typeof
返回结果是bigint
- 是独一无二的
- 该数据类型的
toString()
方法返回的是定义时的方法中的字符串去掉双引号形式 - 该原始数据类型无法与任何数据类型直接运算,包括自己
- 对象属性是该数据类型时,不会被
for...in
、for...of
、Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
获取到,只有``Object.getOwnPropertySymbols(obj)`才可以取得的
let a = Symbol("a");
let b = Symbol("a");
console.log(a === b); //false
console.log(typeof a); //symbol
console.log(a); //Symbol(a)
//console.log( a + a ); //报错
//console.log( a + "" ); //报错
创建方式
通过Symbol()
函数生成,所以在作为对象的属性时需要用方括号表达式,由于不是字符串在取值时也不能使用点属性的形式,需要用方括号的形式
-
注意不是构造函数
-
若参数不是字符串则会先调用该参数的
toString()
方法转化为字符串
let a = Symbol("a");
//let b = new Symbol("b"); //报错,不能使用构造函数方式
let b = Symbol({});
console.log(b); //Symbol([object Object])
let a = Symbol("a");
let obj = {
[a]:1
};
console.log(obj.a); //undefined,此时a是字符串,所以返回undefined
console.log(obj[a]); //1,此时a是Symbol类型的变量,可以取到
静态方法
方法名 | 描述 |
---|---|
Symbol.for(str) |
与Symbol() 函数类似,都是创建一个symbol 数据类型的数据,不同的是该方法会先从全局中找有没有该参数的symbol 已经注册,若没有在创建并注册,若有则返回之前注册的 |
Symbol.keyFor(symbol) |
返回已注册的symbol 类型的数据,若未注册则返回undefined |
let a = Symbol("a");
console.log(a === Symbol("a")); //false
console.log(a === Symbol.for("a")); //false ,未注册,第一次注册
console.log(Symbol.for("a") == Symbol.for("a")); //treu ,已经注册返回注册的
console.log(Symbol.keyFor(Symbol.for("a"))); //a 返回已经注册的symbol的key,也就是描述
console.log(Symbol.keyFor(a)); //undefined 未注册返回undefined
实例属性
属性名 | 描述 |
---|---|
Symbol.prototype.description |
返回定义时的字符串描述信息 |
Set
构造函数
必须使用new
关键字,否则会报错
参数 | 描述 |
---|---|
无参 | 创建一个空的set集合 |
一个数组 | 将数组转化为set集合,可以实现数组去重操作,内部使用严格相等且识别NaN |
实例方法
继承自Object的
keys()
、values()
和entries()
方法,set集合中键和值是同一个值
方法名 | 描述 |
---|---|
Set.prototype.size |
是个属性,返回set集合成员数 |
Set.prototype.add(value) |
将value 添加到该set集合中,并返回该set集合,所以可以链式调用 |
Set.prototype.delete(value) |
将value 从set集合中删除,成功返回true |
Set.prototype.has(value) |
判断该set集合中是否有value 成员 |
Set.prototype.clear() |
清空该set集合 |
Set.prototype.forEach(callback[,thisTar]) |
与数组的forEach() 方法一样,值相当于数组的元素,键相当于数组的下标 |
Map
继承自Object的
keys()
、values()
和entries()
方法,map集合中的键可以存放任意数据类型
构造函数
必须使用new
关键字,否则会报错
参数 | 描述 |
---|---|
无参 | 创建一个空的map集合 |
一个元素为[key,value] 二维数组 |
将数组转化为map集合,内部使用严格相等且识别NaN |
实例方法
方法名 | 描述 |
---|---|
Map.prototype.size |
是个属性,返回map集合的成员数 |
Map.prototype.set(key,value) |
将键值对添加到该set集合中,并返回该set集合,所以可以链式调用 |
Map.prototype.get(key) |
通过键获取值,没有就返回undefined |
Map.prototype.has(key) |
判断该map集合中是否有该键 |
Map.prototype.delete(key) |
通过键删除键值对 |
Map.prototype.clear() |
清空map集合 |
Map.prototype.forEach(callback[,thisTar]) |
与数组的forEach() 方法一样,值相当于数组的元素,键相当于数组的下标 |
面向对象新增
class
- 使用命令方式类似于
let
声明的变量一样,不存在变量提升 - 与函数一样,可以使用表达式的形式定义
- 其实应该算es5构造函数的一个语法糖,他本身就是一个函数
new A(); //不存在变量提升,ReferenceError: A is not defined
class A{ //命令方式
}
console.log(typeof A); //function
console.log(A === A.prototype.constructor); //true ,类就指向构造函数
console.log(A.name); //A
var A = class{ //表达式方式
}
console.log(A.name); //A
var B = A; //与A指向同一块内存空间
console.log(B.name); //A 返回函数第一次的变量名
var Outer = class Inner{ //表达式和命令方式同时使用
}
console.log(Outer.name); //Inner
Outer.name = "newName"; //只读属性无法修改默默失效
console.log(Outer.name); //Inner
console.log(Inner.name); //于函数一样只能在内部使用,ReferenceError: Inner is not defined
- 类中的方法必须使用像对象的简写属性那样,但是不能加逗号分隔,同时也可以使用属性表达式的方式定义方法名
- 类中的方法该类定义在原型上,所以为整个类扩展就可以在类的原型上扩展
- 唯一不同的是,在类中直接定义的方法是不可枚举的,而为类扩展的是可枚举的
class A{
fun1(){}
["fun"+2](){}
}
console.log(new A().fun1 === A.prototype.fun1); //true
A.prototype.fun3 = function(){};
console.log(Object.keys(A.prototype)); //[ 'fun3' ]
console.log(Object.getOwnPropertyNames(A.prototype)); //[ 'constructor', 'fun1', 'fun2', 'fun3' ]
- 使用
class
关键字定义的类,不能像构造函数那样不使用new
关键字也可以调用,不使用new
关键字会报错 - 类内部使用的是严格模式,将方法拿出来后,方法中的
this
指向会是undefined
class A{
fun(){console.log(this)}
}
console.log(new A());
console.log(A()); //TypeError
let a = new A();
a.fun(); //A {}
let fun = a.fun;
fun(); //undefined
- 同样可以指定某个属性的存取器,在类上定义的存储器属于实例对象而不是属于原型对象上的,只设置get未设置set则该属性只读
class A {
get p() {
console.log("get");
return this.value;
}
set p(value) {
console.log("set");
this.value = value;
}
fun(){}
}
let a = new A();
a.p = 123; //set
console.log(a.p); //get
//123
console.log(a.p === A.prototype.p); //false
console.log(a.fun === A.prototype.fun); //true
constructor方法
- 每个类都要有一个
constructor
方法,若没有显式的给定就会默认创建一空的constructor
方法 - 该方法中的内容与es5中的构造函数完全一致,使用
new
关键字时会调用该方法,this
指向创建的对象所以定义在构造方法中的属性属于对象而不是原型对象中 - 若
return
一个非对象内容会忽略,若return
一个对象则new
的时候就是该返回对象而不是this
static静态方法
- 使用
static
修饰符修饰的方法 - 只能通过类名直接调用,不能通过类的实例对象调用
- 静态方法中的
this
指向的是当前类 - 父类的静态方法可以被子类继承
继承
- 子类继承符类使用
extends
关键字,并且必须得在子类构造中显式的在this
关键字出现之前通过super
调用父类构造 - 除拉实例属性和方法以外,父类的静态方法可以被子类继承
class parent { }
class son extends parent {
constructor() {
super();
}
}
//若子类省略构造方法,则会默认创建一个空的且调用父类的方法
class son extends parent {
}
//相当于
class son extends parent {
constructor(...args) {
super(...args);
}
}
super关键字
函数形式只能在子类构造方法中使用,其他地方无法使用
所处环境 | 使用方式 | 描述 |
---|---|---|
类的构造方法中 | super() 函数方式 |
代表父类的构造方法 |
类的方法中 | super.属性 对象方式 |
指向父类的原型对象,并且此时父类中this 指向子类对象,也就是说不能调用父类实例对象的属性 |
对象的方法中 | super.属性 对象方式 |
指向该对象的原型 |
类的静态方法中 | super.属性 对象方式 |
指向父类,并且此时父类中this 指向子类,也就是说子类静态方法中使用该方式调用的是父类静态方法 |
属性的新写法
实例属性
之前版本中,实例属性要通过类中的constructor方法添加,现在可以像Java那样在构造函数外部直接声明
class A{
constructor(){
this.id = "a";
}
}
console.log(new A().id); //a
//现在的
class A {
id = "a";
age;
}
console.log(new A().id); //a
console.log(new A().age); //undefined
静态属性
之前版本中,静态属性只能在类的外部添加,现在可以像Java那样在构造函数外部声明static
修饰的属性,并且这种方式设置的静态属性会覆盖掉函数原有属性
class A {
}
A.id = "a";
console.log(A.id);
A.name = "a"; //函数name属性是只读的,不会更改
console.log(A.name); //A 返回类名
//现在的
class A {
static id = "a"
static name = "a"; //会覆盖掉函数的name属性
}
console.log(A.id); //a
console.log(A.name); //a 返回设置的,而并非类名
私有属性和方法
之前的版本中类中是没有私有属性和方法的,现在可以使用#
开头的属性名或方法名定义私有属性和方法了
class A {
#id;
getId(){
return this.#id;
}
setId(id){
this.#id = id;
}
}
const a = new A();
// console.log(a.#id); //SyntaxError 私有属性在类外部使用会报错
a.setId(10);
console.log(a.getId()); //10
标准库新增
对象
super关键字
- 指向该对象的原型
- 只能在对象方法中使用
静态方法
方法名 | 描述 |
---|---|
Object.is(object,object) |
判断两个变量的值是否严格相等,与严格相等运算符不同的是,NaN 和NaN 是相等的;+0 和-0 是不相等的 |
Object.assign(target[,...source]) |
将source 对象中可枚举的属性复制到target 对象中,即对象的合并;同名属性会被覆盖掉,无法复制描述对象中的存取器;只有一个参数会将这个对象原封不动的返回,若不是对象会现转为对象在返回,若是null 和undefined 会报错;之后个参数若是null 或undefined 不会报错 |
Object.getOwnPropertyDescriptors(obj) |
获取obj 的所有属性的描述对象,可以用此方法配合assign() 方法复制存取器 |
Object.values(obj) |
返回obj 对象自身的所有属性值的数组(不包括不可枚举的属性) |
Object.entries(obj) |
返回obj 对象自身的所有元素以[属性名,属性值] 形式的二维数组(不包括不可枚举的属性) |
Object.fromEntries(arr) |
返回将元素以[属性名,属性值] 形式的二维数组转化成的对象 |
包装对象
String
静态方法
方法名 | 描述 |
---|---|
String.fromCodePoint(...Unicode) |
可以将超过0xFFFF 的码点字符识别转化为字符串 |
实例方法
查询方法
方法名 | 描述 |
---|---|
String.prototype.codePointAt(index) |
返回指定下标的字符Unicode 编码,可以识别超过0xFFFF 的码点字符,若下标不合法返回NaN |
String.prototype.includes(str) |
判断该字符串中是否包含str 字符串 |
String.prototype.startsWith(prefix) |
判断该字符串是否以prefix 为前缀 |
String.prototype.endsWith(suffix) |
判断该字符串是否以suffix 为后缀 |
转化方法
方法名 | 描述 |
---|---|
String.prototype.repeat(count) |
返回一个当前字符串重复count 次拼接的字符串;小数会取整,小于负一的数或Infinity 会报错,小于0大于-1或NaN 会视为0 |
String.prototype.padStart(len,str) |
返回一个使用str 头部补全(一次不够则会重复补)到len 长度的字符串 |
String.prototype.padEnd(len,str) |
返回一个使用str 尾部补全(一次不够则会重复补)到len 长度的字符串 |
String.prototype.trimStart() |
返回一个去除头部空白的字符串 |
String.prototype.trimEnd() |
返回一个去除尾部空白的字符串 |
数值
- 八进制使用
0o
做前缀
静态方法
将全局有管数值型的函数移植到Number上
方法名 | 描述 |
---|---|
Number.isFinite(number) |
判断是否是有限数,非数字会先转化成数值型 |
Number.isNaN(number) |
判断是否是NaN ,非数值会先转化成数值型 |
Number.parseInt(string) |
将字符串转化成整型数值 |
Number.parseFloat() |
将字符串转化成浮点型数值 |
新增了Number.isInteger()
方法、Number.isSafeInteger()
方法和Number.EPSILON
属性
方法名 | 描述 |
---|---|
Number.isInteger(number) |
判断是否为整数,注意小数点后全零也是整数 |
Number.isSafeInteger(number) |
判断是否在能精确表示的数值范围中 |
Number.EPSILON |
极小数,用来做浮点数运算误差范围 |
数组
静态方法
方法名 | 描述 |
---|---|
Array.from(obj[,callback]) |
将伪数组对象转化成正真的数组,会将空位转化为undefined ;callback 类似与数组中的map() 函数处理对象的属性,但是不会处理length 属性 |
Array.of(...element=[]) |
将一组值转化成一个数组,弥补了Array() 构造函数的参数不统一 |
实例方法
转化方法
方法名 | 描述 |
---|---|
Array.prototype.copyWithin(target,start=0,end=this.length) |
原数组的target 位置开始,使用原数组中范围的元素进行替换 |
Array.prototype.fill(element,start=0,end=this.length) |
使用element 填充数组元素,可以指定填充范围 |
Array.prototype.flat(layer=1) |
将嵌套数组拉平一层,可以指定层数,若参数是Infinity 则都会转化成一维数组 |
Array.prototype.flatMap(callback[,thisTar]) |
将嵌套数组拉平一层并且只能拉平一层,cllback 类似于数组的map() 函数处理数组元素,第二个参数绑定回调的this 指向 |
转化方法
方法名 | 描述 |
---|---|
Array.prototype.find(callback[,thisTar]) |
找出第一个使该回调函数返回true 的元素,第二个参数绑定回调的this 指向 |
Array.prototype.findIndex(callback[,thisTar]) |
找出第一个使该回调函数返回true 的元素下标,第二个参数绑定回调的this 指向 |
Array.prototype.include(element) |
判断该数组中是否包含element 元素,可以识别NaN |
Math
静态方法
方法名 | 描述 |
---|---|
Math.trunc(number) |
取整 |
Math.cbrt(number) |
立方根 |
Math.hypot(...number) |
平方和的平方根 |
正则
新增修饰符
符号 | 描述 |
---|---|
y |
粘连,与全局搜索不同的是每次需要从上次匹配结束位置开始的第一个字符匹配正则,否则匹配不到返回null,也就是说他们要连序匹配 |
u |
可以识别超过0xFFFF 的码点字符 |
s |
dotAll模式,. 可以匹配任意字符就包括换行符等终止符号了 |
实例属性
属性名 | 描述 |
---|---|
RegExp.prototype.sticky |
判断是否设置了y 修饰符 |
RegExp.prototype.unicode |
判断是否设置了u 修饰符 |
RegExp.prototype.dotAll |
判断是否设置了s 修饰符 |
RegExp.prototype.flags |
返回所有修饰符,包括新增的 |
命名的分组捕获
可以为正则的每一组匹配最前面使用?<name>
方式起名
let t = "1997-01-01".match(/(\d{4})-(\d{2})-(\d{2})/);
console.log(t);
// [ '1997-01-01',
// '1997',
// '01',
// '01',
// index: 0,
// input: '1997-01-01',
// groups: undefined ] //此时groups是undefined
t = "1997-01-01".match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/);
console.log(t);
// [ '1997-01-01',
// '1997',
// '01',
// '01',
// index: 0,
// input: '1997-01-01',
// groups: { year: '1997', month: '01', day: '01' } ]
// 此时可以根据groups中自己起的名字获取那一组匹配
先行和后行断言
使用匹配单词(?断言)
的形式指定先行断言匹配,使用(?>断言)匹配单词
的形式指定后行断言,主要这里的括号不是分组匹配
//先行断言
let str = "a1_a3";
console.log(str.match(/a(?=1)/)); //匹配a后紧跟着是1的那个a //[ 'a', index: 0]
console.log(str.match(/a(?!1)/)); //匹配a后紧跟着不是1的那个a //[ 'a', index: 3]
//后行断言
let str = "1a_3a";
console.log(str.match(/(?<=1)a/)); //匹配a前紧跟着是1的那个a //[ 'a', index: 1]
console.log(str.match(/(?<!1)a/)); //匹配a前紧跟着不是1的那个a //[ 'a', index: 4]
代理
通过一个代理对象,通过对代理对象操作,间接的对原对象的操作,这就是代理
虽然可以为某个属性设置setter/getter
方法,但是这种操作是为多个属性设置时会略显繁琐,而且setter/getter
方法能力有限,只能拦截存器操作
构造函数
- 必须使用构造函数
new
出来才行,否则会报错 - 必须有两个参数,第一个参数是原对象
target
,第二个参数是代理配置对象handler
- 返回一个代理对象,通过该代理对象间接的操作原对象
- 若一个对象的原型对象是代理对象,则从使用该原型对象上的属性时会拦截,而对于自身就有的不会拦截,也就是说从代理对象上获取值才会受到拦截
静态方法
Proxy.revocable()
这是一个函数,所以不要使用new
关键字,返回一个可撤销代理的代理对象,以{proxy: 代理对象, revoke:revoke()}
,代理对象就和使用构造函数返回的没有区别,revoke()
一旦执行,该代理对象就被收回了
var proxy = Proxy.revocable({}, {});
console.log(proxy); //{ proxy: [Function: fun], revoke: [Function] }
proxy.proxy.id = 1; //设置值需要使用该返回对象返回的代理对象
console.log(proxy.proxy.id); //获取设置的属性
proxy.revoke(); //撤销代理
console.log(proxy); //TypeError,因为被垃圾回收机制回收所以抛出错误
配置对象中的拦截方法
这里所有的拦截方法中的参数都可以省略
方法名 | 拦截操作 | 参数描述 | 返回值要求 |
---|---|---|---|
handler.get(target,propKey,receiver) |
拦截属性取值操作 | target 是原对象,propKey 是原对象属性,receiver 是代理对象本身 |
返回值就是获取对象的属性值,无要求 |
handler.set(target,propKey,value,receiver) |
拦截属性设置操作 | target 是原对象,propKey 是原对象属性,value 是设置的属性值,receiver 是代理对象本身 |
最好返回true 代表设置成功,不要返回false 若是严格模式返回false 会报错 |
handler.has(target,propKey) |
拦截in 运算符判断该对象是否有该属性操作 |
target 是原对象,propKey 是原对象属性 |
必须返回一个布尔值代表该属性是否存在 |
handler.deleteProperty(target,propKey) |
拦截delete 删除对象属性操作 |
target 是原对象,propKey 是原对象属性 |
必须返回一个布尔值代表是否删除成功 |
handler.apply(target,thisTar,args) |
拦截该函数对象被当作函数调用时的操作,包括call() 方法和apply() 方法 |
target 是原对象,thisTar 是函数被调用时的this指向,args 是实参数组形式 |
返回值就是函数调用的返回值,无要求 |
handler.construct(target,args,receiver) |
拦截该函数对象被当作构造函数使用创建的操作,包括new 和反射创建 |
target 是原对象,args 是实参数组形式,receiver 是代理对象本身 |
返回值是创建的对象,所以必须返回一个对象作为创建的对象 |
反射
正常情况下,需要通过一个构造函数,再通过new关键字创建实例化对象,从而调用该实例化对象的方法。而通过对象,来寻找其所在构造函数,或通过方法来找寻所在对象,这就是反射
静态方法
新的方法
方法名 | 描述 |
---|---|
Reflect.get(target,proKey[,thisTar]) |
获取target 的proKey 属性,若该属性设置了存取器,则thisTar 可以设置该存储器中的this 指向 |
Reflect.set(target,proKey,value[,thisTar]) |
设置target 的proKey 属性为value ,若该属性设置了存取器,则thisTar 可以设置该存储器中的this 指向 |
Reflect.has(target,proKey) |
判断target 对象中是否有proKey 属性 |
Reflect.deleteProperty(target,proKey) |
删除target 对象中的proKey 属性,成功返回true 失败返回false |
Reflect.construct(cons,args) |
使用cons 构造函数,根据args 参数创建实例对象,cons 必须是构造函数,否则会报错 |
Reflect.apply(func,thisTar,args) |
调用func 函数,thisTar 是函数中的this 指向,args 是函数中的参数的数组形式 |
Object中迁移的方法
方法名 | 描述 | 对于Object对象中的方法 |
---|---|---|
Reflect.getPrototypeOf (obj) |
获取obj 对象的prototype 对象 |
Object.getPrototypeOf() |
Reflect.setPrototypeOf (obj,objPrototype) |
设置obj 对象的prototyoe 对象为objPrototype ,不同的是该方法返回的是布尔值 |
Object.setPrototypeOf() |
Reflect.defineProperty (obj,propertyName,propertyDesc) |
通过描述对象,为obj 对象定义某个属性 |
Object.defineProperty() |
Reflect.getOwnPropertyDescriptor (obj,propertyName) |
获取obj 的propertyName 属性的描述对象,没有该属性返回undefined |
Object.getOwnPropertyDescriptor() |
Reflect.preventExtensions (obj) |
防止obj 对象扩展,无法添加新属性 |
Object.preventExtensions() |
Reflect.isExtensible (obj) |
判断obj 对象是否可扩展 |
Object.isExtensible() |
Reflect.ownKeys (obj) |
返回obj 对象自身的所有属性名的数组(包括不可枚举的属性和Symbols 的键名) |
Object.getOwnPropertyNames() Object.getOwnPropertySymbols |
异步新增
Promise
promise有三个状态,pending
挂起状态,fulfulled
成功状态,rejected
失败状态,这三种状态只能由挂起到成功或失败状态这两条途径,而且一旦成功或失败状态就不可更改,所以在new
一个promise实例时只要执行了一个resolve
或reject
之后再执行的resolve
和reject
将毫无意义
构造方法
- 参数必须是一个函数,否则会报错,并且该函数会立即同步执行
- 该回调函数有两个参数
resolve
和reject
也是函数类型,这两个参数改变状态的代码由js引擎实现resolve
:代表成功的回调函数,会将该promise状态转化为fulfulled
成功状态value
:该函数有一个参数用来保存成功的结果
reject
:代表失败的回调函数,会将该promise状态转化为rejected
失败状态,失败状态必须要处理,否则会有警告reason
:该函数有一个参数用来保存失败的结果
- 抛出错误,相当于执行
reject(error)
静态方法
方法名 | 描述 |
---|---|
Promise.resolve(object) |
无参,则将返回一个空的且状态是成功的promise对象;参数若是普通值,则将该值放入一个状态为成功的promise对象中并返回;参数若是promise对象,则原封不动返回该对象;参数若是一个带有then() 方法的对象,会立即异步的执行这个then() 方法,该then() 方法中可以控制返回promise状态,也就是说该then() 方法也有两个函数类型参数,一个是resolve 和reject |
Promise.reject(reason) |
将reason 放入一个状态为rejected 的promise对象中并返回 |
Promise.all(pramises) |
接收一个集合,若数组元素不是promise对象会先调用Promise.resolve() 方法包装成一个promise对象,返回一个promise对象,该对象的状态和值由promises集合中所有对象的执行结果决定,全部成功则返回的promise对象状态就是成功的,值就是以数组形式对应的执行结果的值,有一个失败则返回的promise对象状态就是失败的,值是第一个失败的结果 |
Promise.race(pramises) |
接收一个集合,若数组元素不是promise对象会先调用Promise.resolve() 方法包装成一个promise对象,返回一个promise对象,该对象的状态和值由promises数中所有对象的执行结果决定,总是返回第一个完成的promise对象,无论是成功还是失败 |
Promise.allSettled(pramises) |
接收一个集合,若数组元素不是promise对象会先调用Promise.resolve() 方法包装成一个promise对象,返回一个promise对象,该对象的状态为成功的promise对象,只不过该对象的值是将promises集合中每个promise对象的返回值都封装成了promise对象,这些promise对象的状态由上一次的执行结果决定 |
实例方法
方法名 | 描述 |
---|---|
Promise.prototype.then(resolve[,reject]) |
resolve 是成功状态的回调函数,reject 是失败状态的回调函数且可省略,并且这些回调是异步执行的;返回一个新的promise对象,由执行这两个回调函数的返回值决定;返回一个普通值=成功的包装该值的promise对象,返回一个promise对象=该promise对象,抛出错误=返回一个包装该错误的promise对象 |
Promise.prototype.catch(reject) |
reject 是失败的回调函数,相当于调用then(undefuned,reject) 方法所以,返回的promise对象与then() 方法一致 |
Promise.prototype.finally(close) |
close 是无论当前promise是什么状态都会执行的的回调函数,无论该回调中是有retuen 语句,总是返回一个值为undefined 状态为成功的promise对象 |
Promise应用
可以达到串行执行异步任务的效果
new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(1);
},1000);
}).then((value)=>{
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(value+"处理value后");
},500)
})
}).then((value)=>{
console.log(value);
}).catch((error)=>{
console.log(error);
})
Promise原理
const PENDING = Symbol("pending"); //挂起状态
const REJECTED = Symbol("rejected"); //失败状态
const FULFULLED = Symbol("fulfilled"); //成功状态
/*
Promise:构造函数,创建promise实例
excutor:执行器函数,同步立即执行
*/
function Promise(excutor) {
const self = this; //保存当前函数this方便在内部函数使用
this.status = PENDING; //当前promise对象的状态,初始是pending
this.data = undefined; //存储结果数据
this.callbacks = []; //回调函数队列,元素以{ onResolved(){}, onRejected(){} }形式保存
try { //若执行器执行抛出异常则说明该promise是失败的
excutor(resolve, reject); //立即执行执行器函数
} catch (error) { //将抛出的错误信息当作失败回调函数的reason
reject(error)
}
/*
resolve:将当前promise对象状态转为fulfilled,保存value到当前promise对象的data中,异步执行函数队列中成功的回调函数
value:成功的结果
*/
function resolve(value) {
if (self.status !== PENDING) return; //状态只能是从pending到其他状态,若已是成功或失败则不需要执行该函数
self.status = FULFULLED; //改变状态为成功 fulfilled
self.data = value; //保存value到promise的data中
setTimeout(() => { //若在调用resolve之前为此promise对象绑定了回调函数,则依次异步执行这些回调函数
for (let i = 0; i < self.callbacks.length; i++) { //遍历函数执行队列并执行成功的回调
self.callbacks[i].onResolved();
}
});
}
/*
reject:将当前promise对象状态转为rejected,保存reason到当前promise对象的data中,异步执行函数队列中失败的回调函数
reason:失败的原因
*/
function reject(reason) {
if (self.status !== PENDING) return; //状态只能是从pending到其他状态,若已是成功或失败则不需要执行该函数
self.status = REJECTED; //改变状态为失败 rejected
self.data = reason; //保存reason到promise的data中
setTimeout(() => { //若在调用reject之前为此promise对象绑定了回调函数,则依次异步执行这些回调函数
for (let i = 0; i < self.callbacks.length; i++) { //遍历函数执行队列并执行失败的回调
self.callbacks[i].onRejected();
}
});
}
}
/*
then:为当前对象绑定回调函数,再根据回调返回值指返回一个新的promise对象
onResolved:成功的回调函数
onRejected:失败的回调函数
*/
Promise.prototype.then = function (onResolved, onRejected) {
const self = this; //保存当前函数this方便在内部函数使用
onResolved = typeof onResolved === "function" ? onResolved : value => value; //传入参数是非函数,指定回调向下传递参数即可
onRejected = typeof onRejected === "function" ? onRejected : reason => { throw reason } //省略错误回调,向下传递抛出错误,在handle函数中会转化为失败的promise达到向下传递错误的目的
return new Promise((resolve, reject) => { //返回一个新的promise对象
/*
handle:统一处理onResolved或onRejected指定回调中抛出错误、promise对象、普通值的情况,从而决定返回新的promise对象的状态
callback:同步的回调函数,主要使用该回调函数的返回值指定决定新的promise对象的状态
*/
function handle(callback) {
try { //若执行指定回调函数抛出错误则说明该promise是失败的
const result = callback(self.data); //取得回调函数的返回值
if (result instanceof Promise) { //若返回值是promise对象
result.then(resolve, reject); //则新的promise对象的状态由该返回的promise对象的状态决定
} else { //若返回值是普通值则
resolve(result); //则新的promise对象的状态是成功的
}
} catch (error) { //将抛出的错误信息当作失败回调函数的reason
reject(error);
}
}
switch (self.status) {
case PENDING: //若当前promise对象状态是挂起状态,也就是创建promise对象时,执行器中的异步操作才执行到resolve或reject回调,即先绑定后执行
self.callbacks.push({ //将绑定的回调函数添加到回调队列上,等promise对象异步操作时执行resolve或reject回调再由执行器执行
onResolved() { handle(onResolved) }, //将处理成功的回调按照回调队列要求包装在onResolved中
onRejected() { handle(onRejected) } //将处理失败的回调按照回调队列要求包装在onRejectedd中
})
break;
case FULFULLED: //若当前promise对象状态是成功状态,也就是创建promise对象时,执行器中的同步操作执行到resolve(不严谨),即先执行后绑定
setTimeout(() => { handle(onResolved) });
//先执行后绑定是不可能的,只是执行了改变状态的代码,由于执行器其实是由于回调队列中是空的,就直接跳过了执行回调队列的操作,要在调用then方法绑定成功回调时时立刻异步执行
// handle(onResolved);
break;
case REJECTED: //同理,若当前promise对象的状态是失败状态,也就是创建promise对象时,执行器中的同步操作执行到reject回调(不严谨),即先执行后绑定
setTimeout(() => { handle(onRejected) });
//同样的,先执行后绑定是不可能的,只是执行了改变状态的代码,由于执行器其实是由于回调队列中是空的,就直接跳过了执行回调队列的操作,要在调用then方法绑定失败回调时时立刻异步执行
// handle(onRejected);
break;
}
});
}
/*
catch:为当前对象只绑定失败的回调函数,返回一个新的成功promise对象,并且新的promise对象的data是undefined
onrejected:失败的回调
*/
Promise.prototype.catch = function (onrejected) {
return this.then(undefined, onrejected); //直接调用该对象的then方法,将成功的回调设置成非函数值,在then方法中会转化成向下传递值的回调函数,就会返回一个新的且data值是undefined的promise对象
}
/*
resolve:返回一个状态是成功且指定data为value的promise对象
value:可以是一个promise对象,可以是一个普通值
*/
Promise.resolve = function (value) {
if (value instanceof Promise) { //若是一个promise对象
return value; //则直接返回该promise对象
} else { //若是普通值
return new Promise((resolve, reject) => { //将值包装成成功的promise对象并返回
resolve(value);
})
}
}
/*
reject:返回一个状态为失败的promise对象
reason:失败原因
*/
Promise.reject = function (reason) {
return new Promise((resolve, reject) => {
reject(reason);
});
}
/*
all:返回一个promise,只有当所有promise对象成功时才成功,否则失败
*/
Promise.all = function (promises) {
const values = new Array(promises.length); //创建一个与promises对应的数组,用来存放每个promise对象的返回值
let count = 0; //成功的promise计数器
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) { //遍历promises数组
Promise.resolve(promises[i]).then( //数组元素若是普通值使用Promise.resolve进行包装为成功的promise对象
value => {
count++; //每成功一个该计数器加一
values[i] = value; //将执行成功回调函数的value添加到values对应位置
if (count === promises.length) {
resolve(values); //当所有都成功时,则返回的就是失败的promise对象,并且data值是values数组
}
},
reason => { //只要有一个执行了失败的回调,则返回的就是失败的promise对象
reject(reason)
}
)
}
});
}
/*
race:返回一个promise,第一个完成的promise是成功的该返回的就是成功的promise,第一个完成的promise是失败的该返回的就是失败的promise
*/
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) { //遍历promises数组
Promise.resolve(promises[i]).then(resolve, reject); //数组元素若是普通值使用Promise.resolve进行包装为成功的promise对象
}
})
}
Generator函数与异步
生成器函数可以依靠yield表达式暂停函数执行,若在异步回调函数中调用生成器函数返回的迭代器的next方法,就可以保证这些异步操作串行执行
function* async() {
let t = yield setTimeout(() => {
console.log(1);
it.next("yield表达式的返回值"); //异步回调中调用当前生成器函数返回的迭代器的next方法
//甚至还可以使用带参的next方法指定yield表达式的值,来影响下一个异步任务
}, 2000);
yield setTimeout(() => { //等到上个异步任务执行时才会启动该当前异步任务
console.log(t); //输出上一个异步任务指定的yield的表达式的值
}, 1000);
}
let it = async(); //获取迭代器,该迭代器会在这个生成器函数中使用到
it.next(); //开始执行生成器函数
若所有异步任务都使用promise对象进行封装,使用迭代器的next方法获取的对象其中value值就是指定的promise对象,promise对象可以绑定多个成功或失败的回调,再为这些promise对象绑定一个调用该迭代器next的回调,就可以保证多个异步任务串行执行
function autoRun(gen) {
const g = gen(); //获取迭代器
function next(data) {
const result = g.next(data); //向下执行生成器函数,并替换yield表达式的值,从而影响下一个异步任务
if (result.done === false) { //若迭代器未结束
Promise.resolve(result.value) //若yield后不是promise对象则将他转化为成功的promise对象
.then(value => next(value)) //则为当前promise对象绑定执行迭代器next方法的函数
}
}
next();
}
autoRun(function* () {
let a = yield new Promise((resolve, reject) => {
setTimeout(() => {
console.log(1);
resolve(2);
}, 1000)
});
a = yield new Promise((resolve, reject) => {
setTimeout(() => {
console.log(a);
resolve(3);
}, 1000)
});
a = yield new Promise((resolve, reject) => {
setTimeout(() => {
console.log(a);
resolve(1);
}, 1000)
});
});
async函数
声明
- 于普通的函数类似,只不过使用
async
修饰的方法,但是不能作为构造函数那样使用 - 使用
return
语句返回的必然是一个promise对象- 返回一个普通值=成功的包装该值的promise对象
- 返回一个promise对象=该promise对象
- 抛出错误=返回一个包装该错误的promise对象
async function afun(){
// return 1; //Promise {<resolved>: 1}
// throw 1; //Promise {<rejected>: 1}
// return new Promise((resolve,reject)=>{
// // resolve(1); //Promise {<resolved>: 1}
// // reject(1); //Promise {<rejected>: 1}
// //throw 1; //Promise {<rejected>: 1}
// })
}
console.log(afun());
await表达式
- 只能在async函数中使用
- await后跟一个表达式
- 该表达式是一个promise对象,则该表达式的值是该promise对象成功的返回结果,如果该promise对象失败了就会抛出错误,需要用
try...catch
捕获错误才能得到该promise对象失败的返回结果 - 该表达式是一个普通表达式,则该表达式的值是这个普通表达式的值,即有没有await声明是一样的
- 该表达式是一个promise对象,则该表达式的值是该promise对象成功的返回结果,如果该promise对象失败了就会抛出错误,需要用
(async function () {
const t = await Promise.resolve(1);
console.log(t); //1
})();
//失败的promise
(async function () {
try {
const t = await Promise.reject(1);
console.log(t); //抛出错误,所以这里不会执行到
} catch (err) {
console.log(err); //1
}
})();
//普通表达式
(async function () {
const t = await 1 + 1;
//等价于
// const t = 1 + 1;
console.log(t); //2
})();
async函数执行流程
- 调用该函数则该函数同步执行,只有遇到
await
表达式并且右部是promise对象才会进入异步执行状态
async function afun(){
console.log(1);
}
afun();
console.log(2);
// 1
// 2
async function afun(){
await Promise.resolve();
console.log(1);
}
afun();
console.log(2);
// 2
// 1
async实现原理
其实就是生成器函数自动串行执行的升级版,多绑定了一个失败的promise对象的回调函数
function autoRun(gen) {
return new Promise((resolve, reject) => { //异步函数一定返回的是promise对象
const g = gen(); //获取迭代器对象
function next(data, status = 1) { //status状态是1说明是成功的promise,其他值则是失败的promise
let result; //保存迭代器结果对象
try {
result = status === 1 ? g.next(data) : g.throw(data); //根据status决定将yield表达式替换为什么
//向下执行生成器函数,并替换yield表达式的值,从而影响下一个异步任务
} catch (e) { //若生成器函数中抛出错误
reject(e); //则将错误捕获后,并将返回的promise对象变为失败的状态,reason是捕获的错误信息
}
if (result.done === false) { //若迭代器未结束
Promise.resolve(result.value) //若yield后不是promise对象则将他转化为成功的promise对象
.then(
(value) => next(value), //则为当前promise对象绑定成功的回调函数,其中中要执行迭代器next方法的函数
(error) => next(error, 0) //则为当前promise对象绑定失败的回调函数,其中中要执行迭代器throw方法的函数
);
} else { //若迭代器结束
resolve(result.value); //返回成功的promise对象,并且值是最后一个return语句后的值
}
}
next();
});
}
模块化
导入导出语句只能在顶级作用域下,也就是说不能在if等语句和函数中
从v13.2版本开始,Node.js才打开了ES6模块支持
export
- 声明导出:直接导出一条声明语句;此时对外的接口就是声明的变量、函数、类
export var a = 1;
var b = 1;
//export b; //报错,静态解析,未指定对外的接口 他相当于 export 1;
- 整体导出:导出以代码块逗号分隔导出变量的形式;此时对外的接口就这些变量名
var a = 1;
var b = 1;
export{a,b};
export{a} //不会报错,因为指定了对外接口
- 定义别名:可以为每个变量使用
as
定义别名;此时对外的接口就是别名
var a = 1;
var b = 1;
export {a as aa,b as bb}; //指定别名
- 默认导出:每个模块只能有一个默认导出语句,可以导出变量也可以导出声明,因为此时变量的对外接口是
default
所以可以导出一个单个变量
export default function fun(){};
//相当于
var fun = function (){};
export default fun;
//export default var a = 1; //报错,因为已经指定了对外接口是default了,所以在指定为a就有冲突
import
- 逐个导入:导入代码块逗号分隔接口的形式;此时要求导出的接口与导入的接口名完全一致
- 导入的变量就像是const声明的变量那样,不允许修改
- 导入语句会提升到文件首部
- 多次导入同一模块也只会执行一次导入操作
- 由于是静态执行的,所以导入模块必须是字符串常量,不能以变量的形式指定模块
/*-------lib.js-------*/
var a = 1;
var b = 1;
export {a as aa,b}; //指定别名
/*------main.js--------*/
import {aa,b as bbb} from "lib.js"; //此时js后缀可以省略,并且也可以指定别名
import "lib.js"; //只加载模块,不导入任何变量
- 整体导入:导入
*
必须定义别名,此时对象的属性就是所有导出的变量- 导入的对象是不允许修改的
/*-------lib.js-------*/
var a = 1;
var b = 1;
export {a as aa,b}; //指定别名
/*------main.js--------*/
import * as obj from "lib.js"; //此时js后缀可以省略,并且也可以指定别名
- 默认导入:导入的是默认导出的变量,可以随意指定变量名
/*-------lib.js-------*/
export var a = 0;
export default 1;
/*------main.js--------*/
import userName from "lib.js"; //导入默认导出的内容,并为其命名为userName
import userName,{a} from "lib.js"; //同时导入默认导出内容和非默认导出内容
浏览器模块加载
异步加载
为script标签添加defer
或async
属性
defer
:页面渲染结束后加载async
:js脚本现在完毕加载
开启浏览器js脚本模块化
为浏览器添加type="module"
属性和属性值,此时默认的异步加载方式是defer
,可以手动修改为async
CommonJS模块ES6模块的区别
- CommonJs是运行时加载,值拷贝,若模块中在加载完毕后对导出的模块变量进行修改,则引入模块的程序不会检测到
- ES6是编译时输出接口,值引用,若模块中在加载完毕后对导出的模块变量进行修改,则引入模块的程序也会检测到
Comments NOTHING