当前位置: 首页 > news >正文

ES6 全详解 let 、 const 、解构赋值、剩余运算符、函数默认参数、扩展运算符、箭头函数、新增方法,promise、Set、class等等

目录

  • ES6概念
    • ECMAScript6简介
    • ECMAScript 和 JavaScript 的关系
    • ES6 与 ECMAScript 2015 的关系
  • 1、let 、 const 、var 区别
  • 2、变量解构赋值
    • 1、数组解构赋值
    • 2、对象解构赋值
    • 3、字符串的解构赋值
  • 3、展开剩余运算符
    • 1、**展开运算符(...)**
    • 2、**剩余运算符(...)**
  • 4、函数的拓展
    • 函数默认参数
    • 箭头函数
    • 剩余参数(rest)
    • name 属性
  • 5、数组的拓展
    • 扩展运算符
    • isInteger()
    • Math.trunc()
    • Math.sign()
    • Array.from()
    • Array.of()
    • fill()
    • flat()
    • flatMap()
  • 6、字符串的拓展
    • 模版字符串
    • includes()
    • repeat()
  • 7、对象的拓展
    • 对象属性和方法的简写
    • 属性名表达式
    • 方法的 name 属性
    • Object.assign()
    • Object.is()
  • 8、Set数据结构
    • `add()`
    • `has()`
    • `size()`
    • `delete()`
    • `clear()`
    • 把集合转换为数组
  • 9、Map
    • Map方法
  • 10、Symbol()
  • 11、class类
    • class类概念
    • 取值函数(getter)和存值函数(setter)
    • 静态方法
    • 静态属性
  • 12、Promise
    • Promise是什么
    • Promise使用
    • Promise 对象的状态
    • Promise对象方法
      • `Promise.resolve()`
      • `Promise.reject()`
      • `Promise.all()`
      • `Promise.race()`
      • `Promise.allSettled()`
      • `Promise.any()`
    • 手写Promise
  • 13、async与await
    • async
    • await
    • 错误处理

ES6概念

ECMAScript6简介

​ ECMAScript 6.0,简称 ES6,是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言

ECMAScript 和 JavaScript 的关系

要讲清楚这个问题,需要回顾历史。1996 年 11 月,JavaScript 的创造者 Netscape 公司,决定将 JavaScript 提交给标准化组织 ECMA,希望这种语言能够成为国际标准。次年,ECMA 发布 262 号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript,这个版本就是 1.0 版

该标准从一开始就是针对 JavaScript 语言制定的,但是之所以不叫 JavaScript,有两个原因。一是商标,Java 是 Sun 公司的商标,根据授权协议,只有 Netscape 公司可以合法地使用 JavaScript 这个名字,且 JavaScript 本身也已经被 Netscape 公司注册为商标。二是想体现这门语言的制定者是 ECMA,不是 Netscape,这样有利于保证这门语言的开放性和中立性

因此,ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现(另外的 ECMAScript 方言还有 JScript 和 ActionScript)。日常场合,这两个词是可以互换的

ES6 与 ECMAScript 2015 的关系

2011 年,ECMAScript 5.1 版发布后,就开始制定 6.0 版了。因此,ES6 这个词的原意,就是指 JavaScript 语言的下一个版本

但是,因为这个版本引入的语法功能太多,而且制定过程当中,还有很多组织和个人不断提交新功能。事情很快就变得清楚了,不可能在一个版本里面包括所有将要引入的功能。常规的做法是先发布 6.0 版,过一段时间再发 6.1 版,然后是 6.2 版、6.3 版等等

但是,标准的制定者不想这样做。他们想让标准的升级成为常规流程:任何人在任何时候,都可以向标准委员会提交新语法的提案,然后标准委员会每个月开一次会,评估这些提案是否可以接受,需要哪些改进。如果经过多次会议以后,一个提案足够成熟了,就可以正式进入标准了。这就是说,标准的版本升级成为了一个不断滚动的流程,每个月都会有变动

标准委员会最终决定,标准在每年的 6 月份正式发布一次,作为当年的正式版本。接下来的时间,就在这个版本的基础上做改动,直到下一年的 6 月份,草案就自然变成了新一年的版本。这样一来,就不需要以前的版本号了,只要用年份标记就可以了

ES6 的第一个版本,就这样在 2015 年 6 月发布了,正式名称就是《ECMAScript 2015 标准》(简称 ES2015)。2016 年 6 月,小幅修订的《ECMAScript 2016 标准》(简称 ES2016)如期发布,这个版本可以看作是 ES6.1 版,因为两者的差异非常小(只新增了数组实例的includes方法和指数运算符),基本上是同一个标准。根据计划,2017 年 6 月发布 ES2017 标准

因此,ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等,而 ES2015 则是正式名称,特指该年发布的正式版本的语言标准。本书中提到 ES6 的地方,一般是指 ES2015 标准,但有时也是泛指“下一代 JavaScript 语言”

  • 1997年:ECMAScript 1.0

  • 1998年:ECMAScript 2.0

  • 1999年:ECMAScript 3.0

  • 2006年:ECMAScript 4.0 未通过

  • 2009年:ECMAScript 5.0

  • 2015年:ECMAScript 6.0

  • 至今,版本号改用年号的形式

1、let 、 const 、var 区别

var: es6 之前的旧语法

  1. 声明变量
  2. 可以重复声明
  3. var 有预解析,可以先赋值后声明
  4. var 没有块级作用域

