1、let和const
这两个的出现,总感觉是为了开发代码规范而出现的 。我们要组件放弃var,在项目中多用let和const。
与var的区别:
- var有变量提升,有初始化提升,值可以变,var只有函数作用域
- let有变量提升,没有初始值提升,值可以变
- const有变量提升,没有初始化提升,值不可变。但如果是定义对象,则属性可变
暂时性死区问题说明:其实let和const是有变量提升的,但是没有初始化提升:
var name='Amelian'
function fn(){
console.log(name);
let name = 'hzz'
}
fn()// Cannot access 'name' before initialization
2、默认参数
? ? ? ? 在开发中你曾遇到过这样的问题,如果参数不传过来,你就设置默认参数
function(name,age){
var name=name || 'Amelian';
var age=age || 18;
console.log(name,age);
}
fn()// 林三心 18
? ? ? ? 但是这么写确实不优雅,可以使用ES6的默认参数
function fn(name='Amelian',age=18){
console.log(name,age)
}
fn()// 林三心 18
fn('sunshine', 22) // sunshine 22
3、扩展运算符
在es6之前,想拼接多个数组,一般使用下面的操作:
const arr1=[1,2,4]
const arr2=[1,5,4]
const arr3=[1,8,4]
const arr=arr1.concat(arr2).concat(arr3)
有了es6之后,可以直接使用扩展运算符进行操作
const arr1=[1,2,4]
const arr2=[1,5,4]
const arr3=[1,8,4]
const arr=[...arr1,...arr2,...arr3]
4、剩余参数
大家可能会遇到这种问题,一个函数,传入参数的个数是不确定的,这就可以用es6的剩余参数
function fn(name,...params){
console.log(name);
console.log(params)
}
fn('Amelian',1,2) // Amelian [ 1, 2 ]
fn('Amelian',1,2,3,4,5) // Amelian [ 1, 2, 3, 4, 5 ]
5、模板字符串
以前,凭借字符串只能这么做:
const name='Amelian'
const age=22
console.log(name+'今年'+age+'岁啦')// // Amelian今年22岁啦
现在可以这么做,会更优雅
const name='Amelian'
const age=22
console.log(`${name}今年${age}岁啦`)// // Amelian今年22岁啦
6.Object.keys
可以用来获取对象到的key的集合,进而可以获得对应key的vakue
const obj={
name:'Amelain',
age:18,
gender:'女'
}
const keys=Object.keys(obj)
console.log(keys)//['name','age','gender']
7、箭头函数
在es6之前都是使用普通函数
function fn(){}
const fn=function(){}
es6新加了箭头函数
箭头函数和普通函数的区别:
- 箭头函数不可以作为构造函数,不能使用new
- 箭头函数没有自己的this
- 箭头函数没有arguments对象
- 箭头函数没有原型对象
8、Array.prototype.foreach(数组遍历的方法)
const eachArr=[1,2,3,4,5]
//三个参数:遍历项、索引、数组本身
//配合箭头函数
eachArr.forEach((item,index,arr)=>{
console.log(item,index,arr)
})
1 0 [ 1, 2, 3, 4, 5 ]
2 1 [ 1, 2, 3, 4, 5 ]
3 2 [ 1, 2, 3, 4, 5 ]
4 3 [ 1, 2, 3, 4, 5 ]
5 4 [ 1, 2, 3, 4, 5 ]
9、Array.prototype.map(循环遍历,返回新的数组)
常用与返回一个处理过后的新数组
const mapArr=[1,2,3,4,5]
//三个参数:遍历项、索引、数组本身
//配合箭头函数,对每个元素进行翻倍
const mapArr2=mapArr.map((num,index,arr)=>{
return 2*num
})
console.log(mapArr2)
[ 2, 4, 6, 8, 10 ]
10、Array.prototype.filter
顾名思义,是用来过滤的方法
const filterArr=[1,2,3,4,5,6]
//三个参数:遍历项、索引、数组本身
//配合箭头函数,返回大于3的集合
const filterArr2=filterArr.filter((item,index,arr)=>{
return num>3
})
[4,5]
11、Array.prototype.some
some,意思就是只有一个是真的,那就返回真
const someArr=[false,true,true,false]
//三个参数:遍历项、索引、数组本身
//配合箭头函数,只要有一个是为true,就返回true,一个true都没有,就返回false
const someArr2=someArr.filter((bol,index,arr)=>{
return bol
})
console.log(someArr2)//true
12、Array.prototype.every
every跟some是相反的,some是只要有一个就行,every是所有为真才返回真
const everyArr=[false,true,true,false]
//三个参数:遍历项、索引、数组本身
//配合箭头函数,只要有一个是为true,就返回true,一个true都没有,就返回false
const everyArr2=everyArr.filter((bol,index,arr)=>{
return bol
})
console.log(everyArr2)//false
13、Array.prototypr.reduce
- 第一个参数callback函数:pre为上次return的值,next为数组的本次遍历项的值
- 第二个参数为初始值,也是第一个pre
举两个例子:
//计算1+2+3+4+5
const reduceArr=[1,2,3,4,5]
const sum = reduceArr.reduce((pre,next)=>{
return pre+sum
},0)
console.log(sum)//15
//统计元素出现的个数
const nameArr = ['hzz', 'wd', 'lhk', 'lhk', 'hzz']
const totalObj = nameArr.reduce((pre, next) => {
//由于初始值为{}空对象,因此pre[next]表示obj.next,
//由于此时{}为空,因此使用pre[]的方式来表示对象的属性
if (pre[next]) {
pre[next]++//执行次数加一
} else {
pre[next] = 1//表示再次遍历的时候没有重复出现了
}
return pre
}, {})
console.log(totalObj)//{hzz: 2, wd: 1, lhk: 2}
14、对象属性同名简写
? ? ? ? 在之前,同名属性需要这么写
const name = 'Amelian';
const age =22;
const obj={
name:name,
age:age
}
cosole.log(obj)//{name:'Amelian',age:22}
对于es6新增的语法,现在只需要这么写
const name = 'Amelian';
const age =22;
//属性同名可简写
const obj={
name
age
}
cosole.log(obj)//{name:'Amelian',age:22}
15、Promise
? ? ? ? Promise是异步编程的一种解决方案,比传统的解决方案--回调函数和事件--更合理和更强大。它由社区最早提出把和实现。
? ? ? ? 所谓promise,简单来说就是一个容器,里面保存着未来才会结束的事情(通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。Promise提供统一的API,各种异步操作都可以用同样的方法进行处理。
? ? ? ? Promise对象有以下特点
- 对象的状态不受外界影响,Promise对象代表一个异步操作,有三种状态:pedding(进行中)、fulfilled(已成功)、rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变只有两种可能:从pending编程fulfilled和从pending编程rejected。只要两种情况发生,状态就凝固了,不会再变了,会一致保持这个结果。这是称为resolved(已定型)
注意:为了行文方便,下面的resolved统一只指fulfilled状态,不包含rejected状态。
有了Promise对象,可以将异步操作以同步操作的操作表达出来,避免层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步更加容易。
15.1 基本用法
ES6规定,Promise对象是一个构造函数,用来生产Promise实例。
下面代码创造了一个Promise实例
const promise = new Promise(function (resolve, reject) {
//...some code
if (/*异步操作成功*/) {
resolve(value)
}else{
reject(error)
}
})
Promise构造函数接收一个函数作为参数,该函数的两个参数分别是resolve和reject,它们是两个函数,有javaScript引擎提供,不需要自己部署。
? ? ? ? Promise对象编程失败的时候,即异步操作失败的时候调用,并将异步操作报出的错误,作为参数传递出去。
????????Promise实例生成,可以用then方法分别制定resolved状态和rejected状态的回调函数。
promise.then(function (value) {
//success
}, function (error) {
//failure
})
then方法可以接受两个回调函数作为参数,第一个回调函数式Promise对象的状态编程resolved时调用,第二个回调函数式Promise对象变为rejected时调用,这两个函数都是可选的,不一定要提供,他们都接受Promise对象传出的值作为参数。
下面是一个Promise对象的简单的例子。
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done')
})
}
timeout(100).then((value) => {
console.log(value)
})
上面代码中,timeout方法会返回一个Promise实例,表示一段时间后才会发生结果。过了指定时间(ms参数) 以后,Promise实例的状态变成了resolved,就会触发then方法绑定的回调函数。
Promise新建后就会立即执行
let promise = new Promise(function (resolve, reject) {
console.log('Promise');
resolve();
})
promise.then(function () {
console.log('resolved')
})
console.log('hi')
?上面代码中,Promise新建后立即执行,所以首先输出的是Promise
,然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完毕才会执行,所以resolved最后输出。
下面是一个用Promise对象实现的Ajax操作的实例:
const getJSON = function(url) {
const promise = new Promise(function(resolve, reject){
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出错了', error);
});
?上面代码中,getJSON是对XMLHttpRequest 对象的封装,用于发出一个针对json数据的HTTP请求,并且返回一个Promise对象,需要注意的是,在getJSON内部,resolve和reject函数调用时,都携带了参数。
如果调用resolve函数和reject函数时都带有参数,那么它们的参数会被传递给回调函数(也就是then中的执行的函数)。reject函数的参数通常是Error对象的实例,表示抛出的错误;resolve函数的参数除了正常的值之外,还可能是例外一个Promise对象,比如下面这样:
const p1 = new Promise((resolve, reject) => {
//....
})
const p2 = new Promise((resolve, reject) => {
//....
resolve(p1)
})
上面代码中,p1和p2都是promise的实例,但是p2的resolve方法将p1作为参数,即一个异步操作的结果是返回另一个异步操作。也就是说,p1的状态决定了p2的状态,如果p1的状态时pending,那么p2的回调函数就会等待p1的状态改变,如果p1的状态以及是resolved或者reject,那么p2的回调函数将会立即执行。
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('fail'), 3000)
})
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(p1, 1000)
})
})
p2.then(result => console.log(result)).catch(error => console.log(error))
注意,这是p1的状态就会传递给p2,
一般来说,调用resolve或reject以后,Promise的使命就完成了,后续的操作应该放在then方法里面。而不应该直接写在resolve 或reject 的后面,所以,最后在他们前面加上return 语句,这样就不会有意外。
new Promise((resolve, reject) => {
return resolve(1);
// 后面的语句不会执行
console.log(2);
})
15.2、Promise.prototype.then()
? ? ? ? Promise实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototypr上的,它的作用是为了Promise实例添加状态改变时的回调函数。前面说过,then方法的第一个参数就是resolved状态的回调函数,第二个参数是rejected状态的回调函数,他们都是可选的。
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例),因此可以使用链式写法,在 then、方法后面再调用另一个then方法。
15.3 、Promise.prototype.catch()
用于指定发生错误时的回调函数。
getJSON('/posts.json').then(function (posts) {
//....
}).catch(function (error) {
//处理getJSON和前一个回调函数运行时发生的错误
console.log('发生错误!', error);
})
上面代码中,getJSON()方法返回一个Promise对象,如果该对象状态为resolved,则会调用then()方法指定的回调函数;如果异步操作 抛出错误,状态就会变成rejected,就会调用catch()方法指定回调函数。处理这个错误,另外,then()方法指定的回调函数如果运行中抛出错误,也会被catch()方法捕获。
? ? ? ? 一般来说,不要在then()方法里定义reject状态的回调函数,即then的第二个参数,总是使用catch方法
// bad
promise
.then(function(data) {
// success
}, function(err) {
// error
});
// good
promise
.then(function(data) { //cb
// success
})
.catch(function(err) {
// error
});
上面的代码中,第二种写法要好于第一种写法,理由是第二种写法可以捕获前面then方法执行中的错误,也更接近同步的写法(try/catch),因此,建议总是使用catch()方法,而不使用then()方法的第二个参数。
16、class
以前我们使用够着函数生成对象,这么做
function Person(name, age) {
this.name = name;
this.age = age
}
Person.prototype.sayName = function () {
console.log(this.name)
}
const person1 = new Person('hzz', 18)
console.log(person1)
person1.sayName()
?tip:class的本质也是function,class事function的语法糖
现在有了es6,可以这样做
class Person {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name)
}
}
//由于class的本质也是函数,因此通过class创建对象的时候也是通过new的方式
const person1 = new Person('hzz');
person1.sayName()//'hzz'
? ? ? ? 除了以上,还需要知道class的以上知识点
? ? ? ? 静态属性和静态方法,使用static定义的属性和方法只能class自己使用,通过class创造的实例用不了
class Person {
constructor(name) {
this.name = name;
}
static age = 22
static fn() {
console.log('哈哈')
}
sayName() {
console.log(this.name)
}
}
console.log(Person.age);//22
Person.fn()//哈哈
const person1 = new Person('hzz');
person1.sayName()//'hzz'
person1.fn()//undefined
console.log(person1.age)//fn is not a function
extend继承
class Animal {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
class Cat extends Animal {
say() {
console.log(this.name, this.age);
}
}
const cat1 = new Cat('ketty', 3)// 继承了Animal的构造器
cat1.say()// ketty 3
17、结构赋值
以前想提取对象里的属性需要这么做
const obj = {
name: 'Amelain',
age: 22,
gender: '女'
}
const name = obj.name;
const age = obj.age;
const gender = obj.gender;
console.log(name, age, gender)//Amelian 22 女
ES6新增了结构赋值的语法
- 第一个属性是你需要结构赋值对象中的属性名
- 第二个属性是你想赋值出来的属性的命名,若想与原来的一致,那么就可以省略,反之可以重新定义
- 嵌套赋值的时候同理,
const obj = {
name: 'Amelain',
age: 22,
gender: '女',
doing: {
morning: '摸鱼',
afternoon: '摸鱼',
evening: 'sleep'
}
}
const { name, age, gender } = obj
console.log(name, age, gender)//Amelian 22 女
//结构重命名
const { name: myname } = obj
console.log(myname)
//嵌套结构
const { doing: { evening } } = obj;
console.log(evening);//sleep
18、find和findIndex
- find:用于找到第一个符合条件的数组成员,它的参数是一个回调函数,每个数组成员都会执行这个回调函数,找不到返回undefined
- findIndex:用于找到第一个符合条件的数组成员索引,找不到返回-1
//这里传递的是一个回调函数,然后findArr数组中的元素都会以此执行这个回调函数
const arr = [1, 2, 5, 6, 9, 8, 21, 15]
const res1 = arr.find((value, index, arr) => {
return value === 9
})
console.log(res1)//9 返回被查找的元素
const res2 = arr.findIndex((value, index, arr) => {
return value === 9
})
console.log(res2)//4 返回所查找元素的索引
19、for in 和 for of
- for in :遍历方法,可遍历数组和对象
- for of :遍历方法,只能遍历数组,不能 遍历非iterable对象
先看for in:
const obj = { name: 'Amelain', age: 22, gender: '女' }
const arr = [1, 2, 3, 45, 6]
for (let key in obj) {
console.log(key + '----->' + obj[key])
}
/*
name----->Amelain
age----->22
gender----->女
*/
for (let index in arr) {
console.log(index + '----->' + arr[index])
}
/*
0----->1
1----->2
2----->3
3----->45
4----->6
*/
再看for of:
for (let item of arr) {
console.log(item)//逐个输出数组全部元素
}
总结:for in可以同时对对象和数组进行遍历,遍历得到的直接元素是对象的属性或者数组的索引;for of 遍历数组,得到的直接是数组的元素,不能遍历对象。
20、set和map
20.1 先说说set的用法
//可不传数组
const set1 = new Set()
set1.add(1);
set1.add(2);
console.log(set1)//Set(2)?{1, 2}
//也可以传递数组
const set2 = new Set([1, 2, 3])
//添加元素使用 add
set2.add(4);
set2.add('Amelian')
console.log(set2)//Set(5)?{1, 2, 3, 4, 'Amelian'}
//检查是否含有某个元素 使用has
console.log(set2.has(2))//true
//查看长度
console.log(set2.size)//5
//删除元素 使用delete
set2.delete(2)
console.log(set2)//Set(4)?{1, 3, 4, 'Amelian'}
再说说set的不重复性
// 增加一个已有的元素,则增加无效,会被自动去重
const set1 = new Set([1])
set1.add(1)
console.log(set1)//Set(1)?{1}
//传入的数组中有重复项,会自动去重
const set2 = new Set([1, 2, 1, 3, 3, 'Amelian'])
console.log(set2)//Set(4)?{1, 2, 3, 'Amelian'}
Set的不重复性中,要注意引用数据类型和NaN
//两个对象都是不同指针,所以没法去重
const set1 = new Set([1, { name: 'Amelian' }, 2, { name: 'Amelian' }])
console.log(set1)//Set(4)?{1, { name: 'Amelian' }, 2, { name: 'Amelian' }}
//两个对象是同样一个指针,则能去重
const obj = { name: 'Amelian' }
const set2 = new Set([1, obj, 2, obj])
console.log(set2) Set(3) { 1, { name: 'Amelian' }, 2 }
//我们知道NaN!=NaN,即NaN是自身不等于自身的,但是在Set中还是会被去重掉
const set = new Set([1, NaN, 1, NaN])
console.log(set)//Set(2)?{1, NaN}
利用set的不重复性,可以实现数组去重
const arr = [1, 2, 14, 7, 8, 9, 2, 1, 7, 9]
//Set可利用扩展运算符转为数组
//下面的操作即是先把这个需要去重的数组作为参数传给Set,Set的不重复性会将数组去重
//去重后已经变为Set对象了,因此需要重新将Set转成数组
const changeArr = [...new Set(arr)]
//与上面的操作等价
const set1 = new Set(arr)
const quchongArr = [...set1]
console.log(quchongArr)
20.2 Map
? ? ? ? Map对比object最大的好处就是,key不受类型限制
const map1 = new Map()
//新增键值对 使用set(key,value)
map1.set(true, 1);
map1.set(1, 2);
map1.set('哈哈', 'xixi')
console.log(map1)//Map(3)?{true => 1, 1 => 2, '哈哈' => 'xixi'}
//判断map中是否含有某个key,使用has(key)
console.log(map1.has('哈哈'))//true
//获取map中某个键值对,使用get(key)
console.log(map1.get(true))//1
//删除map中某个键值对 使用delete(key)
map1.delete('哈哈')
console.log(map1)//Map(2)?{true => 1, 1 => 2}
//定义map,也可以传入键值对数组集合
const map2 = new Map([[true, 1], [1, 2], ['哈哈', 'xixi']])
console.log(map2)//Map(3)?{true => 1, 1 => 2, '哈哈' => 'xixi'}
?ES7
21、includes
传入元素,如果数组中能找到元素,则返回true,否则返回false
const includeArr = [1, 2, 3, 'Amelian', 'Cifum']
const isAn = includeArr.includes('Amelian')
console.log(isAn)//true
22、求幂运算符
以前求幂,我们需要这么写
const num = Math.pow(3,2) //9
ES7提供了求幂运算符:**
const num = 3**2 //9
***************************以下ES8**************************
23、Object.values(obj)
可以用来获取对象的value的集合
const obj = {
name: 'Amelian',
age: 22,
gender: '女'
}
const values = Object.values(obj)
console.log(values)//['Amelian', 22, '女']
24、Object.entries
可以用来获取对象的键值对集合
const obj = {
name: 'Amelian',
age: 22,
gender: '女'
}
const entries = Object.entries(obj)
console.log(entries)
?25、async/await
?这个是很常用的语法了,我的理解就是:以同步的方式执行异步操作
我们平时可能会遇到这种场景,接口一请求到数据一,而数据一被当做请求二的参数去请求数据二,我们会用promise如下面这样:
function fn() {
//模拟第一次请求
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(5)
}, 1000)
}).then(res => {
//模拟第二次请求
new Promise((resolve, reject) => {
setTimeout(() => {
//拿到第一次请求得到数据去*10,当做第二次请求的数据
resolve(res * 10)
}, 2000)
}).then(res => {
console.log(res)
})
})
}
fn()//1+2=3 3秒后输出50
这样的嵌套是不美观的,如果有很多个接口,那就回嵌套很多层,此时我们就可以使async/await来以同步的方式执行异步,注意一下几点:
- await只能在async函数里使用
- await后面最好接Promise,如果后面接的是普通函数则会直接执行
- async函数返回的是一个Promise
function fn1() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(5)
}, 1000)
})
}
function fn2(data) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(data * 10)
}, 2000)
})
}
async function req() {
//同步的方式执行异步,像排队一样
const data1 = await fn1()//等待1秒后返回数据再往下继续执行
const data2 = await fn2(data1)//拿到data1去请求 2秒后,往下走
console.log(data2) //总共3秒后 输出50
}
req()
***************************以下ES9**************************
26、Promise.finally
新增Promise方法,无论失败还是成功的状态,都会执行这个函数
new Promise((resolve, reject) => {
resolve('成功喽')
}).then(
res => {
console.log(res),
err => { console.log(err) }
}
).finally(() => {
console.log('我是finall')
})
new Promise((resolve, reject) => {
reject('失败哩')
}).then(
res => {
console.log(res),
err => { console.log(err) }
}
).finally(() => {
console.log('我是finall')
})
***************************以下ES9**************************
27、Array.flat
有一个二维数组,我想让他变成一维数组:
const arr = [1, 2, 3, [4, 5, 6]]
console.log(arr.flat())//[1, 2, 3, 4, 5, 6]
//还可以传递参数,参数为降维的次数
const arr1 = [1, 2, 3, [4, 5, 6, [7, 8, 9]]]
console.log(arr1.flat(2))//[1, 2, 3, 4, 5, 6, 7, 8, 9]
//如果传入的参数是一个无限大的数字,那么就表示无论该数组是几维的,均被降维一维数组
const arr2 = [1, 2, 3, [4, 5, 6, [7, 8, 9, [10, 11, 12]]]]
console.log(arr2.flat(Infinity))//[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
28、Array.flatMap
现有个需求:
let arr = ["科比 詹姆斯 安东尼", "利拉德 罗斯 麦科勒姆"];
?将上面的数组转为
[ '科比', '詹姆斯', '安东尼', '利拉德', '罗斯', '麦科勒姆' ]
?第一时间想到?map + flat
let arr = ["科比 詹姆斯 安东尼", "利拉德 罗斯 麦科勒姆"];
const arr2 = arr.map((item) => {
return item.split(" ")//即是将数组中每一项(字符串)每个空格处组成数组
})
console.log(arr2)
//[ ['科比', '詹姆斯', '安东尼'] , ['利拉德', '罗斯', '麦科勒姆'] ]
console.log(arr2.flat())//['科比', '詹姆斯', '安东尼', '利拉德', '罗斯', '麦科勒姆']
29、?.和?..
29.1? ??.
比如我们需要一个变量,是数组且有长度,才做某些操作
const list = null;
//do somethoing
if (list && list.length) {
//do something
}
//使用可选链
const list = null;
if (list?.length) {
//do something
}
比如有一个对象,我们要取一个可能不存在的值,甚至我们都不确定obj、是否存在
const obj = {
cat: {
name: '哈哈'
}
}
const dog = obj?.dog?.name//undefined
比如有个数组,我不确定它存不存在,存在的话就取索引为1的值
const arr = null;
const item = arr && arr[1]
//使用可选链
const arr = null;
const item = arr?.[1]
比如有一个函数,我们不确定它存不存在,存在的话就执行它
const fn = null;
//do something
const res = fn && fn()
//可选链
const res = fn?.()
|