文章目录
一、ES6+
let关键字
基本用法
- 变量未声明不能使用,否则报的错误就是变量未定义
- 在ES6中默认是启动了严格模式的,严格模式的特征就是:变量未声明不能使用,否则报的错误就是变量未定义。
- 在ES5中怎样开启严格模式,在代码的最开始加上:“use strict”
- 通过let声明的变量仅在块级作用域内有效
let命令注意事项
- 不存在变量提升(变量不会挂到windows上
- 暂时性死区
- 如果在区域中存在let命令,那么在这个区域中通过let命令所声明的变量从一开始就生成了一个封闭的作用域,只要在声明变量前使用,就会出错。
- 所谓的“暂时性死区”指的就是,在代码块内,使用let命令声明变量之前,该变量都是不可用的。
- 不允许重复声明
- 形成块级作用域
块级作用域
有一段代码是用大括号包裹起来的,那么大括号里面就是一个块级作用域
- 为什么需要块级作用域?
- 第一:内层变量可能会覆盖外层变量
- 第二: 用来计数的循环变量成为了全局变量
const命令
通过const命令声明的常量,其值是不允许被修改的。
const命令注意事项
- 不存在常量提升
- 只在声明的块级作用域内有效
- 暂时性死区
- 不允许重复声明
- 常量声明必须赋值
解构赋值
数组解构赋值
let arr = [1, 2, 3];
let [num1, num2, num3] = arr;
console.log(num1, num2, num3);
注意事项
- 如果解析不成功,对应的值会为undefined.
- 不完全解构的情况
对象解构赋值
先找到同名属性,然后再赋值给对应的变量。
let obj = {
userName: 'ls',
userAge: 21
};
let {
userName: name,
userAge: age
} = obj;
console.log(name, age)
对象解构赋值注意事项
- 默认解构
- 嵌套结构对象的解构
字符串的解构赋值
字符串也可以进行解构赋值,这是因为字符串被转换成了一个类似于数组的对象。
let [a, b, c, d, e, f] = 'wangcai';
console.log(a, b, c, d, e, f);
let {
length: len
} = 'wangcai';
console.log('len=', len);
函数参数的解构赋值
function test([x, y]) {
return x + y;
}
console.log(test([3, 6]));
解构好处
- 交换变量的值
let num1 = 3;
let num2 = 6;
[num1, num2] = [num2, num1];
console.log(num1, num2);
- 提取JSON对象中的数据
let userData = {
id: 12,
userName: 'wangcai',
userAge: 20
}
let {
id,
userName,
userAge
} = userData;
console.log(id, userName, userAge);
und都在哪些地方出现过?
1)声明的变量,没有赋值
2)访问一个对象中不存在的属性
3)函数没有返回值,默认也是返回und
4)形参没有赋值,也是und
5)访问数组中不存在的索引,对应的元素也是und
6)解析不成功的,得到的也是und
扩展运算符与 rest 运算符
扩展运算符的表现形式是三个点(…), 可以将一个数组转换为用逗号分隔的序列。
展开运算符
应用
- 求数组中的最大值
let arr = [12, 23, 11, 56];
console.log(Math.max.apply(null, arr));
let arr = [12, 23, 11, 56];
console.log(Math.max(...arr));
- 用于函数调用
function test(num1, num2) {
return num1 + num2;
}
let array = [23, 56];
console.log(test(...array));
rest 运算符
- 函数剩余参数
function add(...values) {
console.log(values);
}
add(2, 3);
- 解构剩余参数
let arr = [1, 2, 3, 4, 5, 6];
let [arr1, ...arr2] = arr; //进行解构处理
console.log(arr1); // 1
console.log(arr2); // [2,3,4,5,6]
- rest参数之后不能再有其他的参数
展开运算符和rest区别
- 第一:当3个点(…)出现在函数的形参上或者出现在赋值号的左侧,则表示的就是 rest 运算符
- 第二:当3个点(…)出现在函数的实参上或者出现在赋值号的右侧,则表示它为扩展运算符。
箭头函数
箭头函数注意事项
- 箭头函数直接返回一个对象,用括号
- 箭头函数中this,找出定义箭头函数的上下文(即包含箭头函数最近的函数或者是对象),那么上下文所处的父上下文即为this.
箭头函数不适合的场景
- 不能作为构造函数,不能使用
new
操作符 - 没有
prototype
属性 - 不适合将原型函数定义成箭头函数
- 绑定事件也不建议使用,无法获取this
对象的扩展
简洁表示方式
- 对象中的属性名和变量名一样
let userName = 'wangcai';
let userAge = 18;
let person = {
userName,
userAge
}
console.log(person);
- 方法简写
let userName = 'wangcai';
let userAge = 18;
let person = {
userName,
userAge,
sayHello() {
console.log('Hello');
}
}
person.sayHello();
Object.assign( )方法
Object.assign( ) 方法用来源对象的所有可枚举的属性复制到目标对象。该方法至少需要两个对象作为参数,第一个参数是目标对象,后面的参数都是源对象。只要有一个参数不是对象,就会抛出异常。
let target = {
a: 1,
b: 2
};
let source = {
c: 3,
d: 4
};
Object.assign(target, source);
console.log(target);
深浅拷贝
通过 Object.assign( ) 方法,实现的拷贝只拷贝了属性的值,属于浅拷贝
简单的模拟深拷贝
function clone(source) {
let newObj = {};
for (let key in source) {
// 由于address属性为对象,所以执行递归。
if (typeof source[key] === 'object') {
newObj[key] = clone(source[key]);
} else {
// 如果是name属性直接赋值
newObj[key] = source[key];
}
}
return newObj;
}
自己写的深拷贝
function deepCopy(obj, o) {
if (obj == null) return o;
o = o ? o : new obj.constructor
// o = new o.constructor;
for (let k in obj) {
if (obj[k] instanceof Function) {//判断函数类型,在判断对象之前判断,因为一切皆对象
o[k] = obj[k]
} else if (obj[k] instanceof Array) {//判断数组类型,在判断对象之前判断
o[k] = deepCopy(obj[k], [])
} else if (obj[k] instanceof Object) {//判断对象类型
o[k] = deepCopy(obj[k], {})
} else {
o[k] = obj[k]
}
}
return o
}
let obj = {
name: 'zs',
info: { num: 5, age: 18 },
other: [{ q: 5 }, [1, 5]],
fn: function () {
console.log(111);
}
}
let newobj = deepCopy(obj)//使用方法1 返回新数组
// let newobj = {}
// deepCopy(obj, newobj)//使用方法2 参数一拷贝到参数二
console.log(obj);
console.log(newobj);
let arr = [[[1, 5, 8], 5, [1, 5], [{ w: 8 }], { a: 5 }]]
let newarr = []
deepCopy(arr, newarr)
console.log(arr);
console.log(newarr);
function deepCopy(obj) {
if (obj == null) return o;
let o = new obj.constructor
// o = new o.constructor;
for (let k in obj) {
if (obj[k] instanceof Object) {//判断对象类型
o[k] = deepCopy(obj[k])
} else {
o[k] = obj[k]
}
}
return o
}
let obj = {
name: 'zs',
info: { num: 5, age: 18 },
other: [{ q: 5 }, [1, 5]],
fn: function () {
console.log(111);
}
}
let newobj = deepCopy(obj)//使用方法
console.log(obj);
console.log(newobj);
注意事项
- 如果目标对象与源对象有同名属性,那么后面的属性会覆盖前面的属性。
let target = {
a: 1,
b: 2
};
let source = {
b: 3,
d: 4
};
Object.assign(target, source);
console.log(target);
- 不可枚举的属性不会被复制。
let obj = {};
Object.defineProperty(obj, 'b', {
enumerable: false,//不可枚举
value: 'world'
})
let obj1 = {
a: 'hello'
}
Object.assign(obj1, obj);
console.log('obj1=', obj1);
Symbol
- Symbol是一种数据类型
- Symbol类型的值是通过Symbol函数生成的。它的值是独一无二的,也就是唯一的,可以保证对象中属性名称的唯一。
let s = Symbol();
console.log(typeof s);//symbol
标记
let s = Symbol('s');
let s1 = Symbol('s1');
console.log(s);
console.log(s1);
let s = Symbol('s');
let s1 = Symbol('s');
console.log(s === s1);//false.
应用
作为属性名的Symbol
第一种添加属性的方式:
let mySymbol = Symbol();
let obj = {}
// 第一种添加属性的方式
obj[mySymbol] = 'hello';
console.log(obj[mySymbol]);
第二种添加属性的方式:
let mySymbol = Symbol();
let obj = {
[mySymbol]: 'world' // 注意mySymbol必须加上方括号,否则为字符串而不是Symbol类型。
}
console.log(obj[mySymbol]);
第三种添加属性的方式
let mySymbol = Symbol();
let obj = {};
Object.defineProperty(obj, mySymbol, {
value: '你好'
})
console.log(obj[mySymbol]);
防止属性名称冲突
let obj = {
name: 'zs',
age: 18
}
let mySymbol = Symbol('lib1');
function test1(obj) {
obj[mySymbol] = 42;
}
let mySymbol2 = Symbol('lib2');
function test2(obj) {
obj[mySymbol2] = 369;
}
test1(obj);
test2(obj);
console.log(obj);
Proxy
**Objext.keys(obj)**得到obj对象的key,是个数组
<script>
let obj = {
name: "wc",
age: 18,
adress: "bj",
}
Object.keys(obj).forEach(key => {
let value = obj[key];
Object.defineProperty(obj, key, {
get: function() {
console.log(`监听到了obj对象的${key}属性被访问了`);
return value;
},
set: function() {
console.log(`监听到了obj对象的${key}属性被设置了`);
}
})
})
console.log(obj.name);
obj.name = "wc666";
console.log(obj.adress);
</script>
- 上面监听属性的变化不足
- Object.defineProperty刚开始设计初衷,并不是用来监听对象中的属性
- 如果对象非常复杂,需要递归去监听,一旦递归,性能非常差
- 有些操作监听不了,如添加属性,删除属性…
使用Proxy监听对对象中属性的操作
<script>
let obj = {
name: "wc",
age: 18
}
// Proxy是ES6中的一个类
// objProxy 是上面obj的代理对象
// obj 叫原始对象
// {} handler 处理对象
let objProxy = new Proxy(obj, {
// key 表示你访问的属性名
// target 表示原始对象
get: function(target, key) {
console.log(`监听到了obj对象的${key}属性被访问了`, target);
// .....
return target[key]
},
set: function(target, key, newValue) {
console.log(`监听到了obj对象的${key}属性被设置了`, target);
target[key] = newValue;
}
})
console.log(objProxy.name);
objProxy.name = "wc666"
console.log(objProxy.name);
</script>
当new Proxy时,第1个参数是原始对象,第2个参数是处理对象,处理对象中放捕获器,上面的的get和set其实就是捕获器,proxy中有13的捕获器:
<script>
let obj = {
name: "wc",
age: 18
}
// 代理对象就可以监听到你对对象中属性的操作
let objProxy = new Proxy(obj, {
// 获取值时的捕获器
get: function(target, key) {
console.log(`监听到了obj对象的${key}属性被访问了`, target);
return target[key]
},
// 设置值时的捕获器
set: function(target, key, newValue) {
console.log(`监听到了obj对象的${key}属性被设置了`, target);
target[key] = newValue;
},
// 监听in的捕获器
has: function(target, key) {
console.log(`监听到了obj对象的${key}属性in操作`, target);
return key in target;
},
// 监听delete的捕获器
deleteProperty: function(target, key) {
console.log(`监听到了obj对象的${key}属性delete操作`, target);
delete target[key]
}
})
// 判断name是否是objProxy的属性
console.log("name" in objProxy);
delete objProxy.name;
console.log(objProxy.name);
</script>
注意:要使Proxy起作用,必须针对Proxy对象进行操作,不是针对目标对象进行操作(上面的是student对象)。
Set和Map结构
- 在ES6之前,我们存储数据的结构主要有两种:数组、对象。在ES6中新增了另外两种数据结构:Set、Map,以及它们的另外形式WeakSet、WeakMap。
Set
- Set结构与数组类似,但是成员的值都是唯一的,没有重复值。创建Set我们需要通过Set构造函数(暂时没有字面量创建的方式), 我们可以发现Set中存放的元素是不会重复的,那么Set有一个非常常用的功能就是给数组去重
常用的操作方法
- add(value) : 添加某个值,返回Set结构本身。
- delete(value) : 删除某个值,返回一个布尔值,表示删除是否成功
- has(value) : 返回一个布尔值,表示参数是否为Set的成员.
- clear() : 清除所有成员,没有返回值
- 遍历 : 另外Set是支持for of的遍历的
- size属性,返回的是Set结构中的成员总数
- Set结构中的成员是不允许出现重复值的(数组去重)
// 清除数组中的重复数据.
// Set函数可以接受一个数组或者是类似数组的对象,作为参数。
let array = [1, 2, 3, 3, 5, 6];
let s = new Set(array);
console.log(Array.from(s));
WeakSet使用
和Set类似的另外一个数据结构称之为WeakSet,也是内部元素不能重复的数据结构。
和Set区别
- 区别一:WeakSet中只能存放对象类型,不能存放基本数据类型;
- 区别二:WeakSet对元素的引用是弱引用,如果没有其他引用对某个对象进行引用,那么GC可以对该对象进行回收;
- 区别三:WeakSet不能遍历,因为WeakSet只是对对象的弱引用,如果我们遍历获取到其中的元素,那么有可能造成对象不能正常的销毁,所以存储到WeakSet中的对象是没办法获取的;
WeakSet常见的方法:
- add(value):添加某个元素,返回WeakSet对象本身
- delete(value):从WeakSet中删除和这个值相等的元素,返回boolean类型
- has(value):判断WeakSet中是否存在某个元素,返回boolean类型
// 1.Weak Reference(弱引用)和Strong Reference(强引用)
let obj1 = {
name: "wc"
}
let obj2 = {
name: "xq"
}
let obj3 = {
name: "z3"
}
// let arr = [obj1, obj2, obj3]
// obj1 = null
// obj2 = null
// obj3 = null
// const set = new Set(arr)
// arr = null
// 2.WeakSet的用法
// 2.1.和Set的区别一: 只能存放对象类型
const weakSet = new WeakSet()
weakSet.add(obj1)
weakSet.add(obj2)
weakSet.add(obj3)
// 2.2.和Set的区别二: 对对象的引用都是弱引用
// 3.WeakSet的应用
const pWeakSet = new WeakSet()
class Person {
constructor() {
pWeakSet.add(this)
}
running() {
if (!pWeakSet.has(this)) {
console.log("Type error: 调用的方式不对")
return
}
console.log("running~")
}
}
let p = new Person()
// p = null
p.running()
const runFn = p.running
runFn()
const obj = {
run: runFn
}
obj.run()
Map
用于存储映射关系
和对象区别
- 对象存储映射关系只能用字符串(ES6新增了Symbol)作为属性名(key)
- 某些情况下我们可能希望通过其他类型作为key,比如对象,这个时候会自动将对象转成字符串来作为key,那么我们就可以使用Map
Map的常用属性和方法
- 常见的属性之size:返回Map中元素的个数;
- 常见的方法之set(key, value):在Map中添加key、value,并且返回整个Map对象
- 常见的方法之get(key):根据key获取Map中的value;
- 常见的方法之has(key):判断是否包括某一个key,返回Boolean类型;
- 常见的方法之delete(key):根据key删除一个键值对,返回Boolean类型
- 常见的方法之clear():清空所有的元素;
- 常见的方法之forEach(callback, [, thisArg]):通过forEach遍历Map;
- Map也可以通过for of进行遍历。
const info = {
name: "wc"
}
const info2 = {
age: 18
}
// 1.对象类型的局限性: 不可以使用复杂类型作为key
// const obj = {
// address: "bj",
// [info]: "haha",
// [info2]: "hehe"
// }
// console.log(obj)
// 2.Map映射类型
const map = new Map()
map.set(info, "wc")
map.set(info2, "xq")
console.log(map)
// 3.Map的常见属性和方法
// console.log(map.size)
// 3.1. set方法, 设置内容
map.set(info, "z3")
console.log(map)
// 3.2. get方法, 获取内容
// console.log(map.get(info))
// 3.3. delete方法, 删除内容
// map.delete(info)
// console.log(map)
// 3.4. has方法, 判断内容
// console.log(map.has(info2))
// 3.5. clear方法, 清空内容
// map.clear()
// console.log(map)
// 3.6. forEach方法
// map.forEach(item => console.log(item))
// 4.for...of遍历
for (const item of map) {
const [key, value] = item
console.log(key, value)
}
WeakMap的使用
和Map类型的另外一个数据结构称之为WeakMap,也是以键值对的形式存在的。
和Map有什么区别:
- 区别一:WeakMap的key只能使用对象,不接受其他的类型作为key;
- 区别二:WeakMap的key对对象的引用是弱引用,如果没有其他引用引用这个对象,那么GC可以回收该对象
- 区别三:WeakMap也是不能遍历的,没有forEach方法,也不支持通过for of的方式进行遍历;
WeakMap常见的方法有四个:
- set(key, value):在Map中添加key、value,并且返回整个Map对象;
- get(key):根据key获取Map中的value;
- has(key):判断是否包括某一个key,返回Boolean类型;
- delete(key):根据key删除一个键值对,返回Boolean类型;
let obj1 = {
name: "wc"
}
let obj2 = {
name: "xq"
}
// 1.WeakMap的基本使用
const weakMap = new WeakMap()
// weakMap.set(123, "aaa")
weakMap.set(obj1, "aaa")
weakMap.set(obj2, "bbb")
obj1 = null
obj2 = null
用weakMap解决循环引用问题
循环引用,a引用b,b引用a,
深copy的循环引用,obj里面的一个属性不断复制obj自身,会造成栈满爆栈
function deepCopy(obj, weakmap = new WeakMap()) {
if (obj == null) return o;
let o = new obj.constructor
if (weakmap.get(obj)) {//当已存在obj就不再复制
return weakmap.get(obj)
}
weakmap.set(obj, o)//用WeakMap的key保存原对象的引用记录, value是对应的深拷贝对象的引用
// o = new o.constructor;
for (let k in obj) {
if (obj[k] instanceof Object) {//判断对象类型
o[k] = deepCopy(obj[k], weakmap)
} else {
o[k] = obj[k]
}
}
return o
}
let obj = {
name: 'zs',
info: { num: 5, age: 18 },
other: [{ q: 5 }, [1, 5]],
fn: function () {
console.log(111);
}
}
obj.qwe = obj
let newobj = deepCopy(obj)
console.log(obj);
console.log(newobj);
class类
定义一个类
<script>
// Person是类名
class Person {
}
let p = new Person();
console.log(p); // 对象
</script>
<script>
// Person是类名
// 类表达式
let Person = class {
}
let p = new Person();
console.log(p); // 对象
</script>
通过类创建出来的对象
<script>
function Student() {}
console.log(typeof Student); // function
class Person {
}
let p = new Person();
// 通过class创建的类,也有prototype
console.log(Person.prototype);
console.log(p.__proto__);
console.log(Person.prototype == p.__proto__);
console.log(typeof Person);
</script>
通过class创建的对象,赋值私有属性和公有属性
<script>
class Person {
// 一个类只有一个constructor
// 1)内部创建一个对象 nm = {}
// 2)将Person的原型prototype赋值给创建出来的对象 nm.__proto__ = Person.prototype
// 3)将对象赋值给this 将this指向对象 new绑定 this = mn;
// 4)执行constructor中的代码
// 5)返回创建出来的对象 return nm
constructor(name, age) {
this.name = name;
this.age = age;
}
}
// p的私有属性:name age
let p = new Person("wc", 18);
console.log(p.hasOwnProperty("name"));
console.log(p.hasOwnProperty("age"));
</script>
私有属性(ES13#)
class Person{
// 实例属性
height = 188;
// 之前:不希望外面访问 潜规则
_address = "bj"
// ES13对象属性,可以以#打头,表示私有
#money = "1个亿"
// 静态属性,也叫类属性
static total = "100件"
// 静态属性,也叫类属性, 私有
static #sum = "1000万"
getMoney(){
return this.#money;
}
getSum(){
return Person.#sum;
}
}
let p = new Person();
console.log(p.height);
console.log(p._address);
// console.log(p.#money);
console.log(p.getMoney());
console.log(Person.total);
// console.log(Person.#sum);
console.log(p.getSum());
对象中的方法或公有属性
<script>
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
// running是公有属性还是私有属性
running() {
console.log(this.name + " running...");
}
}
let p = new Person("wc", 18);
p.running();
console.dir(p)
</script>
类中的设置器和访问器
<script>
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
// _address 表示不建议在类的外面访问
// 当时就可以使用get 和 set
this._address = "bj"
}
get address() {
console.log("getter调用了~");
// getter返回什么,address属性就是什么
return this._address;
}
set address(value) {
this._address = value;
}
}
let p = new Person("wc", 18);
console.log(p.address); // 自动调用上面的get address(){}
p.address = "gz" // 自动调用上面的set address(){}
console.log(p.address);
</script>
静态属性
<script>
// 一切都是对象
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
running() {
console.log(this.name + " running...");
}
// 静态属性 只能通过类名来访问
static eating() {
console.log("eating...");
}
}
Person.eating();
let p = new Person("wc", 10);
p.eating();
</script>
继承
<script>
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
running() {
console.log(this.name + " running...");
}
static eating() {
console.log("eating...");
}
}
// extends表示继承 Student类继承了Person类
class Student extends Person {
constructor(name, age, sno) {
// 由于Student继承了Person,当new Student时,还需要走Person的constructor
// super(); // super表示调用Persion的constructor
// this.name = name; // name表示Student的私有属性
// this.age = age; // age表示Student的私有属性
// this.sno = sno; // sno表示Student的私有属性
super(name, age);
this.sno = sno;
}
}
let stu = new Student("wc", 18, 110);
console.log(stu.name);
console.log(stu.age);
stu.running();
Student.eating(); // 静态属性也可以继承到
</script>
父类有的公有属性,子类是可以重写
继承,JS中内置的类
<script>
// 自己实现的类,去继承JS中的内置的类
class MyArray extends Array {
// 你的push,覆盖了Array中的push
// 重写
push() {
console.log("....");
}
}
let marr = new MyArray();
marr.push(1)
marr.push(2)
console.log(marr);
</script>
静态代码块
class Person {
// 静态代码块,在加载这个类的时候,就执行了
static {
console.log(this);
console.log("hello es6+");
console.log("hello es6+");
}
}
es6+
ES7
ES7-Array Includes
判断一个数组中是否包含一个指定的元素
let names = ["wc", "xq", "z3"];
if (names.includes("wc")) {
console.log("包含wc")
}
console.log(names.indexOf(NaN)); // -1
console.log(names.includes(NaN)); // true
ES7-指数exponentiation运算符
const res = Math.pow(2, 2);
const res2 = 3 ** 3;
console.log(res, res2)
ES8
ES8-Object values 和 Object entries
const obj = {
name: "wc",
age: 18,
height: 1.88,
address: "bj"
}
// 1.获取所有的key
const keys = Object.keys(obj)
console.log(keys)
// 2.ES8 Object.values
const values = Object.values(obj)
console.log(values)
// 3.ES8 Object.entries 键值对象
// 3.1. 对对象操作
const entries = Object.entries(obj)
console.log(entries)
for (const entry of entries) {
const [key, value] = entry
console.log(key, value)
}
// 3.2. 对数组/字符串操作
console.log(Object.entries(["wc", "xq"]))
console.log(Object.entries("Hello"))
ES8-String Padding
某些字符串我们需要对其进行前后的填充,来实现某种格式化效果,ES8中增加了 padStart 和 padEnd 方法,分别是对字符串的首尾进行填充的。应用场景:比如需要对身份证、银行卡的前面位数进行隐藏:
// padStart和padEnd
// 1.应用场景一: 对时间进行格式化
// const minute = "15".padStart(2, "0")
// const second = "6".padStart(2, "0")
// console.log(`${minute}:${second}`)
// 2.应用场景二: 对一些敏感数据格式化
let cardNumber = "410883199898764665"
const sliceNumber = cardNumber.slice(0, -4)
cardNumber = sliceNumber.padStart(cardNumber.length, "*")
console.log(cardNumber)
ES8-Trailing Commas
在ES8中,我们允许在函数定义和调用时多加一个逗号
function foo(num1, num2, ) {
console.log(num1, num2)
}
foo(10, 20, )
ES8-Object Descriptors
ES8-Async Function
ES9
- ES9-iterator迭代器
- ES9-Object spread operators
- ES9-Promise finally
ES10
ES10-flat flatMap
- flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回
- flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组
- 注意一:flatMap是先进行map操作,再做flat的操作;
- 注意二:flatMap中的flat相当于深度为1;
// flat的使用: 将一个数组, 按照制定的深度遍历, 将遍历到的元素和子数组中的元素组成一个新的数组, 进行返回
const nums = [10, 20,
[111, 222],
[333, 444],
[
[123, 321],
[231, 312]
]
]
const newNums1 = nums.flat(1)
console.log(newNums1)
const newNums2 = nums.flat(2)
console.log(newNums2)
// 2.flatMap的使用:对数组中每一个元素应用一次传入的map对应的函数
const messages = [
"wc",
"xq",
"z3"
]
// 1.for循环的方式:
// const newInfos = []
// for (const item of messages) {
// const infos = item.split(" ")
// for (const info of infos) {
// newInfos.push(info)
// }
// }
// console.log(newInfos)
// 2.先进行map, 再进行flat操作
// const newMessages = messages.map(item => item.split(" "))
// const finalMessages = newMessages.flat(1)
// console.log(finalMessages)
// 3.flatMap
const finalMessages = messages.flatMap(item => item.split(" "))
console.log(finalMessages)
ES10-Object fromEntries
entries转换成对象
// 1.对象
const obj = {
name: "wc",
age: 18,
height: 1.88
}
const entries = Object.entries(obj)
const info = Object.fromEntries(entries)
console.log(info)
应用
//查询字符串
let queryString = "?name=wc&age=18&height=1.88";
let params = new URLSearchParams(queryString);
console.log(params.get("name"));
console.log(params.get("age"));
console.log(params.get("height"));
// console.log(params.entries());
for(let item of params.entries()){
console.log(item);
}
let obj = Object.fromEntries(params.entries());
console.log(obj);
ES10-trimStart trimEnd
去除一个字符串首尾的空格,我们可以通过trim方法
ES10中给我们提供了trimStart和trimEnd
const message = " Hello World "
console.log(message.trim())
console.log(message.trimStart())
console.log(message.trimEnd())
ES11
ES11-BigInt
MAX_SAFE_INTEGER的数值,表示的可能是不正确
let maxInt = Number.MAX_SAFE_INTEGER;
console.log(maxInt);
console.log(maxInt + 1);
console.log(maxInt + 2);
那么ES11中,引入了新的数据类型BigInt,用于表示大的整数, BitInt的表示方法是在数值的后面加上n
let bigInt = 9007199254740992n;
console.log(bigInt + 1n);
console.log(bigInt + 2n);
ES11-Nullish Coalescing Operator
ES11,Nullish Coalescing Operator增加了空值合并操作符
let info = undefined
// info = info || "默认值"//info ||= "默认值"
// console.log(info)
// ??: 空值合并运算符
info = info ?? "默认值"
console.log(info)
ES11-Optional Chaining
可选链也是ES11中新增一个特性,主要作用是让我们的代码在进行null和undefined判断时更加清晰和简洁:
const obj = {
name: "wc",
friend: {
name: "xq",
// running: function() {
// console.log("running~")
// }
}
}
// 1.直接调用: 非常危险
// obj.friend.running()
// 2.if判断: 麻烦/不够简洁
// if (obj.friend && obj.friend.running) {
// obj.friend.running()
// }
// 3.可选链的用法: ?.
obj?.friend?.running?.()
ES11-Global This
之前我们希望获取JavaScript环境的全局对象,不同的环境获取的方式是不一样的
- 在浏览器中可以通过this、window来获取;
- 在Node中我们需要通过global来获取;
在ES11中对获取全局对象进行了统一的规范:globalThis
console.log(globalThis)
ES11-for…in标准化
在ES11之前,虽然很多浏览器支持for…in来遍历对象类型,但是并没有被ECMA标准化,在ES11中,对其进行了标准化,for…in是用于遍历对象的key的。
let obj = {
name: "wc",
age: 18,
height: 1.88
}
for (let key in obj) {
console.log(key)
}
ES12
ES12-logical assignment operators
// 赋值运算符
// const foo = "xq"
let counter = 100
counter = counter + 100
counter += 50
// 逻辑赋值运算符
function foo(message) {
// 1.||逻辑赋值运算符
// message = message || "默认值"
// message ||= "默认值"
// 2.??逻辑赋值运算符
// message = message ?? "默认值"
message ?? = "默认值"
console.log(message)
}
foo("wc")
foo()
// 3.&&逻辑赋值运算符
let obj = {
name: "wc",
running: function() {
console.log("running~")
}
}
// 3.1.&&一般的应用场景
// obj && obj.running && obj.running()
// obj = obj && obj.name
obj && = obj.name
console.log(obj)
ES13
ES13-method .at()
Array.prototype.at()
at() 方法接收一个整数值并返回该索引的项目,允许正数和负数。负整数从数组中的最后一个项目开始倒数。
ES13-Object.hasOwn(obj, propKey)
- Object中新增了一个静态方法(类方法): hasOwn(obj, propKey), 方法用于判断一个对象中是否有某个自己的属性;
- 和之前Object.prototype.hasOwnProperty区别
- 区别一:防止对象内部有重写hasOwnProperty
- 区别二:对于隐式原型指向null的对象, hasOwnProperty无法进行判断
const obj = {
name: "wc",
age: 18,
// 防止对象中也有一个自己的hasOwnProperty方法
hasOwnProperty: function() {
return "ok"
},
__proto__: {
address: "bj"
}
}
console.log(obj.name, obj.age)
console.log(obj.address)
console.log(obj.hasOwnProperty("name"))
console.log(obj.hasOwnProperty("address"))
console.log(Object.hasOwn(obj, "name"))
console.log(Object.hasOwn(obj, "address"))
// 和hasOwnProperty的区别二:
const info = Object.create(null)
info.name = "wc"
// console.log(info.hasOwnProperty("name"))
console.log(Object.hasOwn(info, "name"))
ES13-New members of classes
在ES13中,新增了定义class类中成员字段(field)的其他方式:
- Instance public fields
- Static public fields
- Instance private fields
- static private fields
- static block
class Person {
// 实例属性
// 对象属性: public 公共 -> public instance fields
height = 1.88
// 对象属性: private 私有: 潜规则
// _intro = "name is wc"
// ES13对象属性: private 私有: 潜规则
#intro = "name is wc"
// 2.类属性(static)
// 类属性: public
static totalCount = "1000万"
// 类属性: private
static #maleTotalCount = "1000万"
constructor(name, age) {
// 对象中的属性: 在constructor通过this设置
this.name = name
this.age = age
this.address = "bj"
}
// 3.静态代码块
static {
console.log("Hello World")
console.log("Hello Person")
}
}
const p = new Person("wc", 18)
console.log(p)
console.log(p.name, p.age, p.height, p.address, p.#intro)
console.log(Person.#maleTotalCount)
二、Promise与异步方案
手写Promise
b站视频
知乎链接
添加链接描述
添加链接描述
添加链接描述
异步
- 同步代码
书写顺序和代码的执行顺序是一样的 - 异步代码
异步代码的书写顺序和代码的执行顺序不一样
解决异步问题
<script>
// 最早解决异步问题:靠回调函数
// 1.设计这样的一个函数
function execCode(counter, successCallback, failureCallback) {
// 异步任务
setTimeout(() => {
if (counter > 0) { // counter可以计算的情况
let total = 0
for (let i = 0; i < counter; i++) {
total += i
}
// 在某一个时刻只需要回调传入的函数
successCallback(total)
} else { // 失败情况, counter有问题
failureCallback(`${counter}值有问题`)
}
}, 3000)
}
// 2.ES5之前,处理异步的代码都是这样封装
execCode(100, (value) => {
console.log("本次执行成功了:", value)
}, (err) => {
console.log("本次执行失败了:", err)
});
</script>
Promise
- Promise是一个类,可以翻译成 承诺、许诺 、期约;
- 当我们需要的时候,给予调用者一个承诺:待会儿我会给你回调数据时,就可以创建一个Promise的对象;
- 在通过new创建Promise对象时,我们需要传入一个回调函数,我们称之为executor
- 这个回调函数会被立即执行,并且给传入另外两个回调函数resolve、reject;
- 当我们调用resolve回调函数时,会执行Promise对象的then方法传入的回调函数;
- 当我们调用reject回调函数时,会执行Promise对象的catch方法传入的回调函数;
<script>
// p叫promise对象
// 手写promise
// 当new Promise时,执行器会立即执行
// Promise有三个状态 当new出来时,是处于等状态
// 调用resolve可以把等待状态的promise变成成功态
// 调用reject可以把等待状态的promise变成失败态
// 一个promise只能从等待到成功或从等待到失败
let p = new Promise((resolve, reject) => {
console.log("我是执行器,我立即执行了...");
// 在执行器中通常写异步代码
// 我们说的异步指的是定时器中的回调函数
setTimeout(() => {
// console.log("我是定时器");
// 在异步代码中,可以调用resovle或reject
// resolve,reject是一个函数
// resolve中的值,就是成功的值,也就是终值 value
// resolve("包包"); // 就是把等待的promise变成成功的promise
// reject中的值,就是失败的值,也就是失败的原因 reason
reject("没钱"); // 就是把等待的promise变成失败的promise
}, 3000)
});
</script>
Promise有三种状态:
- 等待状态:pending 默认你创建出来的promise是处于等待状态
- 成功状态:fulfulled 当调用resolve时,就可以把promise从等待变成成功
- 失败状态:rejected 当调用reject时,就可以把promise从等待变成失败
<script>
// Promise解决异步问题
function execCode(counter) {
let promise = new Promise((resolve, reject) => {
// 异步任务
setTimeout(() => {
if (counter > 0) { // counter可以计算的情况
let total = 0
for (let i = 0; i < counter; i++) {
total += i
}
// 成功的回调
resolve(total)
} else { // 失败情况, counter有问题
// 失败的回调
reject(`${counter}有问题`)
}
}, 3000)
})
return promise;
}
let promise = execCode(100)
promise.then(result => {
console.log(result);
}, err => {
console.log(err);
})
</script>
resolve
- resolve不同值的区别
- 情况一:如果resolve传入一个普通的值或者对象,那么这个值会作为then回调的参数;
- 情况二:如果resolve中传入的是另外一个Promise,那么这个新Promise会决定原Promise的状态:
- 情况三:如果resolve中传入的是一个对象,并且这个对象有实现then方法,那么会执行该then方法,并且根据then方法的结果来决定Promise的状态:
<script>
// resolve的实参问题
const p = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve("p的resolve")
reject("没钱")
}, 2000)
})
const promise = new Promise((resolve, reject) => {
// 1)参数是普通的数据
// resolve(["a","b","c"])
// 2)参数是promise
// 如果resolve的参数是promise,最终结果由p决定
// resolve(p)
// 3)参数是thenable(就是一个对象中有一个then函数)
resolve({
then: function(resolve, reject) {
// resolve("包包")
reject("没钱")
}
})
});
promise.then(res => {
console.log(res);
}, err => {
console.log(err);
})
</script>
then函数
- then方法是Promise对象上的一个方法(实例方法):
- 它其实是放在Promise的原型上的 Promise.prototype.then
- then方法接受两个参数:
- fulfilled的回调函数:当状态变成fulfilled时会回调的函数;
- reject的回调函数:当状态变成reject时会回调的函数;
- 一个Promise的then方法是可以被多次调用的:
- 每次调用我们都可以传入对应的fulfilled回调;
- 当Promise的状态变成fulfilled的时候,这些回调函数都会被执行;
<script>
// then函数
const promise = new Promise((resolve, reject) => {
resolve("success")
// reject("error")
});
promise.then(res => {
console.log(res);
}, err => {
console.log(err);
})
promise.then(res => {
console.log(res);
}, err => {
console.log(err);
})
promise.then(res => {
console.log(res);
}, err => {
console.log(err);
})
</script>
then函数的返回值
- then方法本身是有返回值的,它的返回值是一个Promise,所以我们可以进行如下的链式调用:
- 但是then方法返回的Promise到底处于什么样的状态呢?
- Promise有三种状态,那么这个Promise处于什么状态呢?
- 当then方法中的回调函数本身在执行的时候,那么它处于pending状态;
- 当then方法中的回调函数返回一个结果时,那么它处于fulfilled状态,并且会将结果作为resolve的参数;
- 情况一:返回一个普通的值;
- 情况二:返回一个Promise;
- 情况三:返回一个thenable值;
- 当then方法抛出一个异常时,那么它处于reject状态;
thenable
<script>
// then函数的返回值问题
const promise = new Promise((resolve, reject) => {
resolve("success")
});
promise.then(res => {
console.log(res);
// 如果返回thenable,整体的promise取决于thenable的状态
return {
then: function(resolve, reject) {
// resolve("包包")
reject("没钱")
}
};
}, err => {
console.log(err);
}).then(res => {
console.log("res:", res);
}, err => {
console.log("err:", err);
})
</script>
then的顺延
<script>
// then的顺延
const promise = new Promise((resolve, reject) => {
reject("bad")
});
promise.then(res => {
console.log(res);
}, err => {
console.log(err);
// 这里返回了und 就意味着新的promise是成功的
// new Error("我错了") 就意味着新的promise是失败的
throw new Error("我错了")
}).then(res => {
console.log("res:", res);
}, null).then(null, err => {
console.log("err:", err);
})
</script>
catch方法
- catch方法也是Promise对象上的一个方法(实例方法):
- 它也是放在Promise的原型上的 Promise.prototype.catch
- 一个Promise的catch方法是可以被多次调用的:
- 每次调用我们都可以传入对应的reject回调;
- 当Promise的状态变成reject的时候,这些回调函数都会被执行;
catch方法 – 返回值
- 事实上catch方法也是会返回一个Promise对象的,所以catch方法后面我们可以继续调用then方法或者catch方法:
- 下面的代码,后续是catch中的err2打印,还是then中的res打印呢?
- 答案是res打印,这是因为catch传入的回调在执行完后,默认状态依然会是fulfilled的;
- 如果我们希望后续继续执行catch,那么需要抛出一个异常:
finally
- 在ES9中,新增了finally方法,无论promise是成功的,还是失败的,最终都会执行finally
- finally方法是不接收参数的,因为无论前面是fulfilled状态,还是rejected状态,它都会执行。
<script>
// finally
const promise = new Promise((resolve, reject) => {
reject("bad")
});
promise.then(res => {
console.log(res);
}, err => {
console.log(err);
throw new Error("我错了")
}).then(res => {
console.log("res:", res);
}).catch(err => {
console.log("err:", err);
}).finally(() => {
console.log("哈哈哈哈")
console.log("呵呵呵呵")
})
</script>
Promise系统掌握之类方法(静态方法)
- then、catch、finally方法都属于Promise的实例方法,都是存放在Promise的prototype上
resolve
- 有时候我们已经有一个现成的内容了,希望将其转成Promise来使用,这个时候我们可以使用 Promise.resolve 方法来完成。
- Promise.resolve的用法相当于new Promise,并且执行resolve操作:
- resolve参数的形态:
- 情况一:参数是一个普通的值或者对象
- 情况二:参数本身是Promise
- 情况三:参数是一个thenable
<script>
// 类方法(静态方法)
const promise = Promise.resolve("hello")
promise.then(res => {
console.log("then结果:", res)
})
// 相当于
// new Promise((resolve) => {
// resolve("hello")
// })
</script>
reject
- reject方法类似于resolve方法,只是会将Promise对象的状态设置为reject状态。
- Promise.reject的用法相当于new Promise,只是会调用reject:
- Promise.reject传入的参数无论是什么形态,都会直接作为reject状态的参数传递到catch的。
<script>
// 类方法(静态方法)
const promise = Promise.reject("rejected error")
promise.catch(err => {
console.log("err:", err)
})
// 相当于
// new Promise((_, reject) => {
// reject("rejected error")
// })
</script>
all方法
- 它的作用是将多个Promise包裹在一起形成一个新的Promise;
- 新的Promise状态由包裹的所有Promise共同决定:
- 当所有的Promise状态变成fulfilled状态时,新的Promise状态为fulfilled,并且会将所有Promise的返回值组成一个数组;
- 当有一个Promise状态为reject时,新的Promise状态为reject,并且会将第一个reject的返回值作为参数;
<script>
// 类方法(静态方法) all
// 创建三个Promise
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve("p1 resolve")
reject("p1 reject error")
}, 3000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve("p2 resolve")
reject("p2 reject error")
}, 2000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p3 resolve")
}, 5000)
})
// 类方法(静态方法) all
// all的作用:所有promise都成功后,得到所有成功后的promise结果
// 如果有一个先失败了,直接得到最先失败promise的结果
Promise.all([p1, p2, p3]).then(res => {
// ['p1 resolve', 'p2 resolve', 'p3 resolve']
console.log(res);
}).catch(err => {
console.log(err);
})
</script>
allSettled
- all方法有一个缺陷:当有其中一个Promise变成reject状态时,新Promise就会立即变成对应的reject状态。
- 那么对于resolved的,以及依然处于pending状态的Promise,我们是获取不到对应的结果的;
- 在ES11(ES2020)中,添加了新的API Promise.allSettled:
- 该方法会在所有的Promise都有结果(settled),无论是fulfilled,还是rejected时,才会有最终的状态;
- 并且这个Promise的结果一定是fulfilled的;
- allSettled的结果是一个数组,数组中存放着每一个Promise的结果,并且是对应一个对象的;
- 这个对象中包含status状态,以及对应的value值;
<script>
// 类方法(静态方法) allSettled
// 创建三个Promise
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve("p1 resolve")
reject("p1 reject error")
}, 3000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve("p2 resolve")
reject("p2 reject error")
}, 2000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p3 resolve")
}, 5000)
})
// 类方法: allSettled
// [
// {
// "status": "fulfilled",
// "value": "p1 resolve"
// },
// {
// "status": "fulfilled",
// "value": "p2 resolve"
// },
// {
// "status": "fulfilled",
// "value": "p3 resolve"
// }
// ]
// allSettled 获取所有的promise的结果,不管成功还是失败
Promise.allSettled([p1, p2, p3]).then(res => {
console.log("all settled:", res)
})
</script>
race方法
- race是竞技、竞赛的意思,表示多个Promise相互竞争,谁先有结果,那么就使用谁的结果;
<script>
// 创建三个Promise
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve("p1 resolve")
reject("p1 reject error")
}, 3000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve("p2 resolve")
reject("p2 reject error")
}, 2000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p3 resolve")
}, 5000)
})
// 类方法: race方法 race是比赛的意思
// 特点: 会等到第一个Promise有结果(无论这个结果是fulfilled还是rejected)
Promise.race([p1, p2, p3]).then(res => {
console.log("race promise:", res)
}).catch(err => {
console.log("race promise err:", err)
})
</script>
any方法
- any方法是ES12中新增的方法,和race方法是类似的:
- any方法会等到一个fulfilled状态,才会决定新Promise的状态;
- 如果所有的Promise都是reject的,那么也会等到所有的Promise都变成rejected状态;
- 如果所有的Promise都是reject的,那么会报一个AggregateError的错误。
<script>
// 类方法: any方法
// 创建三个Promise
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve("p1 resolve")
reject("p1 reject error")
}, 3000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p2 resolve")
// reject("p2 reject error")
}, 2000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve("p3 resolve")
reject("p3 reject error")
}, 5000)
})
// 类方法: any方法
// any 返回第1个成功的 或者 返回所有都失败了
Promise.any([p1, p2, p3]).then(res => {
console.log("any promise res:", res)
}).catch(err => {
console.log("any promise err:", err)
})
</script>
async函数
- async是一个关键字,用于声明一个异步函数,async是asynchronous简写,是异步的意思。
- sync是synchronous简写,是同步的意思。
<script>
// 普通函数
// function foo() { }
// const bar = function () { }
// const baz = () => { }
// 生成器函数
// function* foo() { }
// 异步函数
async function foo() {
console.log("foo function1")
console.log("foo function2")
console.log("foo function3")
}
// async返回promise
let res = foo();
console.log(res);
let gn = async function() {};
let kn = async () => {};
class Person {
async running() {}
}
</script>
async函数的返回值
异步函数的结果永远都是promise
异步函数内部代码的执行过程和普通函数是一样的,默认也是同步执行。异步函数和普通函数的区别,如下:
- 异步函数可以有返回值,但是不管返回什么普通值,都会包裹在Pormise.resolve中
- 如果异步函数自己返回了promise,得到的Promies状态由这个promise决定
- 如果我们异步函数返回值是一个对象并且实现thenable,得到的Promies状态由then方法中做了什么才能决定
- 如果在async函数中抛出一个错误,得到的promise是一个失败的promsie
<script>
async function foo() {
console.log("foo function1")
console.log("foo function2")
console.log("foo function3")
// 1)返回普通值,promis是成功的promsie
// return 123;
// 2)返回promise res这个promise是成功还是失败,取决于你返回的promise是成功还是失败
// return new Promise((resolve, reject)=>{
// setTimeout(()=>{
// resolve("hello")
// },2000)
// })
// 3)返回thenable res这个promise是成功还是失败,取决于你返回的thenable是成功还是失败
return {
then: function(resolve, reject) {
reject("没钱~")
}
}
}
let res = foo();
res.then(res => {
console.log("res:", res);
}).catch(err => {
console.log("err:", err);
})
</script>
async函数有异常
<script>
// 在async函数中,如果抛出一个错误,res这个promise是失败的promise
async function foo() {
console.log("foo function1")
console.log("foo function2")
console.log("foo function3")
throw new Error("我是异常")
}
let res = foo();
res.then(res => {
console.log("res:", res);
}).catch(err => {
console.log("err:", err);
})
</script>
awati的使用
async关键字可以单独使用,在异步函数内部可以使用await关键字,但是在普通函数中不能使用await关键字
- 作用
- await后面跟一个表达式,这个表达式通常是一个promise
- 这个await可以等待它后面的promise成功后,拿到成功的结果,得到之后,才会执行后面的代码
- await后面跟不同的数据:
- 如果await后面跟一个普通值,那么会直接返回这个值。
- 如果await后面跟一个thenable对象,那么要看你这个thenable中的then做了什么。
- 如果await后面的promise是失败的,需要通过try catch来获取失败的结果。
<script>
// await后面跟一个普通值
// function fn(){
// await 123
// }
// SyntaxError: await is only valid in async functions and the top level bodies of modules
// fn();
// awati 1)必须写在async函数中 2)await后面通常是跟一个promsie
// 3)await前面就可以获取promise成功的结果
async function gn() {
let rs = await 123
console.log("rs:", rs); // 123
// 返回一个und
}
gn().then(res => {
console.log("res:", res);
})
</script>
await后面通常跟promise
<script>
function bar() {
console.log("bar function")
return new Promise(resolve => {
setTimeout(() => {
resolve(123)
}, 2000)
})
}
async function foo() {
// await后续返回一个Promise, 那么会等待Promise有结果之后, 才会继续执行后续的代码
// await下面的代码相当于一个.then
const res1 = await bar()
console.log("await后面的代码:", res1)
const res2 = await bar()
console.log("await后面的代码:", res2)
}
foo()
</script>
<!-- <script>
function bar() {
return new Promise((resolve, reject) => {
setTimeout(() => {
// resolve("包包")
reject("没钱")
}, 2000)
})
}
async function foo() {
// bar().then(res=>{
// console.log("res:",res);
// }).catch(err=>{
// console.log("err:",err);
// })
try {
let res = await bar();
console.log("res:", res);
} catch (err) {//用try catch返回失败结果
console.log("err:", err);
}
}
foo();
</script> -->
使用async+await处理异常问题
<script>
function requestData(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(url)
// reject("error message")
}, 2000);
})
}
// await后面跟promise
// await前面得到成功的promise的结果
// await下面就可以使用成功的结果 相当于then
// await等待的意思 让我们处理异步像同步代码一样
async function getData() {
const res1 = await requestData("001")
console.log("res1:", res1)
const res2 = await requestData(res1)
console.log("res2:", res2)
const res3 = await requestData(res2)
console.log("res3:", res3)
return res3
}
getData().then(res => {
console.log(res);
})
</script>
浏览器事件环
进程:
计算机已经运行直来的程序,是操作系统操作程序的一种方式。当一个软件运行起来后,就是一个进程,电脑上可以运行很多软件,在OS上,有很多的进程,进程是OS分配资源(CPU和内存)的基本单位。OS可以当在一个工厂,一个个的车间就是进程。
线程:
操作系统能够运行运算调度的最小单位,一个进程中,至少要包含一个线程,用来执行程序中的代码,这个线程叫主线程,线程才是真正干活的,类似于工厂中的工人。一个车间如果只有一个工人,就是单线程,如果一个车间中有N个工人,就是多线程。
操作系统的工作方式
- 操作系统是如何做到同时让多个进程(边听歌、边写代码、边查阅资料)同时工作呢?
- 这是因为CPU的运算速度非常快,它可以快速的在多个进程之间迅速的切换;
- 当我们进程中的线程获取到时间片时,就可以快速执行我们编写的代码;
- 对于用户来说是感受不到这种快速的切换的;
浏览器是多进程的:
浏览器是一个多进程的软件,一个选项卡,就是一个进程,进程之间一般是独立的。在每一个进程中,包含了很多的线程,其中就包括JS代码执行线程。执行JS代码的线程就一个,也就是说,同一个时刻,只能做一件事,那么我们就说JS是单线程的。如果遇到了一个非常耗时的任务,线程就阻塞,此时,JS的主线程不会等待,浏览器会开一些其它线程去执行耗时任务,小线程执行的结果,就通过回调函数告诉主线程,我们说的JS是单线程的,是指主线程是单线程的,浏览器内部还可以开一些其它线程,如定时器线程,如ajax数据请求线程。
异步代码分两类:
- 宏任务队列(macrotask queue):ajax、setTimeout、setInterval、DOM监听、UI Rendering等
- 微任务队列(microtask queue):Promise的then回调、 Mutation Observer API、queueMicrotask()等
JS代码的执行顺序:
- 从代码段开始执行
- 如果遇到一个宏任务,会把这个任务放到一个宏任务队列,如果遇到一个微任务,就把这个微任务放到微任务任务中。
- 当同步代码执行完毕后,先去清空微任务队列。
- 当微任务队列清空完毕后,从宏任务队列中取出一个宏任务,去执行,在执行过程中,你的宏任务中可能还有同步代码或宏任务或微任务,重复上面的步骤,执行完一个宏任务,肯定要清空微任务队列。
例题
Promise.resolve().then(() => {
console.log(0);
return Promise.resolve(4);
}).then((res) => {
console.log(res)
})
Promise.resolve().then(() => {
console.log(1);
}).then(() => {
console.log(2);
}).then(() => {
console.log(3);
}).then(() => {
console.log(5);
}).then(() =>{
console.log(6);
})
第一次: 在发现 Promise.resolve(4) 的时候,创建 NewPromiseResolveThenableJob,并将其送入微任务队列
第二次: 在处理 Promise.resolve(4) 的时候,调用 then 方法时,内部创建了微任务来处理回调函数
解析
https://juejin.cn/post/7151707736562991112
https://juejin.cn/post/6953452438300917790
三、Iterator-Generator
迭代器
- 迭代器(iterator),使用户在容器对象(container,例如链表或数组)上遍访的对象,使用该接口无需关心对象的内部实现细节。
- 其行为像数据库中的光标,迭代器最早出现在1974年设计的CLU编程语言中;
- 在各种编程语言的实现中,迭代器的实现方式各不相同,但是基本都有迭代器,比如Java、Python等;
- 从迭代器的定义我们可以看出来,迭代器是帮助我们对某个数据结构进行遍历的对象。
- 在JavaScript中,迭代器也是一个具体的对象,这个对象需要符合迭代器协议(iterator protocol):
- 迭代器协议定义了产生一系列值(无论是有限还是无限个)的标准方式;
- 在JavaScript中这个标准就是一个特定的next方法;
- next方法有如下的要求:
- 一个无参数或者一个参数的函数,返回一个应当拥有以下两个属性的对象:
- done(boolean)
- 如果迭代器可以产生序列中的下一个值,则为 false。(这等价于没有指定 done 这个属性。)
- 如果迭代器已将序列迭代完毕,则为 true。这种情况下,value 是可选的,如果它依然存在,即为迭代结束之后的默认返回值。
- value
- 迭代器返回的任何 JavaScript 值。done 为 true 时可省略。
- 迭代器返回的任何 JavaScript 值。done 为 true 时可省略。
可迭代对象
- 当一个对象实现了iterable protocol协议时,它就是一个可迭代对象;
- 这个对象的要求是必须实现 @@iterator 方法,在代码中我们使用 Symbol.iterator 访问该属性;
- 好处
- 当一个对象变成一个可迭代对象的时候,就可以进行某些迭代操作;
- 比如 for…of 操作时,其实就会调用它的 @@iterator 方法;
原生迭代器对象
- String、Array、Map、Set、arguments对象、NodeList集合;
可迭代对象的应用
- JavaScript中语法:for …of、展开语法(spread syntax)、yield*、解构赋值(Destructuring_assignment);
- 创建一些对象时:new Map([Iterable])、new WeakMap([iterable])、new Set([iterable])、new WeakSet([iterable]);
- 一些方法的调用:Promise.all(iterable)、Promise.race(iterable)、Array.from(iterable);
自定义类的迭代实现
迭代器的中断
- 迭代器在某些情况下会在没有完全迭代的情况下中断:
- 比如遍历的过程中通过break、return、throw中断了循环操作;
- 比如在解构的时候,没有解构所有的值;
- 那么这个时候我们想要监听中断的话,可以添加return方法:
生成器
- 生成器是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等。
- 生成器函数也是一个函数,但是和普通的函数有一些区别:
- 首先,生成器函数需要在function的后面加一个符号:*
- 其次,生成器函数可以通过yield关键字来控制函数的执行流程:
- 最后,生成器函数的返回值是一个Generator(生成器):
- 生成器事实上是一种特殊的迭代器;
- MDN:Instead, they return a special type of iterator, called a Generator.
生成器函数执行
- 调用next
- 通过yield来返回结果
生成器传递参数 – next函数
- 给每个分段来传递参数
- 在调用next函数的时候,可以给它传递参数,那么这个参数会作为上一个yield语句的返回值;
- 注意:也就是说我们是为本次的函数代码块执行提供了一个值;
生成器提前结束 – return函数
- return传值后这个生成器函数就会结束,之后调用next不会继续生成值
生成器抛出异常 – throw函数
- 抛出异常后我们可以在生成器函数中捕获异常;
- 但是在catch语句中不能继续yield新的值了,但是可以在catch语句外使用yield继续中断函数的执行
生成器替代迭代器
- 生成器是一种特殊的迭代器,那么在某些情况下我们可以使用生成器来替代迭代器:
- 还可以使用yield*来生产一个可迭代对象
- 相当于是一种yield的语法糖,只不过会依次迭代这个可迭代对象,每次迭代其中的一个值
- 相当于是一种yield的语法糖,只不过会依次迭代这个可迭代对象,每次迭代其中的一个值
自定义类迭代 – 生成器实现
- 在之前的自定义类迭代中,我们也可以换成生成器:
对生成器的操作
异步处理方案
- 案例需求:
- 我们需要向服务器发送网络请求获取数据,一共需要发送三次请求;
- 第二次的请求url依赖于第一次的结果;
- 第三次的请求url依赖于第二次的结果;
- 依次类推;
Generator方案
优化
- 自动执行generator函数
- 目前我们的写法有两个问题:
- 第一,我们不能确定到底需要调用几层的Promise关系;
- 第二,如果还有其他需要这样执行的函数,我们应该如何操作呢?
- 所以,我们可以封装一个工具函数execGenerator自动执行生成器函数:
自动执行generator函数
异步函数 async function
- async关键字用于声明一个异步函数:
- async是asynchronous单词的缩写,异步、非同步;
- sync是synchronous单词的缩写,同步、同时;
- 写法
异步函数的执行流程
- 异步函数的内部代码执行过程和普通的函数是一致的,默认情况下也是会被同步执行。
- 异步函数有返回值时,和普通函数会有区别:
- 情况一:异步函数也可以有返回值,但是异步函数的返回值相当于被包裹到Promise.resolve中;
- 情况二:如果我们的异步函数的返回值是Promise,状态由会由Promise决定;
- 情况三:如果我们的异步函数的返回值是一个对象并且实现了thenable,那么会由对象的then方法来决定;
- 如果我们在async中抛出了异常,那么程序它并不会像普通函数一样报错,而是会作为Promise的reject来传递;
await关键字
- async函数另外一个特殊之处就是可以在它内部使用await关键字,而普通函数中是不可以的。
- await关键字有什么特点呢?
- 通常使用await是后面会跟上一个表达式,这个表达式会返回一个Promise;
- 那么await会等到Promise的状态变成fulfilled状态,之后继续执行异步函数;
- 如果await后面是一个普通的值,那么会直接返回这个值;
- 如果await后面是一个thenable的对象,那么会根据对象的then方法调用来决定后续的值;
- 如果await后面的表达式,返回的Promise是reject的状态,那么会将这个reject结果直接作为函数的Promise的reject值
// 1.普通函数
// function foo1() {
// await 123
// }
// foo1()
// 2.await关键字
// await条件: 必须在异步函数中使用
function bar() {
console.log("bar function")
return new Promise(resolve => {
setTimeout(() => {
resolve(123)
}, 100000)
})
}
async function foo() {
console.log("-------")
// await后续返回一个Promise, 那么会等待Promise有结果之后, 才会继续执行后续的代码
const res1 = await bar()
console.log("await后面的代码:", res1)
const res2 = await bar()
console.log("await后面的代码:", res2)
console.log("+++++++")
}
foo()
await和async结合
// 1.定义一些其他的异步函数
function requestData(url) {
console.log("request data")
return new Promise((resolve) => {
setTimeout(() => {
resolve(url)
}, 3000)
})
}
async function test() {
console.log("test function")
return "test"
}
async function bar() {
console.log("bar function")
return new Promise((resolve) => {
setTimeout(() => {
resolve("bar")
}, 2000);
})
}
async function demo() {
console.log("demo function")
return {
then: function(resolve) {
resolve("demo")
}
}
}
// 2.调用的入口async函数
async function foo() {
console.log("foo function")
const res1 = await requestData("why")
console.log("res1:", res1)
const res2 = await test()
console.log("res2:", res2)
const res3 = await bar()
console.log("res3:", res3)
const res4 = await demo()
console.log("res4:", res4)
}
foo()
四、throw
错误处理方案
throw语句:
- throw语句用于抛出一个用户自定义的异常;
- 当遇到throw语句时,当前的函数执行会被停止(throw后面的语句不会执行);
- 如果我们执行代码,就会报错,拿到错误信息的时候我们可以及时的去修正代码。
- throw表达式就是在throw后面可以跟上一个表达式来表示具体的异常信息
- throw关键字可以跟上哪些类型呢?
- 基本数据类型:比如number、string、Boolean
- 对象类型:对象类型可以包含更多的信息
- 但是每次写这么长的对象又有点麻烦,所以我们可以创建一个类:
Error类型
- JavaScript已经给我们提供了一个Error类,我们可以直接创建这个类的对象
- Error包含三个属性
- messsage:创建Error对象时传入的message;
- name:Error的名称,通常和类的名称一致;
- stack:整个Error的错误信息,包括函数的调用栈,当我们直接打印Error对象时,打印的就是stack;
- Error有一些自己的子类:
- RangeError:下标值越界时使用的错误类型;
- SyntaxError:解析语法错误时使用的错误类型;
- TypeError:出现类型错误时,使用的错误类型;
异常的捕获
异常传递过程
- foo函数在被执行时会抛出异常,也就是我们的bar函数会拿到这个异常;
- 但是bar函数并没有对这个异常进行处理,那么这个异常就会被继续传递到- 调用bar函数的函数,也就是test函数;
- 但是test函数依然没有处理,就会继续传递到我们的全局代码逻辑中;
- 依然没有被处理,这个时候程序会终止执行,后续代码都不会再执行了;
try catch
- 在ES10(ES2019)中,catch后面绑定的error可以省略。
- 当然,如果有一些必须要执行的代码,我们可以使用finally来执行:
- finally表示最终一定会被执行的代码结构;
- 注意:如果try和finally中都有返回值,那么会使用finally当中的返回值