let:es6 语法

  1. 声明变量
  2. 不可以重复声明
  3. let 没有预解析,必须先声明后赋值
  4. 有块级作用域
  5. 有暂时性死区

const:es6 语法

  1. 声明常量
  2. 不可以重复定义
  3. 声明后不可以赋值、更新
  4. 没有预解析
  5. 有块级作用域
  6. 有暂时性死区

说明:

  • 暂时性死区是指,在代码块内,使用letconst命令声明变量之前,该变量都是不可用的
  • 如果在声明之前使用这些变量,就会报错。因此,使用letconst定义的变量一定要在声明后再使用,否则会报错

2、变量解构赋值

1、数组解构赋值

按照位置一一对应赋值

const [name, age, obj] = ['longge', 30, { a: 10 }] 
console.log(name, age, obj.a)  // longge 30 10let [x, , y] = [1, 2, 3];
console.log(x , y)  // 1 3let [head, ...tail] = [1, 2, 3, 4];
console.log(head)  // 1
console.log(tail)  // [2, 3, 4]

结构不成功,变量的值就等于undefined

let [foo] = []
let [bar, foo] = [1]
// foo的值都是undefined

2、对象解构赋值

  1. 属性可以无序
  2. 通过属性名一 一对应,并不是按照位置顺序来对应值
  3. 可以通过旧属性名:新变量名
 const {age: newAge,uname,girlFriend: { username },} = {uname: '李新浩',age: 12,girlFriend: {age: 11,username: 'lisi',},}console.log(uname, newAge)  // 李新浩 12console.log(username)  // lisi

3、字符串的解构赋值

字符串也可以解构赋值,这是因为此时,字符串被转换成了一个类似数组的对象

const [a, b, c, d, e] = 'hello'
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值

let {length : len} = 'hello'
len // 5

3、展开剩余运算符

1、展开运算符(…)

展开运算符(Spread Operator)是一个用于在数组或对象字面量中插入表达式的语法。它允许开发者在一个数组或对象中展开另一个数组或对象

下面是几个使用展开运算符的示例:

在数组字面量中展开数组:

let arr1 = [1, 2, 3]  
let arr2 = [...arr1, 4, 5]  // [1, 2, 3, 4, 5]

在函数调用中展开数组:

function sum(a, b, c) {  return a + b + c  
}  
let arr = [1, 2, 3]  
console.log(sum(...arr))  // 6

在对象字面量中展开对象:

let obj1 = { a: 1, b: 2 }  
let obj2 = {...obj1, c: 3}  // {a: 1, b: 2, c: 3}

在模板字符串中展开对象:

let obj = { a: 1, b: 2 }  
console.log(`${obj.a} ${obj.b} ${obj.c}`)  // 1 2 undefined  
console.log(`${...obj}`)  // "1 2 undefined"

注意,展开运算符只能用于数组或对象字面量的初始化,不能在运行时动态添加元素到数组或对象。

2、剩余运算符(…)

用于赋值号左边或函数形参

是个真数组,箭头函数没有arguments,但可以使用剩余运算符接收动态的参数

示例:

 const [a, ...args] = [1, 2, 3, 4]console.log(args) // [2,3,4]
 const fn = (...args) => {console.log(args)let sum = 0args.forEach((item) => {sum += item})return sum}console.log(fn(1))  // 1console.log(fn(1, 2))  // 3console.log(fn(1, 2, 3))  // 6

4、函数的拓展

函数默认参数

ES6 函数可以有默认参数,当函数接收了参数,会覆盖默认值

