ECMAScript 和 JavaScript 的关系:ECMAScript是一种实现的规范,对其约束和定义,而JavaScript正是这套规范的实现。所以ES的新特性就是JavaScript的新特性。 详细可参考:https://es6.ruanyifeng.com/ ## let 和 const命令 ### let let是用来生成变量的,类似于我们之前学习的
var
。区别是let
所声明的变量,只在let
命令所在的代码块内有效。例如:
//示例一: 代码块
{
let a = 10;
var b = 1;
}
a // 报错:ReferenceError: a is not defined.
b // 1,var可以正常获取
//示例二: 循环外使用
for (let i = 0; i < 10; i++) {
// ...
}
console.log(i);
// ReferenceError: i is not defined
const
声明一个不可变的常量,一旦生命后续不可修改否则报错。所以我们声明时就必须要给初始值,否则将没有任何意义。
const PI = 3.1415;
PI // 3.1415
PI = 3;
// 修改PI报错——TypeError: Assignment to constant variable.
//不给初始值报错
const xc;
// SyntaxError: Missing initializer in const declaration
const
的作用域与let
命令相同:只在声明所在的块级作用域内有效。
:::info const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
:::
let&const不存在变量提升
var
命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined
。这种现象多多少少是有些奇怪的,按照一般的逻辑,变量应该在声明语句之后才可以使用。
let
命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。
const
命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
// var 的情况
console.log(foo); // 输出undefined
var foo = 2;
// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;
// const 的情况
if (true) {
console.log(MAX); // ReferenceError
const MAX = 5;
}
let&const不允许重复声明
const声明的常量,和let声明的变量在相同作用域内,不允许重复声明同一个变量。
//案例一
// 报错
function func() {
let a = 10;
var a = 1;
}
// 报错
function func() {
let a = 10;
let a = 1;
}
//案例二
function func(arg) {
let arg;
}
func() // 报错
function func(arg) {
{
let arg;
}
}
func() // 不报错
//案例三
{
var message = "Hello!";
let age = 21;
// 以下两行都会报错
const message = "Goodbye!";
const age = 15;
}
总结
let
- let作用——声明变量,变量且不能重复声明。
- 块级作用域,声明的变量只在当前块级(
{}
)下生效 - 不能变量提升,在使用let声明变量之前,一定要先声明变量
- 不影响作用域链,如下
{
let xc = '小陈';
function fn (){
console.log(xc);
}
fn()//小陈
}
const
- const作用——声明常量,一定要赋初始值
- 一般常量使用大写(这不是语法要求,是一个潜规则,用于区分变量)
- 常量的值不能修改
- 块级作用域
- 对于数组或对象的修改,不算对常量的修改,不会报错。因为他们指向的是一个地址,只要地址不变,可以任意操作对象或数组
块级作用域
ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。为啥需要块级作用域?
常见不使用块级作用域的两种缺陷场景。
- 第一种场景,内层变量可能会覆盖外层变量。
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}
f(); // undefined
上面运行逻辑是,if
代码块的外部使用外层的tmp
变量,内部使用内层的tmp
变量。但是,函数f
执行后,输出结果为undefined
,原因在于变量提升,导致内层的tmp
变量覆盖了外层的tmp
变量。
- 第二种场景,用来计数的循环变量泄露为全局变量。
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5 ,正常来说循环外我们应该访问不到的
上面代码中,变量i
只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。
ES6块级作用域
- 解决内层变量可能会覆盖外层变量
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
f1()
上面的函数有两个代码块,都声明了变量n
,运行后输出 5
。这表示外层代码块不受内层代码块的影响。如果两次都使用var
定义变量n
,最后输出的值才是 10
。
- 任意作用域的嵌套
{
{
{
{
{ let insane = 'Hello World' }
console.log(insane); // 报错
}
}
}
};
上方报错是因为不在同一个块级作用域上,第四层级的作用域无法读取第五层级的作用域
- 内层作用域可以定义外层作用域的同名变量
{
{
{
{
{ let insane = 'Hello World' }
let insane = 'Hello World'
}
}
}
};
块级作用域——函数生命使用
ES6之前是不支持在块级作用域声明的,只能在顶层作用域和函数作用域之中声明。函数生命语句行为类似<font style="color:rgb(74, 74, 74);">let</font>
的使用,在块级作用域之外不可引用。
// 块级作用域内部的函数声明语句,建议不要使用
{
let a = 'secret';
function f() {
return a;
}
}
// 块级作用域内部,优先使用函数表达式
{
let a = 'secret';
let f = function () {
return a;
};
}
ES6 的块级作用域必须有大括号,如果没有大括号,JavaScript 引擎就认为不存在块级作用域。函数声明也是如此,严格模式下,函数只能声明在当前作用域的顶层。
变量的解构赋值
按照一定模式从数组或对象中提取值,对变量赋值一些列操作被称为解构赋值。
//数组解构
const NAME = ['小陈','小周','小张'];
let [chen,zhou,zhang] = NAME;
console.log(chen,zhou,zhang)
//对象解构
const xiaoZhang = {
name: '小张',
age: '21',
zhiye : function(){
console.log('摄影师')
}
}
let {name,age,zhiye} = xiaoZhang;
zhiye()
通常方法结构我们用的比较多,类似于zheye()这种。如果一个方法频繁调用我们可以使用方法结构
模板字符串
ES6之前生命字符串的方式有两种第一种单引号【’’】、第二种双引号【””】,ES6之后引入了模板字符串【``】
区别:
- 字符串内容中可以直接出现换行符。其他两种不可以,编译报错
- 变量拼接
let xc = `小陈,超级超级超级
帅!!!`
let name = `小陈`;
let xiangxi = `${name},迷死我了`
console.log(xiangxi) //小陈,迷死我了
简化开发
- ES6允许在大括号里面,直接写入变量和函数,作为对象的属性和方法
let book = '西游记';
let readBook = function(){
console.log('读西游记');
}
const arr = {
book,
readBook
}
console.log(arr)
//ES6之前写法
const arr1 = {
book: book,
readBook: readBook
}
console.log(arr1)
箭头函数 【=>
】
=>作用:用来简化函数的定义
//箭头函数
let fn = function(){
console.log('普通声明明方式')
}
//省略了function关键字
let fn1 = () =>{
console.log('箭头声明方式')
}
fn()
fn1()
特点:
- this的静态特性,this始终指向函数声明时所在作用域下的this的值
// 箭头函数的额this是静态的,this始终指向函数声明时所在作用域下的this的值
function A() {
console.log(this.name)
}
//而这个箭头函数是在全局作用域下声明的,所以this也是指向window
let B = () => {
console.log(this.name);
}
window.name = 'this的静态特性';
const school = {
name: 'xiaochen'
}
//直接调用
A() //this的静态特性
B() //this的静态特性
//call
A.call(school); //xiaochen
B.call(school); //this的静态特性
- 不能作为构造函数实例对象
- 不能使用 arguments 变量
- 箭头函数的缩写
- 省略小括号,当形参有且只有一个的时候
let add = n =>{
return n+1;
}
console.log(add(2))
2. <font style="color:rgb(77, 77, 77);">省略花括号,当代码体只有一条语句的时候,此时</font><font style="color:rgb(0, 0, 0);background-color:rgb(248, 248, 64);">return</font><font style="color:rgb(77, 77, 77);">也必须省略</font>
let add = n => n+1;
console.log(add(2))
注意事项:
- 箭头函数适合与this无关的回调,比如定时器,数组的方法回调。
- 箭头函数不适合与this有关的回调,比如DOM元素的事件回调、对象的方法。
btn.addEventListener("click",function(){
//此时普通函数的this指向事件缘
//如果使用箭头函数,事件源将变成外部作用域的this值,即这个函数所在的作用域
})
函数参数默认值
可以给形参赋初始值,一般位置要靠后(潜规则)
function number(a,b,c=3) {
console.log(a,b,c)
}
number(1) //1 undefined 3
也可以与解构赋值中运用
//函数参数初始值与结构赋值结合使用
function data({name,age='22'}){
console.log(name);
console.log(age);
}
data({
name='zzl',
// age='23'
})
rest参数
rest参数:用于获取函数的实参,可以代替arguments。
function data(...args) {
console.log(args); //输出的是一个数组,可以使用filter some...
}
data('xc', 'xz', 'xj', 'xl');
data('xc')
注意:rest参数必须放在参数最后
function data(a,b,...args) {
console.log(a);
console.log(b);
console.log(args);
}
data('x','c','xc','xz','xl')
扩展运算符
扩展运算符是能将数组转换为逗号分隔的参数序列const names = ['xc', 'xz', 'xl'];
function data() {
console.log(arguments);
}
//不用扩展运算符
data(names);//只输出一个结果,是一个数组
// 用扩展运算符
data(...names);//输出3个结果,等价于:data('xc', 'xz', 'xl'),即参数序列
常见应用:
// 数组的合并
const A = ['aa', 'bb'];
const B = ['cc', 'dd'];
const C = [...A, ...B];
console.log(C) //[aa,bb,cc,dd]
// 数组的克隆
const A = ['a', 'b', 'c'];
const B = [...A];
console.log(B) //[a,b,c]
// 将伪数组转换为真正的数组
const A = documents.querySelectorAll('div');
const B = [...A];
console.log(B) // [div,div,div]
Symbol
symbol是Es6中新增的数据类型。
作用:
- 防止属性名发生冲突,表示一个独一无二的值。
注意点:
- Symbol值不能与其他数据进行运算
- Symbol定义的对象属性不能使用for…in循环遍历,但是可以使用Reflect.ownKeys来获取对象的所有键名
//1. 创建symbol类型值
let x = Symbol('xc');
let c = Symbol('xc');
console.log(typeof x)//查看Symbol声明出的值,是什么类型
//2. 判断Symbol声明两个名称一样的值是否相等
console.log(x == c);
//3. symbol声明的值不能进行计算,下面会报错
let result = c + x;
symbol添加描述:Symbol()函数创建 Symbol 值时,可以用参数添加一个描述。
let c = Symbol('xc');
console.log(c.description); //xc
迭代器
任何数据结构只要部署了Iterator
接口,就可以使用for…of
来遍历.
//for in保存的是键名,for of保存的是键值
const xiyou = ['AA', 'BB', 'CC', 'DD'];
for (let v of xiyou) {
console.log(v) // 'AA','BB','CC','DD'
}
for (let v in xiyou) {
console.log(v);
}
let iterator = xiyou[Symbol.iterator]();
console.log(iterator.next());
迭代过程如下:
- 通过 Symbol.iterator 创建一个迭代器,指向当前数据结构的起始位置;
- 随后通过 next 方法进行向下迭代指向下一个位置,next 方法会返回当前位置的对象,对象包含了 value 和 done 两个属性,value 是当前属性的值,done 用于判断是否遍历结束;
- 当 done 为 true 时则遍历结束。
const xiyou = ['AA', 'BB', 'CC', 'DD'];
let iterator = xiyou[Symbol.iterator]();
//{value: 'AA', done: false}
console.log(iterator.next());
//{value: 'BB', done: false}
console.log(iterator.next());
//{value: 'CC', done: false}
console.log(iterator.next());
//{value: 'DD', done: false}
console.log(iterator.next());
//{value: undefined, done: true}
console.log(iterator.next());
创建一个数组,然后通过 Symbol.iterator
方法创建一个迭代器,之后不断地调用 next 方法对数组内部项进行访问,当属性 done 为 true 时访问结束。
Promise
promise三种状态
- pending(进行中)
- fulfilled(已成功)
- rejected(已失败)
基本用法
//异步请求
const promise = new Promise(function (resolve, reject) {
// ... some code
if (/* 异步操作成功 */) {
resolve(value);
} else {
reject(error);
}
});
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数
- resolve作用是:将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去
- reject作用是:将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
promise.then(function(value) {
// success
}, function(error) {
// failure
});
then
作用是为 Promise 实例添加状态改变时的回调函数。then方法的第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的。then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
// ...
});
catch
Promise.prototype.catch()
方法是.then(null, rejection)
或.then(undefined, rejection)
的别名,用于指定发生错误时的回调函数。
const promise = new Promise(function(resolve, reject) {
throw new Error('test');
});
promise.catch(function(error) {
console.log(error);
});
// Error: test
then中也可以实现同等效果。相比不要在then()方法里面定义 Reject 状态的回调函数(即then的第二个参数),推荐使用catch方法捕获处理。
这种方式跟类似于写法(try/catch),不管catch前存在多少<font style="color:rgb(13, 20, 30);">then</font>
他都会进行捕获。
// 不推荐
promise.then(function(data) {
// success
}, function(err) {
// error
});
// 推荐
promise.then(function(data) { //cb
// success
})
.catch(function(err) {
// error
});
finally
finally()
方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。
ES6提供了新的数据结构set(集合)。它类似于数组,但成员的值都是唯一的,集合实现了iterator接口,所以可以使用「扩展运算符』和「 for…of…』进行遍历,集合的属性和方法:
promise
.finally(() => {
// 语句
});
// 等同于
promise
.then(
result => {
// 语句
return result;
},
error => {
// 语句
throw error;
}
);
如果不使用finally
方法,同样的语句需要为成功和失败两种情况各写一次。有了finally
方法,则只需要写一次。
Set和Map数据结构
Set
Set集合是ES6新增的数据结构。类似于数组,但成员的值都是唯一的。set集合实现了iterator接口,所以可以使用「扩展运算符』或「 for…of…』进行遍历集合的属性和方法。基本使用
//生命Set集合
let s = new Set();
let s2 = new Set(['A', 'B', 'C', 'D'])
//元素个数
console.log(s2.size);
//添加新的元素
s2.add('E');
//删除元素
s2.delete('A')
//检测
console.log(s2.has('C'));
//清空
s2.clear()
实际应用
let arr = [1, 2, 3, 4, 5, 4, 3, 2, 1]
//1.数组去重
let result = [...new Set(arr)]
console.log(result);
//2.交集
let arr2 = [4, 5, 6, 5, 6]
let result2 = [...new Set(arr)].filter(item => new Set(arr2).has(item))
console.log(result2);
//3.并集
let result3 = [new Set([...arr, ...arr2])]
console.log(result3);
//4.差集
let result4 = [...new Set(arr)].filter(item => !(new Set(arr2).has(item)))
console.log(result4);
Map
Map数据结构。类似于对象,也是键值对的集合。但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map也实现了iterator接口,所以可以使用『扩展运算符』和「for…of…』进行遍历let m = new Map();
m.set('name', 'xc');
m.set('change', () => {
console.log('好酷!')
})
let key = {
school: '清华大学'
}
m.set(key, ['北京', '西安']);
//size
console.log(m.size);
//删除
m.delete('name');
//获取
console.log(m.get('change'));
// //清空
// m.clear()
//遍历
for (let v of m) {
console.log(v);
}
Map 结构原生提供三个遍历器生成函数和一个遍历方法。
- Map.prototype.keys():返回键名的遍历器。
- Map.prototype.values():返回键值的遍历器。
- Map.prototype.entries():返回所有成员的遍历器。
- Map.prototype.forEach():遍历 Map 的所有成员。
const map = new Map([
['F', 'no'],
['T', 'yes'],
]);
for (let key of map.keys()) {
console.log(key);
}
// "F"
// "T"
for (let value of map.values()) {
console.log(value);
}
// "no"
// "yes"
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
// 等同于使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
如果我们想把Map转换为数组,最简单的办法就是使用扩展运算符
const map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
[...map.keys()]
// [1, 2, 3]
[...map.values()]
// ['one', 'two', 'three']
[...map.entries()]
// [[1,'one'], [2, 'two'], [3, 'three']]
[...map]
// [[1,'one'], [2, 'two'], [3, 'three']]
Class
ES6后新引入了Class(类)这个概念,作为对象的模板。通过class
关键字,可以定义类。
定义class
class students{
constructor(name,age){
this.name = name;
this.age = age;
}
//定义一个方法。不需要加上function这个关键字,直接把函数定义放进去了就可以
toString(){
return '(' + this.name + ', ' + this.age + ')';
}
}
let student = new students('喜洋洋','9')
console.log(student.toString()) //(喜洋洋, 9)
constructor()
<font style="color:rgb(13, 20, 30);">constructor()</font>
方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor()方法,如果没有显式定义,一个空的constructor()方法会被默认添加。
//显示生命构造
class students{
constructor(name,age){
this.name = name;
this.age = age;
}
}
//隐式生命构造,使用默认的
class students{
}
↑等同于↓
class students{
constructor(){}
}
类的实例
定义好类,怎么调用呢,就要实例化对象才能使用。实例对象的写法使用new
关键字完成如下:
class students{
//……
}
//实例化对象
let student = new students()
实例属性的新写法
ES2022 为类的实例属性,又规定了一种新写法。实例属性现在除了可以定义在constructor()方法里面的this上面,也可以定义在类内部的最顶层。
// 原来的写法
class IncreasingCounter {
constructor() {
this._count = 0;
}
get value() {
return this._count;
}
increment() {
this._count++;
}
}
现在的新写法是,这个属性也可以定义在类的最顶层,其他都不变。
class IncreasingCounter {
_count = 0;
get value() {
return this._count;
}
increment() {
this._count++;
}
}
注意,新写法定义的属性是实例对象自身的属性,而不是定义在实例对象的原型上面。好处是,所有实例对象自身的属性都定义在类的头部,看上去比较整齐,一眼就能看出这个类有哪些实例属性。
取值函数(getter)和存值函数(setter)
在“类”的内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为class students{
constructor(name,age){
this.name = name;
this.age = age;
}
get name(){
return 'name';
}
set name(value){
console.log('set'+value);
}
}
let student = new students('喜洋洋','9')
console.log('*'+student.name)
静态方法
静态方法需要在方法前加上static
关键字 ,可以直接通过类
的方式去调用。静态方法不会创建的实例对象继承,也就是说我们创建的实例对象无法调用。
class students{
static testStatic(){
console.log('测试静态方法');
}
}
let student = new students()
students.testStatic() //测试静态方法
student.testStatic() //student.testStatic is not a function
注意,如果静态方法包含this关键字,这个this指的是类,而不是实例。同一个类中,静态方法名和非静态方法名可以重复。
私有化属性
ES2022正式为class添加了私有属性,方法是在属性名之前使用#表示。
class IncreasingCounter {
#count = 0;
get value() {
console.log('Getting the current value!');
return this.#count;
}
increment() {
this.#count++;
}
}
const counter = new IncreasingCounter();
counter.#count // 报错
counter.#count = 42 // 报错
Class继承
class
通过extends
关键字实现继承,让子类继承父类的属性和方法。
class person{
introduce(){
console.log("测试继承效果");
}
}
class students extends person{
}
let student = new students()
student.introduce()//测试继承效果
子类继承父类,如果显示生命构造函数(不使用默认的),子类必须在constructor()方法中调用super(),否则就会报错。子类没有定义constructor()方法,这个方法会默认生成
class person {
introduce() {
console.log("测试继承效果");
}
}
class students extends person {
constructor() { }
}
let student = new students()
student.introduce() //Must call super constructor in derived class before accessing 'this' or returning from derived constructor
这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,添加子类自己的实例属性和方法。如果不调用super()方法,子类就得不到自己的this对象。
ES6 的继承机制,则是先将父类的属性和方法,加到一个空的对象上面,然后再将该对象作为子类的实例,即“继承在前,实例在后”。这就是为什么 ES6 的继承必须先调用super()方法,因为这一步会生成一个继承父类的this对象,没有这一步就无法继承父类。 注意,这意味着新建子类实例时,父类的构造函数必定会先运行一次。class person {
constructor() {console.log(1); }
}
class students extends person {
constructor() {
super();
console.log(2);
}
}
let student = new students()
//1
//2
私有属性和私有方法的继承
子类可以继承父类除私有属性和方法外的所有属性和方法。私有属性和方法只能在当前类中使用。
class Foo {
#p = 1;
#m() {
console.log('hello');
}
}
class Bar extends Foo {
constructor() {
super();
console.log(this.#p); // 报错
this.#m(); // 报错
}
}
模块化
模块化:将一个大的程序文件,拆分成许多小的文件,最终将小文件组合打包起来运行。好处如下:
- 防止命名冲突
- 代码复用
- 高维护性
ES6后通过export
命令显式指定输出的代码,再通过import
命令输入。
import { stat, exists, readFile } from 'fs';//从fs模块,加载三个方法
模块开发主要通过export和import实现
- export命令用于规定模块的对外接口
- import命令用于输入其他模块提供的功能
export
export关键字可以输出该变量、方法
export var firstName = 'Michael';
export function getName(x,y){
return x+y;
}
//统一不暴露接口
var firstName = 'Michael';
function getName(x, y) {
return x + y;
}
export { firstName, getName };
//默认暴露
export default {
firstName:'清华大学',
getName:function(){
console.log('加油!!!')
}
}
import
import命令可以加载export的模块,使用总结//1. 通用导入方式
import * as m1 from "./main.js"
import * as m2 from "./main.js"
//2. 解构赋值方式
import {school,teach} from "./main.js"
import {school as guigu,findJob} from "./main.js"
import {default as m3 } from "./main.js"
//3. 简便形式(只针对默认暴露)
import m3 from "./main.js"
export default
export default命令,为模块指定默认输出。
// export-default.js
export default function () {
console.log('foo');
}
//----------------------------------
//其他模块加载该模块时,import命令可以为该匿名函数指定任意名字。
// import-default.js
import customName from './export-default';
customName(); // 'foo'
上面代码的import命令,可以用任意名称指向export-default.js输出的方法,这时就不需要知道原模块输出的函数名。需要注意的是,这时import命令后面,不使用大括号。
正常输出和默认输出对比
// 第一组
export default function crc32() { // 输出
// ...
}
import crc32 from 'crc32'; // 输入
// 第二组
export function crc32() { // 输出
// ...
};
import {crc32} from 'crc32'; // 输入
数组扩展
Array.prototype.includes
:用来检测数组中是否包含某个元素,返回布尔类型值
let arr = ['清华','北大','哈弗','耶鲁'];
console.log(arr.includes('清华'));//true
console.log(arr.includes('浙大'));//false
async&await函数
async
async
函数返回一个 Promise
对象。async
函数内部return
语句返回的值,会成为then
方法回调函数的参数。
async function fn() {
return 'hello async';
}
fn().then(v => console.log(v));
fn方法内返回的值会被then方法接收到。
async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
await timeout(ms);
return value;
}
asyncPrint('hello world', 3000).then(v => console.log(v));
await
正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。
async function f() {
// 等同于
// return 123;
return await 123;
}
f().then(v => console.log(v))
// 123
- await必须放在async函数中
- await右侧的表达式一般为promise对象
- await可以返回的是右侧promise成功的值
- await右侧的promise如果失败了,就会抛出异常,需要通过try…catch捕获处理
开发使用
/ajax请求返回一个promise
function sendAjax(url) {
return new Promise((resolve, reject) => {
//创建对象
const x = new XMLHttpRequest();
//初始化
x.open('GET', url,true);
//发送
x.send();
//时间绑定
x.onreadystatechange = () => {
if (x.readyState === 4) {
if (x.status >= 200 && x.status < 300) {
//成功
resolve(x.response)
} else {
//失败
reject(x.status)
}
}
}
})
}
//async 与 await 测试
async function main() {
let result = await sendAjax("https://api.apiopen.top/api/getTime")
console.log(result);
}
main()