function fn(a = 0, b = 0) {  // 函数的形参可以加默认参数0return a + b}console.log(fn())   // 0console.log(fn(1,1))   // 2console.log(fn.name)   // fn   (name属性可以访问函数名)

箭头函数

  1. 箭头函数没有this,它内部this由所处作用域(上下文)决定,call/apply/bind也不能改变箭头函数this
  2. 箭头函数没有arguments,普通函数有,但是可以使用剩余运算符…
  3. 箭头函数不能new(不能实例化)
  4. 箭头函数没有函数提升
 const fun = () => {console.log(this)// console.log(arguments) //  报错 箭头函数没有arguments}

当只有一条return语句,{ }和return可以一起省略,不能只省略一个

const getSum = (x, y) => {return x + y
}
const getSum = (x, y) => x + y
console.log(getSum(3, 4))

形参只有一个,小括号可以省略,其余情况全部要加()

const f2 = (x) => x * 2 // (x)=> {return x *2}
const f2 = x => x * 3

剩余参数(rest)

如果你需要在箭头函数中使用类似 arguments 的功能,可以使用剩余参数(rest)

const fn = (...args) => {  for (let arg of args) {  console.log(arg)}  
};  fn(1, 2, 3)  // 输出 1, 2, 3

name 属性

函数的name属性,返回该函数的函数名

function foo() {}
foo.name // "foo"

5、数组的拓展

  1. Array.isArray()
  2. Array.from()
  3. Array.of()
  4. Array.flat()数组扁平化

扩展运算符

let arr1 = [1,2,3]
let arr2 = [4,5,6]
// 合并数组
console.log([...arr1,...arr2])   // [1,2,3,4,5,6]

isFinite()isNaN()Number()方法

  • 减少全局性方法,使得语言逐步模块化

  • 与传统的全局方法相比,isFinite()isNaN()的区别在于,传统方法先调用Number()将非数值的值转为数值,再进行判断

  • 而这两个新方法只对数值有效,Number.isFinite()对于非数值一律返回false

  • Number.isNaN()只有对于NaN才返回true,非NaN一律返回false

let num1 = Number.isFinite(100) //true
let num4 = Number.isFinite("100") //false
let num1 = Number.isNaN(100) // false
let num2 = Number.isNaN(NaN) //true
let num3 = Number.isNaN("kerwin") //false
let num4 = Number.isNaN("100") // false

isInteger()

用来判断一个数值是否为整数

let num1 = Number.isInteger(1000) // true
let num2 = Number.isInteger(1000.0) //true
let num3 = Number.isInteger("abc") //false
let num4 = Number.isInteger("1000") // false

Math.trunc()

将小数部分抹掉,返回一个整数

console.log(Math.trunc(1.3)) //1
console.log(Math.trunc(1.9))// 1
console.log(Math.trunc(-1.5)) //-1
console.log(Math.trunc(-1.2))//-1

Math.sign()

正数返回1,负数返回-1,正0返回0,负0返回-0,非数字返回NaN

Math.sign(-100) // -1
Math.sign(100) // +1
Math.sign(0) // +0
Math.sign(-0) // -0
Math.sign("abc") // NaN

Array.from()

将类数组对象转换为真正数组

//将类数组的对象,转为真数组
let arrayLike = {'0': 'a','1': 'b','2': 'c',length: 3
};
// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

将字符串转为真数组

Array.from('hello')
// ['h', 'e', 'l', 'l', 'o']
let namesSet = new Set(['a', 'b'])
Array.from(namesSet) // ['a', 'b']

Array.of()

将一组值转化为数组,即新建数组

Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1

fill()

fill 充满

使用自己想要的参数替换原数组内容,会改变原来的数组

参数描述
value必需,填充的值。
start可选,开始填充位置。
end可选,停止填充位置 (默认为 array.length),到end为止之前结束
    let arr1 = ['a', 'b', 'c']console.log(arr1.fill(7))   // [7, 7, 7] 始末位置不写,会将所有值替换console.log(arr1)   // [7, 7, 7]   会改变原数组let arr2 = new Array(3).fill(7)   console.log(arr2)   // [7, 7, 7] 将创建的空数组中值,全部替换
let arr = ['a', 'b', 'c']
console.log(arr.fill(7,1,2))   // ['a', 7, 'c']//上面代码表示,fill方法从 1 号位开始,向原数组填充 7,到 2 号位之前结束

注意,如果填充的类型为对象,那么被赋值的是同一个内存地址的对象,而不是深拷贝对象

flat()

  • flat(depth),depth指定要提取嵌套数组的结构深度,默认值为 1
  • 按照一个可指定的深度递归遍历数组,将所有元素与遍历到的子数组中的元素合并为一个新数组返回
  • 该方法返回一个新数组,对原数据没有影响
let arr1 = [1, 2, [3, 4]]
arr1.flat()
// [1, 2, 3, 4]let arr2 = [1, 2, , 4, 5].flat()
// [1, 2, 4, 5]  如果原数组有空位,flat()方法会跳过空位let arr3 = [1, 2, [3, 4, [5, 6]]]
arr2.flat()
// [1, 2, 3, 4, [5, 6]]let arr4 = [1, 2, [3, 4, [5, 6]]]
arr3.flat(2)
// [1, 2, 3, 4, 5, 6]//使用 Infinity,可展开任意深度的嵌套数组
let arr5 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]]
arr4.flat(Infinity)
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

flatMap()

  • 它与 map() 连着深度值为 1 的 flat()几乎相同,但 flatMap() 通常在合并成一种方法的效率稍微高一些
  • flatMap() 方法返回新数组,不改变数据
  • 其中每个元素都是回调函数的结果,并且结构深度 depth 值只能为 1,只能展开一层数组
// 语法
array.flatMap(function (item[, index[, array]]{}[,thisArg])
// 当前数组成员、当前数组成员的下标(从0开始)、原数组、this指向
let obj = [{name: "A",list: ["鞍山", "安庆", "安阳"]},{name: "B",list: ["北京", "保定", "包头"]}]
console.log(obj.flatMap(item => item.list))   // ['鞍山', '安庆', '安阳', '北京', '保定', '包头']

6、字符串的拓展

新增:

  1. startsWith()
  2. endsWith()
  3. repeat()
  4. match()
  5. search()

模版字符串

  • 模板字符串(template string)是增强版的字符串,用反引号(``)标识

  • 字符串中可以出现换行符

  • 可以使用 ${xxx} 形式输出变量

let str = `helloworld
`// 当遇到字符串与变量拼接的情况使用模板字符串

includes()

判断字符串中是否存在指定字符

let myname = "hello world"console.log(myname.includes("e"))  // true
console.log(myname.startsWith("k"))  // false
console.log(myname.endsWith("w"))  // true

repeat()

repeat()方法返回一个新字符串,表示将原字符串重复n次

let str = "hello world"console.log(str.repeat(3))  // hello worldhello worldhello world
console.log(str.repeat(0))  // "" // 参数如果是小数,会向下取整
console.log(str.repeat(3.9))  // hello worldhello worldhello world
console.log(str.repeat("3"))  // hello worldhello worldhello world

7、对象的拓展

对象属性和方法的简写

   // es5定义对象let obj = {name:name,age:age,getName:function(){return this.name}}// es6定义对象可以简写let obj1={name, //当对象属性名与变量名一致时候可以这样简写age,getName(){ //定义方法时可以省略掉function关键字和冒号return this.name}}

属性名表达式

ES6 允许字面量定义对象时,用表达式作为对象的属性名,即把表达式放在方括号内

let lastWord = 'last word'
const a = {'first word': 'hello',[lastWord]: 'world'
};
a['first word']  // "hello"
a[lastWord]  // "world"
a['last word']  // "world"obj['a' + 'bc'] = 123

方法的 name 属性

函数的name属性,返回函数名。对象方法也是函数,因此也有name属性。

const person = {sayName() {console.log('hello!')},
};
person.sayName.name   // "sayName"

Object.assign()

第一个参数是目标对象,后面可以跟一个或多个源对象作为参数

Object.assign(target, object1,object2,......)
// 目标对象、后面都是源对象
const obj1 = {
name: "李四"
};
const obj2 = {
name:"张三"
};
const obj3 = {
age:18
};
Object.assign(obj1, obj2, obj3);
//obj1 {name: '张三', age: 18}

Object.is()

用来比较两个值是否严格相等,与(===)基本类似

Object.is("q","q")      // true
Object.is(1,1)          // true
Object.is([1],[1])      // false
Object.is({q:1},{q:1})  // false

与(===)的区别

//一是+0不等于-0
Object.is(+0,-0)  //false
+0 === -0  //true
//二是NaN等于本身
Object.is(NaN,NaN) //true
NaN === NaN  //false

8、Set数据结构

  • 它类似于数组,但是成员的值都是唯一的,没有重复的值
  • Set本身是一个构造函数,用来生成 Set 数据结构
  • 可利用set集合元素的不可重复性,进行数组去重
  • 可传入(数组、字符串、其他 Set 对象、等可迭代对象)

示例:

// 用于从数组中删除重复元素
const numbers = [1,1,2,2,2,3,3,3,3]
console.log([...new Set(numbers)])   // [1,2,3]const arr = [1, 2, 2, 3, 4, 4, 5]  
const set = new Set(arr) 
console.log(set)   // Set { 1, 2, 3, 4, 5 } 返回一个对象实例// 去除重复字符
const str = 'hello'
const set = new Set(str)
console.log([...set])   //  ['h', 'e', 'l', 'o']

add()

  • 如果该 Set 实例对象中没有相同值的元素,Set 实例的add()方法会在该集合中插入一个具有指定值的新元素
  • 返回添加了值的 Set 对象
// 语法
add(value)

示例:

const mySet = new Set()
mySet.add(1)
mySet.add(1)
mySet.add(5).add("some text")  // 可以链式调用
console.log(mySet)   // Set [1, 5, "some text"]

has()

Set 实例的 has() 方法返回一个布尔值来指示对应的值是否存在于该集合中

// 语法
has(value)   // value 要测试是否存在于 Set 对象中的值

示例:

const set1 = new Set([1, 2, 3, 4, 5])
console.log(set1.has(1))   //  true
console.log(set1.has(5))   //  true
console.log(set1.has(6))   //  false

size()

Set实例的 size 访问器属性将返回该集合中去除了重复元素的个数

示例:

const set1 = new Set()
const object1 = {}set1.add(42)
set1.add('forty two')
set1.add('forty two')
set1.add(object1)console.log(set1.size)  // 3  空对象也算长度

delete()

删除某个值,返回一个布尔值,表示删除是否成功

clear()

清除所有成员,没有返回值

把集合转换为数组

const set1 = new Set()
const object1 = {}set1.add(42)
set1.add('forty two')
set1.add('forty two')
set1.add(object1)
console.log(Array.from(set1))   // [42, 'forty two', {}]  Array.from()把集合转成数组 

9、Map

  • Object 对象只接受字符串作为键名,提供了“字符串—值”的对应
  • 而 Map “键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键,提供了“值—值”的对应

Map方法

Map.prototype.delete() 删除键值对
Map.prototype.get() 获取键值
Map.prototype.has() 判断是否有某个键
Map.prototype.set() 设置键值对
Map.prototype.size() 获取键值对的个数

Map.prototype.entries() 获取所有键值对
Map.prototype.values() 获取所有值
Map.prototype.keys() 获取所有键
Map.prototype.forEach() 遍历
Map.prototype.clear() 清空所有键值对

示例1:

const myMap = new Map()
myMap.set("bar", "baz")
myMap.set(1, "foo")console.log(myMap)   // Map(2) {'bar' => 'baz', 1 => 'foo'}
console.log(myMap.size)   // 2
console.log(myMap.has("bar"))   // truemyMap.clear()console.log(myMap.size)   // 0
console.log(myMap.has("bar"))   // false

示例2:

const m = new Map()
const o = {p: 'Hello World'}
m.set(o, 'content')  // 对象o作为m的键
m.get(o)   // "content"
m.has(o)   // true
m.delete(o)   // true
m.has(o)   // false

10、Symbol()

产生独一无二的值

 //  产生独一无二的值 Symbolconst s1 = Symbol('a')const s2 = Symbol('a')console.log(s1 === s2) // falseconsole.log(typeof s1) // 'symbol'

11、class类

class类概念

ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已,ES6 的类,完全可以看作构造函数的另一种写法

constructor()方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor()方法,如果没有显式定义,一个空的constructor()方法会被默认添加

// ES5生成实例对象的传统方法是通过构造函数
function Point(x, y) {this.x = xthis.y = y
}
Point.prototype.toString = function () {return '(' + this.x + ', ' + this.y + ')'
}
let p = new Point(1, 2)
// 上面的代码用 ES6 的class改写,就是下面这样
class Point {constructor(x, y) {this.x = xthis.y = y}//方法前面不需要加上function这个关键字toString() {return '(' + this.x + ', ' + this.y + ')'}
}
//使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致
let p = new Point(1, 2)

构造函数的prototype属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面

class Point {constructor() {// ...}toString() {// ...}toValue() {// ...}
}
// 等同于
/*Point.prototype = {constructor() {},toString() {},toValue() {},
}*/

取值函数(getter)和存值函数(setter)

在“类”的内部可以使用getset关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。

class MyClass {constructor() {// ...}get prop() {return 'getter';}set prop(value) {console.log('setter: '+value);}
}let inst = new MyClass();inst.prop = 123;
// setter: 123inst.prop
// 'getter'

静态方法

类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”

class Foo {static classMethod() {//如果静态方法包含this关键字,这个this指的是类,而不是实例。console.log(this)}
}Foo.classMethod() // 'hello'var foo = new Foo();
foo.classMethod()//报错

静态属性

静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性

class Foo {
}Foo.prop = 1
Foo.prop   // 1

目前,只有这种写法可行,因为 ES6 明确规定,Class 内部只有静态方法,没有静态属性

12、Promise

Promise是什么

  • Promise 是异步编程的一种解决方案,比传统的解决方案回调函数, 更合理和更强大
  • ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象
  • 指定回调函数方式更灵活易懂
  • 解决异步回调地狱的问题

什么是回调地狱:

  • 当一个回调函数嵌套一个回调函数的时候,就会出现一个嵌套结构,当嵌套的多了就会出现回调地狱的情况
  • 缺点:可读性差,外层无法捕获内层的异常,耦合严重,牵一发动全身

例如:发送三个 ajax 请求

  • 第一个正常发送
  • 第二个请求需要第一个请求的结果中的某一个值作为参数
  • 第三个请求需要第二个请求的结果中的某一个值作为参数
// 这样就出现了多层嵌套,就出现了回调地狱
ajax({url: '我是第一个请求',success (res) {// 现在发送第二个请求ajax({url: '我是第二个请求'data: { a: res.a, b: res.b },success (res2) {// 进行第三个请求ajax({url: '我是第三个请求',data: { a: res2.a, b: res2.b },success (res3) { console.log(res3) }})}})}
})

回调地狱,其实就是回调函数嵌套过多导致的

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

当代码成为这个结构以后,已经没有维护的可能了

Promise使用

语法:

new Promise(function (resolve, reject) {// resolve 表示成功的回调// reject 表示失败的回调
}).then(function (res) {// 成功的函数
}).catch(function (err) {// 失败的函数
})

Promise 对象的状态

Promise 对象通过自身的状态,来控制异步操作。Promise 实例具有三种状态。

异步操作未完成(pending)
异步操作成功(fulfilled)
异步操作失败(rejected)

这三种的状态的变化途径只有两种。

从“未完成”到“成功”
从“未完成”到“失败”

一旦状态发生变化,就凝固了,不会再有新的状态变化。这也是 Promise 这个名字的由来,它的英语意思是“承诺”,一旦承诺成效,就不得再改变了。这也意味着,Promise 实例的状态变化只可能发生一次。

因此,Promise 的最终结果只有两种。

异步操作成功,Promise 实例传回一个值(value),状态变为fulfilled。
异步操作失败,Promise 实例抛出一个错误(error),状态变为rejected。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Promise对象方法

Promise 是一个对象,也是一个构造函数

Promise.resolve()

Promise.resolve()方法将现有对象转为 Promise 对象

Promise.resolve('kerwin')
// 等价于
new Promise(resolve => resolve('kerwin'))

Promise.reject()

Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected

const p = Promise.reject('error');
// 等同于
const p = new Promise((resolve, reject) => reject('error'))

Promise.all()

Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.all([p1, p2, p3]);

p的状态由p1,p2,p3 决定,分成两种情况。

(1)只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

Promise.race()

Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.race([p1, p2, p3]);

上面代码中,只要p1p2p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

Promise.allSettled()

Promise.allSettled()方法,用来确定一组异步操作是否都结束了(不管成功或失败)。所以,它的名字叫做”Settled“,包含了”fulfilled“和”rejected“两种情况。

const promises = [ ajax('/200接口'), ajax('/401接口') ];Promise.allSettled(promises).then(results=>{// 过滤出成功的请求results.filter(item =>item.status === 'fulfilled');过滤出失败的请求results.filter(item=> item.status === 'rejected');
})

Promise.any()

只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。

Promise.any()Promise.race()方法很像,只有一点不同,就是Promise.any()不会因为某个 Promise 变成rejected状态而结束,必须等到所有参数 Promise 变成rejected状态才会结束。

手写Promise

/** @作者: kerwin*/
function KerwinPromise(executor) {this.status = "pending";this.result = undefined;this.cb = []var _this = this;function resolve(res) {if (_this.status !== "pending") return;// console.log(_this)_this.status = "fulfilled"_this.result = res;_this.cb.forEach(item => {item.successCB && item.successCB(_this.result)});}function reject(res) {if (_this.status !== "pending") return;// console.log("reject")_this.status = "rejected"_this.result = res;_this.cb.forEach(item => {item.failCB && item.failCB(_this.result)});}executor(resolve, reject)
}KerwinPromise.prototype.then = function (successCB, failCB) {if(!successCB){successCB = value=>value}if(!failCB){failCB = error=>error}// successCB()return new KerwinPromise((resolve, reject) => {if (this.status === "fulfilled") {var result = successCB && successCB(this.result)// console.log(result);if (result instanceof KerwinPromise) {result.then(res => {// console.log(res)resolve(res);}, err => {// console.log(err)reject(err)})} else {resolve(result);}}if (this.status === "rejected") {var result = failCB && failCB(this.result)if (result instanceof KerwinPromise) {result.then(res => {// console.log(res)resolve(res);}, err => {// console.log(err)reject(err)})} else {reject(result);}}if (this.status === "pending") {//收集回调this.cb.push({successCB: () => {var result = successCB && successCB(this.result)if (result instanceof KerwinPromise) {result.then(res => {// console.log(res)resolve(res);}, err => {// console.log(err)reject(err)})} else {resolve(result);}},failCB: () => {var result = failCB && failCB(this.result)if (result instanceof KerwinPromise) {result.then(res => {// console.log(res)resolve(res);}, err => {// console.log(err)reject(err)})} else {reject(result);}}})}})
}KerwinPromise.prototype.catch= function(failCB){this.then(undefined,failCB)
}

13、async与await

async

async 函数,是个语法糖,使得异步操作变得更加方便

  • 更好的语义
  • 返回值是 Promise
async function test(){}
test()

await

await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。

async function test(){var res1 =  await ajax("http://localhost:3000/news1")var res2 =  await ajax("http://localhost:3000/news2")return res2
}
test().then(res=>{console.log("返回结果",res)
}).catch(err=>{console.log("err",err)
})

错误处理

try{var res1 =  await ajax("http://localhost:3000/news1")var res2 =  await ajax("http://localhost:3000/news2")
}catch(err){console.log("err",err)
}

相关文章:

ES6 全详解 let 、 const 、解构赋值、剩余运算符、函数默认参数、扩展运算符、箭头函数、新增方法,promise、Set、class等等

目录 ES6概念ECMAScript6简介ECMAScript 和 JavaScript 的关系ES6 与 ECMAScript 2015 的关系 1、let 、 const 、var 区别2、变量解构赋值1、数组解构赋值2、对象解构赋值3、字符串的解构赋值 3、展开剩余运算符1、**展开运算符(...)**2、**剩余运算符(...)** 4、函数的拓展函…...

c++ - 类的默认成员函数

文章目录 前言一、构造函数二、析构函数三、拷贝构造函数四、重载赋值操作符五、取地址及const取地址操作符重载 前言 默认成员函数是编译器自动生成的,也可以自己重写,自己重写之后编译器就不再生成,下面是深入了解这些成员函数。 一、构造…...

Java哈希查找(含面试大厂题和源码)

哈希查找(Hash Search)是一种基于哈希表(Hash Table)的数据查找方法。哈希表通过使用哈希函数将键(Key)映射到表中的位置来存储数据,从而实现快速的数据访问。哈希查找的效率通常取决于哈希函数…...

c++中常用库函数

大小写转换 islower/isupper函数 char ch1 A; char ch2 b;//使用islower函数判断字符是否为小写字母 if(islower(ch1)){cout << ch1 << "is a lowercase letter." << end1; } else{cout << ch1 << "is not a lowercase lette…...

Scrapy框架 进阶

Scrapy框架基础Scrapy框架进阶 【五】持久化存储 命令行&#xff1a;json、csv等管道&#xff1a;什么数据类型都可以 【1】命令行简单存储 &#xff08;1&#xff09;语法 Json格式 scrapy crawl 自定义爬虫程序文件名 -o 文件名.jsonCSV格式 scrapy crawl 自定义爬虫程…...

ubuntu22安装snipaste

Ubuntu 22.04 一、Snipaste 介绍和下载 Snipaste 官网下载链接: Snipaste Downloads 二、安装并使用 Snipaste # 1、进入Snipaste-2.8.9-Beta-x86_64.AppImage 目录&#xff08;根据自己下载目录&#xff09; cd /home/jack/Downloads/softwares/AppImage# 2、Snipaste-2.8.9-…...

spring-cloud微服务openfeign

Spring Cloud openfeign对Feign进行了增强&#xff0c;使其支持Spring MVC注解&#xff0c;另外还整合了Ribbon和Nacos&#xff0c;从而使得Feign的使用更加方便 优势&#xff0c;openfeign可以做到使用HTTP请求远程服务时就像洞用本地方法一样的体验&#xff0c;开发者完全感…...

小程序变更主体需要多久?

小程序迁移变更主体有什么作用&#xff1f;小程序迁移变更主体的好处有很多哦&#xff01;比如可以获得更多权限功能、公司变更或注销时可以保证账号的正常使用、收购账号后可以改变归属权或使用权等等。小程序迁移变更主体的条件有哪些&#xff1f;1、新主体必须是企业主体&am…...

19 Games101 - 笔记 - 相机与透镜

**19 ** 相机与透镜 目录 摘要一 照相机主要部分二 小孔成像与视场(FOV)三 曝光(Exposure)四 景深(Depth of Field)总结 摘要 虽说照相机与透镜属于相对独立的话题&#xff0c;但它们的确是计算机图形学当中的一部分知识。在过往的十多篇笔记中&#xff0c;我们学习的都是如…...

Flink入门学习 | 大数据技术

⭐简单说两句⭐ ✨ 正在努力的小新~ &#x1f496; 超级爱分享&#xff0c;分享各种有趣干货&#xff01; &#x1f469;‍&#x1f4bb; 提供&#xff1a;模拟面试 | 简历诊断 | 独家简历模板 &#x1f308; 感谢关注&#xff0c;关注了你就是我的超级粉丝啦&#xff01; &…...

Arthas实战教程:定位Java应用CPU过高与线程死锁

引言 在Java应用开发中&#xff0c;我们可能会遇到CPU占用过高和线程死锁的问题。本文将介绍如何使用Arthas工具快速定位这些问题。 准备工作 首先&#xff0c;我们创建一个简单的Java应用&#xff0c;模拟CPU过高和线程死锁的情况。在这个示例中&#xff0c;我们将编写一个…...

HTML制作跳动的心形网页

作为一名码农 也有自己浪漫的小心思嗷~ 该网页 代码整体难度不大 操作性较强 祝大家都幸福hhhhh 效果成品&#xff1a; 全部代码&#xff1a; <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML><HEAD><TITLE> 一个…...

如何在Odoo 17 销售应用中使用产品目录添加产品

Odoo&#xff0c;作为一个知名的开源ERP系统&#xff0c;发布了其第17版&#xff0c;新增了多项功能和特性。Odoo 17包中的一些操作简化了&#xff0c;生产力提高了&#xff0c;用户体验也有了显著改善。为了为其用户提供新的和改进的功能&#xff0c;Odoo不断进行改进和增加新…...

为什么pdf拆分出几页之后大小几乎没有变化

PDF 文件的大小在拆分出几页之后几乎没有变化可能有几个原因&#xff1a; 图像压缩: 如果 PDF 文件中包含图像&#xff0c;而这些图像已经被压缩过&#xff0c;拆分后的页面依然会保留这些压缩设置&#xff0c;因此文件大小可能不会显著变化。 文本和矢量图形: PDF 文件中的文…...

如何在 VM 虚拟机中安装 OpenEuler 操作系统保姆级教程(附链接)

一、VMware Workstation 虚拟机 若没有安装虚拟机的可以参考下篇文章进行安装&#xff1a; 博客链接https://eclecticism.blog.csdn.net/article/details/135713915 二、OpenEuler 镜像 点击链接前往官网 官网 选择第一个即可 三、安装 OpenEuler 打开虚拟机安装 Ctrl …...

(六)PostgreSQL的组织结构(3)-默认角色和schema

PostgreSQL的组织结构(3)-默认角色和schema 基础信息 OS版本&#xff1a;Red Hat Enterprise Linux Server release 7.9 (Maipo) DB版本&#xff1a;16.2 pg软件目录&#xff1a;/home/pg16/soft pg数据目录&#xff1a;/home/pg16/data 端口&#xff1a;57771 默认角色 Post…...

DockerFile定制镜像

dockerfile 简介 Dockerfile 是⼀个⽤来构建镜像的⽂本⽂件&#xff0c;⽂本内容包含了⼀条条构建镜像所需的指令和 说明&#xff0c;每条指令构建⼀层&#xff0c;最终构建出⼀个新的镜像。 docker镜像的本质是⼀个分层的⽂件系统 centos的iso镜像⽂件是包含bootfs和rootfs…...

Java8中JUC包同步工具类深度解析(Semaphore,CountDownLatch,CyclicBarrier,Phaser)

个人主页&#xff1a; 进朱者赤 阿里非典型程序员一枚 &#xff0c;记录平平无奇程序员在大厂的打怪升级之路。 一起学习Java、大数据、数据结构算法&#xff08;公众号同名&#xff09; 引言 在Java中&#xff0c;并发编程一直是一个重要的领域&#xff0c;而JDK 8中的java.u…...

岛屿个数(dfs)

[第十四届蓝桥杯省B 岛屿个数] 小蓝得到了一副大小为 M N MN MN 的格子地图&#xff0c;可以将其视作一个只包含字符 0 0 0&#xff08;代表海水&#xff09;和 1 1 1&#xff08;代表陆地&#xff09;的二维数组&#xff0c;地图之外可以视作全部是海水&#xff0c;每个岛…...

【C++造神计划】运算符

1 赋值运算符 赋值运算符的功能是将一个值赋给一个变量 int a 5; // 将整数 5 赋给变量 a 运算符左边的部分叫作 lvalue&#xff08;left value&#xff09;&#xff0c;右边的部分叫作 rvalue&#xff08;right value&#xff09; 左边 lvalue 必须是一个变量 右边 rval…...

Cortex-M3/M4处理器的bit-band(位带)技术

ARM Cortex-M3/M4的位带&#xff08;Bit-Band&#xff09;技术是一种内存映射技术&#xff0c;它允许对单个位进行直接操作&#xff0c;而不需要对整个字&#xff08;通常是32位&#xff09;进行操作。这项技术主要用于对特定的位进行高效的读写&#xff0c;特别是在需要对GPIO…...

【TOP】IEEE旗下1区,影响因子将破8,3个月录用,CCF推荐,性价比高!

计算机类 ● 好刊解读 IEEE出版社、中科院2区TOP&#xff0c;CCF推荐&#xff0c;今天推荐的期刊可谓buff叠满&#xff0c;好刊质量靠谱&#xff0c;有意向评职晋升毕业作者可重点关注&#xff1a; 01 期刊简介 ✅出版社&#xff1a;IEEE ✅影响因子&#xff1a;7.5-8.0 ✅…...

赚钱游戏 2.0.1 版 (资源免费)

没有c编辑器的可以直接获取资源来玩 #include <iostream> #include <string> #include <windows.h> #include <conio.h> #include <fstream> #include <ctime> #include <time.h> #include <stdio.h> #include <cstring&g…...

服务调用-微服务小白入门(4)

背景 各个服务应用&#xff0c;有很多restful api&#xff0c;不论是用哪种方式发布&#xff0c;部署&#xff0c;注册&#xff0c;发现&#xff0c;有很多场景需要各个微服务之间进行服务的调用&#xff0c;大多时候返回的json格式响应数据多&#xff0c;如果是前端直接调用倒…...

代码随想录算法训练营第三十六天| 435. 无重叠区间、 763.划分字母区间、56. 合并区间

435 题目&#xff1a; 给定一个区间的集合 intervals &#xff0c;其中 intervals[i] [starti, endi] 。返回 需要移除区间的最小数量&#xff0c;使剩余区间互不重叠 。 题目链接&#xff1a;435. 无重叠区间 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; …...

【AIGC调研系列】rerank3是什么

Rerank 3是一个针对企业搜索和检索辅助生成&#xff08;RAG&#xff09;系统优化的新型基础模型&#xff0c;它支持多语种、多结构数据搜索&#xff0c;并提供高精度的语义重排。通过这种方式&#xff0c;Rerank 3能够大幅提升响应准确度和降低延迟&#xff0c;同时大幅降低成本…...

Linux下网络编程基础知识--协议

网络基础 这一个课程的笔记 相关文章 协议 Socket编程 高并发服务器实现 线程池 协议 一组规则, 数据传输和数据的解释的规则。 比如说依次发送文件的文件名, 文件的大小, 以及实际的文件, 这样规定发送一个文件的顺序以及发送的每一个部分的格式等可以算是一种协议 型协议 …...

在 VS Code 中使用 GitHub Copilot

Code 结合使用。 GitHub Copilot 是什么 GitHub Copilot 是一个可以帮助你更简单、更快速地编写代码的工具&#xff0c;由 GPT-3 提供支持。你只需编写所需代码的描述——例如&#xff0c;编写一个函数来生成一个随机数&#xff0c;或对一个数组进行排序——Copilot 就会为你…...

使用spring-ai快速对接ChatGpt

什么是spring-ai Spring AI 是一个与 Spring 生态系统紧密集成的项目&#xff0c;旨在简化在基于 Spring 的应用程序中使用人工智能&#xff08;AI&#xff09;技术的过程。 简化集成&#xff1a;Spring AI 为开发者提供了方便的工具和接口&#xff0c;使得在 Spring 应用中集…...

免费的 ChatGPT 网站(六个)

&#x1f525;博客主页&#xff1a; 小羊失眠啦. &#x1f3a5;系列专栏&#xff1a;《C语言》 《数据结构》 《C》 《Linux》 《Cpolar》 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 文章目录 一、insCode二、讯飞星火三、豆包四、文心一言五、通义千问六、360智脑 现在智能…...

如何搭建情侣网站/百度知道网址

最近在学习中遇到了protobuf&#xff0c;哇喔竟然不知道&#xff0c;马上进行了学习&#xff0c;protobuf也是数据解析的方式&#xff0c;平时使用最多的是json和xml&#xff0c;那么好了&#xff0c;对比下他们的区别&#xff0c;并且附上protobuf的使用。 数据交互xml、json、…...

做宣传语的网站/12月10日新闻

NAT 提供了许多优点和好处。但是&#xff0c;使用 NAT 也有一些缺点&#xff0c;包括不支持某些类型的流量。使用 NAT 的优点包括&#xff1a;NAT 允许对内部网实行私有编址&#xff0c;从而维护合法注册的公有编址方案。NAT 通过应用程序端口级别的多路复用节省了地址。利用 N…...

企业网站建设怎么选择空间/百度推广天天打骚扰电话

接入AliPay后&#xff0c;编译报错&#xff1a;Undefined symbols for architecture arm64: "_OBJC_CLASS_$_类名", referenced from 前不久刚在一个项目里接入过支付宝支付&#xff0c;就是把AliPaySDK.bundle和AliPaySDK.framework两个导入工程&#xff0c;然后再…...

文字域名可以做网站/浙江网络推广

安装完成后不能编译&#xff0c;提示&#xff1a;**Error**: You must have glib installed.解决sudo apt-get install libglib2.0-dev pkg-config转载于:https://www.cnblogs.com/sdq928/archive/2010/09/13/1825335.html...

wordpress 多域名绑定域名/公司做网站一般多少钱

对开发者来说&#xff0c;最让人头疼的问题之一莫过于读写文件的时候&#xff0c;由于编码千差万别&#xff0c;出现乱码问题。这时候我们可以使用chardet包来检测文件类型&#xff0c;然后再根据类型来decode&#xff0c;下面看举个例子&#xff1a; 案例展示&#xff1a; #…...

临沂网站推广/进入百度搜索首页

有一个很多人都明白的现象&#xff1a;只要不是底层&#xff0c;通常男人想脱单会比女生容易一点&#xff0c;尤其是条件尚可的男性&#xff0c;相比于和自己层次相当的女生&#xff0c;恋爱和结婚的难度都低得多。为什么&#xff1f;有个标准答案&#xff1a;因为男人可以向下…...