主题
复习与新增的运算符(Day3 1/4)
本页对应 Day03_ES6,共 6 段视频,约 50 分钟。
学习目标
- 能说明指数运算符
**的含义,并正确计算或改写为Math.pow()等等价次方表达式。 - 能使用可选链运算符
?.访问对象属性、调用方法,并解释遇到null/undefined时短路返回undefined的行为。 - 能区分空值判断运算符
??与逻辑或||,在给定0、false、''、null、undefined等操作数时正确判断表达式结果。 - 能识别并使用逻辑赋值运算符
&&=、||=、??=,说明其等价展开形式及左侧必须为变量的原因。 - 能按“操作数个数、操作数类型要求、表达式结果类型、是否有副作用”归纳本页新增运算符的规则。
- 能在链式属性访问和方法调用场景中选择合适运算符,避免因中间值为
null/undefined导致报错。
知识点
回顾:先把前篇四块串起来
这一节先回顾前天内容,目的是把 ES6 新增特性按“数值、函数、数组、对象”四块串起来,避免知识点越学越碎。怎么想:不要背结论,要按“新增了什么、解决什么问题、和旧写法差在哪”去回忆。怎么做:数值部分记住二进制 0B、八进制 0O,以及 Number.isNaN、Number.isFinite、安全整数、Math 新增方法、数字分隔符 _;函数部分重点记参数默认值、剩余参数、箭头函数、参数尾逗号;数组部分重点记扩展运算符、Array 构造函数与实例方法;对象部分重点记属性简写、方法简写、表达式作属性名、super、对象扩展运算符、Object 构造函数方法。易错点:剩余参数替代 arguments,但它是真数组、名字自定义、只拿“剩下的实参”;箭头函数不能实例化,没有自己的 arguments,也没有自己的 this,this 沿作用域向上找最近的非箭头函数。
指数运算符:从 Math.pow 到 **
为什么需要:求次方以前要写 Math.pow(2, 10),写法偏长。怎么想:** 就是求次方数,左边是底数,右边是指数。怎么做:
js
console.log(2 ** 10); // 1024
console.log(8 ** 3); // 512易错点:别把 2 ** 10 理解成“2 乘 10”,它是 2 的 10 次方;旧代码里仍可能见到 Math.pow,两者表达的是同类需求。
可选链运算符:让链式读取不再轻易报错
为什么需要:对象层级一深,就常写 user.info.address。如果中间某一层是 undefined,继续点下去就会报错。怎么想:先分析执行顺序。user.parent 得到 undefined,再执行 undefined.address 必然报错。可选链 ?. 的作用是:前面若是 null 或 undefined,就短路,不再往后执行,整个表达式得到 undefined。怎么做:
js
const user = {
address: '上海',
info: { address: '北京' }
};
console.log(user.address); // 上海
console.log(user.info.address); // 北京
console.log(user.parent); // undefined
// console.log(user.parent.address); // 报错
console.log(user.parent?.address); // undefined调用方法时,问号点要放在小括号前:
js
user.say?.();
user.it?.();易错点:可选链不是“永不报错”,它只对 null/undefined 起短路作用;方法不存在时用 user.it?.() 可得到 undefined,但写成 user.it() 仍会报“不是函数”。它适合链式调用,但不要滥用成掩盖数据结构问题。
空值判断运算符:只认 null 和 undefined
为什么需要:|| 会把 0、false、空字符串都当成不成立,于是 0 || 100 得到 100,但有时我们只想在“空值”时取默认值。怎么想:?? 很像 ||,但判断更严格:左边是 null 或 undefined 才取右边,否则取左边。怎么做:
js
console.log(100 || 'hello'); // 100
console.log(0 || 'hello'); // hello
console.log(0 ?? 100); // 0
console.log(false ?? 100); // false
console.log(null ?? 100); // 100
console.log(undefined ?? 100); // 100易错点:?? 不会把 0、false 当空值;它和 || 的区别要能举例说明。
逻辑赋值运算符:把判断和赋值合成一步
为什么需要:过去常写 a = a && b、a = a || b、a = a ?? b,逻辑赋值把“判断 + 赋值”合并。怎么做:
js
let a1 = 10;
let a2 = 20;
let a3 = 30;
a1 &&= 250; // 等价于 a1 = a1 && 250
a2 ||= 250; // 等价于 a2 = a2 || 250
a3 ??= 250; // 等价于 a3 = a3 ?? 250
console.log(a1, a2, a3); // 250 20 30易错点:左边必须是变量,不能写 100 &&= 200;const 声明的常量不能重新赋值;&&=、||=、??= 都有副作用,重点看左边变量最终值,不要死盯整个表达式的值。
总结:按“操作数、类型、副作用”记规则
指数运算符 ** 两个操作数,通常都是 number,结果也是 number,无副作用。可选链 ?. 用于属性/方法访问,遇到 null/undefined 短路为 undefined,无副作用。空值判断 ?? 只在左边为 null/undefined 时取右边。逻辑赋值 &&=、||=、??= 左边必须是变量,会产生赋值副作用。调试时不要猜结果,按“先算左边、再看是否短路、最后看返回什么”一步步推导。
复习提纲(保留原代码)
text
1. 数值新增特性
1.1 二进制和八进制表示
1.2 Number构造函数本身新增的属性方法
1.3 Math对象新增的属性方法
1.4 数字分隔符
2. 函数新增特性
2.1 参数默认值
2.2 reset 参数
2.3 箭头函数
2.4 参数为逗号
3. 数组新增特性
2.1 扩展运算符
将数组拆分成逗号分隔的参数序列
从参数序列中获取到数组
2.2 Array构造函数的属性方法
2.3 Array实例的属性方法
4. 对象新增特性
4.1 {}声明对象 属性简写
4.2 {}声明对象 方法简写
4.3 {}声明对象 表达式作为属性名
4.4 super关键字
4.5 扩展运算符
将对象拆分为逗号分隔键值对序列
从逗号分隔键值对序列获取到对象
4.6 Object构造函数本身的属性方法本节笔记未包含图片,以下按转写稿把四大块和新增运算符串起来复习。注意术语纠正:reset 参数应为 rest 参数,参数为逗号指参数尾逗号。
数值新增特性
为什么需要:旧写法里八进制用前导 0,容易和普通数字混淆;数值判断、安全整数判断也需要更明确的 API。怎么想:把“表示方式”和“判断/计算方法”分开记。怎么做:二进制用 0b/0B,八进制用 0o/0O,与十六进制 0x 对应;Number 构造函数新增了 isNaN、isFinite 一类判断,以及安全整数、整数判断;Math 对象新增了课堂演示的方法;数字分隔符用下划线提高可读性。易错点:旧式前导 0 八进制已不推荐;分隔符只是可读性,不参与数值内容;Number.isInteger 与 Number.isSafeInteger 不是一回事,超过安全范围后即使看起来像整数,也不能保证计算精度。
函数新增特性
为什么需要:让参数更灵活,让回调更简洁。怎么想:先区分“形参默认值”“剩余实参”“箭头函数简写”。怎么做:参数默认值写在形参后;rest 参数代替 arguments,它是纯数组,名字可自定义,只收集剩余实参;箭头函数只有一个参数可省小括号,函数体只有一条 return 语句时可省大括号和 return,很适合做回调;参数尾逗号是语法特点。易错点:arguments 是类数组、系统创建、能拿到所有实参;rest 是纯数组、自己命名、只拿剩余实参。箭头函数不能实例化,没有自己的 arguments,this 沿作用域向上找最近的非箭头函数;箭头函数中可以用 rest。
数组新增特性
为什么需要:数组传参、复制、合并、解构都需要更统一的语法。怎么想:扩展运算符有两个方向:拆开、收集。怎么做:...arr 可把数组拆成逗号分隔的参数序列,用于函数传参、数组复制、数组合并;反向可用 rest 参数或解构赋值从参数序列中取到数组。Array 构造函数有 Array.from、Array.of 等属性方法;实例方法包括平铺、find 等课堂列出的方法。易错点:拆出来的“逗号分隔参数序列”是语法结构,不是数据类型,不能单独当值用,要放在函数调用括号、数组字面量或解构位置才生效;它很像解构赋值等号左边,也是语法结构。
对象新增特性
为什么需要:声明对象时减少重复代码,并支持更灵活的属性名。怎么想:属性简写看“属性名和变量名是否相同”;方法简写看是否省掉 function;表达式属性名看中括号里是否写表达式。怎么做:{ name }、{ say() {} }、{ [expr]: value };super 指向方法定义时所属对象的原型,不是调用该方法的对象;对象扩展运算符可把对象拆成逗号分隔键值对序列,用于复制、合并,反向只能用于解构赋值。易错点:super 只能在简写方法中使用,和 this 不同;对象扩展拆出的键值对序列也是语法结构;对象反向收集不能用于 rest 参数,只能用于解构赋值。
指数运算符
为什么需要:方便求次方。怎么想:过去用 Math.pow,现在可用 **。怎么做:2 ** 10 表示 2 的 10 次方,8 ** 3 表示 8 的 3 次方。易错点:两个操作数要求是 number,结果也是 number,没有副作用。
可选链运算符
为什么需要:链式读取 user.parent.address 时,若 user.parent 是 undefined,再点 address 会报错。怎么想:先分析 user.parent 得到 undefined,undefined.address 必然报错;问号点用于短路。怎么做:user?.address、user?.address?.city;调用方法写成 user.it?.()。调试过程:user.parent?.address 不报错,整个表达式得到 undefined;user.it?.() 中 it 不存在,也不报错,返回 undefined;user.say?.() 若 say 存在,则返回其返回值。易错点:调用方法的问号点在括号前:obj.method?.();一旦短路,后续不再执行,并以 undefined 作为整个表达式结果;可选链没有副作用。
空值判断运算符
为什么需要:|| 会把 0、false、空字符串等假值也当成“没有值”,而 ?? 只判断 null 和 undefined。怎么想:左操作数是 null 或 undefined 才取右边,否则取左边。怎么做:100 ?? 'hello' 得 100;0 ?? 100 得 0;false ?? 100 得 false;null ?? 100 得 100;undefined ?? 100 得 100。易错点:它不做类型转换,只看 null/undefined;与逻辑或相似但取值规则不同;没有副作用。
逻辑赋值运算符
为什么需要:把逻辑判断和赋值合并。怎么想:x &&= y 等价于 x = x && y;x ||= y 等价于 x = x || y;x ??= y 等价于 x = x ?? y。怎么做:a &&= 250、a ||= 250、a ??= 250。调试过程:a1 = 10; a1 &&= 250 后 a1 为 250;a2 = 20; a2 ||= 250 后仍为 20;a3 = 30; a3 ??= 250 后仍为 30。易错点:左操作数必须是变量形式,不能写 100 &&= 200;它有副作用;表达式值是左操作数重新赋值后的值。
小结
这四块内容细碎但常用:数值看表示与判断,函数看默认值、rest、箭头函数,数组和对象看扩展运算符与解构,新增运算符看短路、空值判断和赋值副作用。复习时把“语法结构”和“数据类型”分清,把 this、super、arguments、rest 的区别记牢。
新增运算符总览
本节把 ES6 新增的几个运算符放在一起对比。先看总表,重点记住三件事:操作数个数、表达式值的类型、有没有副作用。
| 运算符 | 含义 | 操作数个数 | 操作数类型要求 | 表达式值的类型 | 有无副作用 |
|---|---|---|---|---|---|
** | 指数运算符 | 2 | number | number | 无 |
?. | 可选链运算符 | 2 | 无 | undefined 或其他 | 无 |
?? | 空值判断运算符 | 2 | 无 | 多种 | 无 |
&&= | 逻辑与赋值 | 2 | 左边是变量形式 | 左边重新赋值后的值 | 有 |
||= | 逻辑或赋值 | 2 | 左边是变量形式 | 左边重新赋值后的值 | 有 |
??= | 空值判断赋值 | 2 | 左边是变量形式 | 左边重新赋值后的值 | 有 |
指数运算符 **
为什么需要: 过去求次方要用 Math.pow,写起来多一层函数调用。** 让“求次方”像加减乘除一样直接。
怎么想: a ** b 就是 a 的 b 次方,操作数都是 number,结果也是 number,没有副作用。
怎么做:
js
console.log(10 ** 2); // 100
console.log(2 ** 10); // 1024
console.log(Math.pow(2, 10)); // 1024易错点: 不要把 ** 当成普通乘法;它只负责求次方。转写稿里说“十的二次方、二的十次方”,本质就是左操作数的右操作数次方。
可选链运算符 ?.
为什么需要: 链式读取对象属性时,中间某一层可能是 undefined。例如先得到 user.parent 为 undefined,再继续 .address 就会报错。可选链的作用是:一旦遇到 null 或 undefined 就短路,不再往后执行。
怎么想: 转写稿里特别强调不要硬猜,要分析:user.parent 先得到 undefined,undefined.address 不能调用,所以会报错;写成 user.parent?.address 后,发现 parent 是 undefined,直接以 undefined 作为整个表达式结果。
怎么做:
js
const user = {
address: '上海',
parent: { name: 'A', age: 2, address: '北京' },
say() { return 'say'; }
};
console.log(user.address); // 上海
console.log(user.parent.address); // 北京
console.log(user.parent?.address); // 北京
console.log(user.missing?.address); // undefined,不报错
console.log(user.say?.()); // say
console.log(user.it?.()); // undefined,不报错调用方法时,问号点要写在“方法名和小括号之间”:user.say?.()。如果直接写 user.it(),会报“不是函数”。
易错点: ?. 只处理 null/undefined 造成的短路,不是万能容错;方法调用是 obj.method?.(),不是 obj?.method() 的随意替换;链式调用中一旦短路,后面的属性或调用都不再执行。
空值判断运算符 ??
为什么需要: 逻辑或 || 会把 0、false、空字符串都当成假值,从而取第二个操作数。但有些场景只想在 null 或 undefined 时兜底,0 和 false 应该保留。
怎么想: ?? 很像 ||,但判断规则更窄:左边是 null 或 undefined 才取右边,否则取左边。
怎么做:
js
console.log(100 ?? 'hello'); // 100
console.log(0 ?? 100); // 0
console.log(false ?? 100); // false
console.log(null ?? 100); // 100
console.log(undefined ?? 100); // 100易错点: 不要把 ?? 和 || 混用。0 || 100 得 100,0 ?? 100 得 0;false || 100 得 100,false ?? 100 得 false。?? 没有副作用,也不会做真值判断。
逻辑赋值运算符 &&=、||=、??=
为什么需要: 它们把“判断 + 重新赋值”合成一步,减少 x = x && y 这类重复写法。
怎么想: x &&= y 等价于 x = x && y;x ||= y 等价于 x = x || y;x ??= y 等价于 x = x ?? y。因为要重新赋值,所以左边必须是变量形式,并且有副作用。
怎么做:
js
let a1 = 10, a2 = 20, a3 = 30;
a1 &&= 250; // a1 = a1 && 250,a1 变为 250
a2 ||= 250; // a2 = a2 || 250,a2 仍为 20
a3 ??= 250; // a3 = a3 ?? 250,a3 仍为 30
console.log(a1, a2, a3); // 250 20 30
let b = null;
b ??= 100; // b 变为 100易错点: 左边不能写直接量,例如 100 &&= 200 不合法;用 const 声明的变量不能重新赋值,所以也不能用这些赋值运算符;&&= 在左边为假时会短路,右侧不执行;??= 只在左边是 null/undefined 时赋值。表达式值通常是“左边重新赋值后的值”,但实际开发中更常关注左边变量最终变成了什么。
课堂画面






代码示例
示例一:指数运算符 ** 与可选链运算符 ?.
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>指数运算符 与 可选链运算符</title>
</head>
<body>
<script>
/* ========== 1. 指数运算符 ** ========== */
console.log(10 ** 2); // 100,10 的 2 次方
console.log(2 ** 10); // 1024,2 的 10 次方
console.log(89 ** 3); // 704969
console.log(Math.pow(10, 2));// 100,以前只能这样求次方
/* ========== 2. 可选链运算符 ?. ========== */
const user = {
address: '上海',
parent: {
a: 2,
address: '北京'
},
say() {
return 'say';
}
};
// 正常的点调用
console.log(user.address); // 上海
console.log(user.parent.address); // 北京
// 读取不存在的属性:直接点会报错
// console.log(user.parent2.address); // TypeError: Cannot read properties of undefined
// 换成可选链:不报错,得到 undefined
console.log(user.parent2?.address); // undefined
console.log(user?.address); // 上海
console.log(user?.parent?.address); // 北京
console.log(user?.parent2?.address);// undefined(短路,后面不再执行)
// 调用方法
console.log(user.say()); // say
// console.log(user.say2()); // TypeError: user.say2 is not a function
console.log(user.say2?.()); // undefined,不报错
console.log(user?.say?.()); // say
</script>
</body>
</html>要点解释
10 ** 2就是 10 的 2 次方,等价于Math.pow(10, 2);两个操作数都必须是 number,表达式值也是 number,无副作用。user.parent2?.address的执行顺序:先取user.parent2得到undefined,?.发现是null/undefined就短路,不再往后读address,直接把undefined作为整个表达式的值。这就是它叫“短路运算符”的原因。- 易错点①:
user.parent2.address这种中间环节为undefined的链式调用,一定会报Cannot read properties of undefined,这正是可选链要解决的问题;以前只能一层层if判断。 - 易错点②:调用方法时问号点要写在方法名和小括号之间:
user.say2?.(),而不是user.say2()。前者方法不存在返回undefined,后者直接报is not a function。 - 易错点③:
user?.say?.()与user.say2?.()是两种不同位置的判断——前者判断user在不在,后者判断方法在不在,可组合使用。
示例二:空值判断运算符 ?? 与逻辑或 || 的对比
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>空值判断运算符 ??</title>
</head>
<body>
<script>
/* ========== 1. 逻辑或 ||:第一个操作数“不成立”就取第二个 ========== */
console.log(100 || 'hello'); // 100
console.log(0 || 'hello'); // hello(0 转成 false)
console.log(false || 'hello'); // hello(false 转成 false)
console.log('' || 'hello'); // hello(空串转成 false)
console.log(null || 'hello'); // hello
console.log(undefined || 'hello'); // hello
console.log(NaN || 'hello'); // hello
/* ========== 2. 空值判断 ??:只有 null / undefined 才取第二个 ========== */
console.log(100 ?? 'hello'); // 100
console.log(0 ?? 'hello'); // 0 ← 与 || 不同
console.log(false ?? 'hello'); // false ← 与 || 不同
console.log('' ?? 'hello'); // '' ← 与 || 不同
console.log(NaN ?? 'hello'); // NaN ← 与 || 不同
console.log(null ?? 'hello'); // hello
console.log(undefined ?? 'hello'); // hello
/* ========== 3. 第二个操作数会不会被执行 ========== */
function test() {
console.log('第二个操作数被执行了');
return 'hello';
}
console.log(100 ?? test()); // 100,test() 不执行(短路)
console.log(null ?? test()); // 执行 test(),输出 hello
</script>
</body>
</html>要点解释
??也是一种逻辑运算符,跟||是一类的,取值规则非常像:第一个操作数“不行”就取第二个。- 关键区别:
||会先把第一个操作数隐式转成布尔值,所以0、false、''、NaN都算“不成立”;而??不做类型转换,只看第一个操作数是不是null或undefined,是才取第二个。 - 易错点①:想用
??给“可能是 0 或空串”的变量兜底,千万别写成||,否则0、''会被误替换成默认值。 - 易错点②:
??有短路特性,第一个操作数不是null/undefined时,第二个操作数根本不会执行,所以不要把有副作用的代码放在右边还指望它一定运行。 - 注意:
??不能和||、&&不加括号混用(如a ?? b || c会报语法错误),需要加括号明确优先级。
示例三:逻辑赋值运算符 &&=、||=、??=
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>逻辑赋值运算符</title>
</head>
<body>
<script>
let a1 = 10;
let a2 = 20;
let a3 = 30;
/* ========== 1. 逻辑与赋值 &&= ========== */
// 等价于 a1 = a1 && 250
a1 &&= 250;
console.log(a1); // 250(10 成立,取第二个操作数)
/* ========== 2. 逻辑或赋值 ||= ========== */
// 等价于 a2 = a2 || 250
a2 ||= 250;
console.log(a2); // 20(20 成立,取第一个,赋值等于没变)
/* ========== 3. 空值判断赋值 ??= ========== */
// 等价于 a3 = a3 ?? 250
a3 ??= 250;
console.log(a3); // 30(不是 null/undefined,保持原值)
let a4 = null;
a4 ??= 250;
console.log(a4); // 250(是 null,取第二个)
let a5 = 0;
a5 ??= 250;
console.log(a5); // 0(0 不是 null/undefined,保持原值)
/* ========== 4. 表达式的值 = 左边变量重新赋值之后的值 ========== */
let a6 = 10;
console.log(a6 ||= 250); // 10,表达式的值就是赋值后 a6 的值
/* ========== 易错演示 ========== */
// 错误1:左边必须是变量,不能是直接量
// 100 &&= 200; // SyntaxError: Invalid left-hand side in assignment
// 错误2:const 声明的常量不能重新赋值
// const c = 10;
// c &&= 250; // TypeError: Assignment to constant variable.
</script>
</body>
</html>要点解释
- 三个运算符都是“赋值运算符 + 逻辑判断”的合体,写法跟
+=、-=是一个思路,本质是x = x && y、x = x || y、x = x ?? y的简写。 - 关键行:
a1 &&= 250中a1是 10(成立),逻辑与取第二个操作数,于是a1变成 250;a2 ||= 250中a2是 20(成立),逻辑或取第一个,值保持 20。 a3 ??= 250与a5 ??= 250对照看:30、0都不是null/undefined,所以都不会被覆盖——这正是??=和||=的核心差别(0用||=会被覆盖)。- 易错点①:这三个运算符有副作用,副作用只作用于左边,所以左边必须是变量的形式。写
100 &&= 200会直接语法报错,因为不能给直接量赋值(和++/--的规则一样)。 - 易错点②:用
const声明的变量不能参与这三种赋值,会抛Assignment to constant variable。 - 易错点③:整个表达式的值就是“左边操作数重新赋值之后的值”,但在实际开发中通常只关心变量本身的值,不去看表达式的值。
常见错误
默认值用了
||,0 和空字符串被吞掉- 现象:
opts.timeout || 3000在传入0时仍然得到 3000;换成??后又发现''不再被兜底。 - 原因:
||判断的是“假值”(含0、''、false、NaN),??判断的是“空值”(仅null/undefined),两者判定标准不同。 - 修法:先用一句话明确业务语义——是“没传”还是“传了假值”——再决定用
??还是||;同一项目里的默认值统一用同一种,别混着写。
- 现象:
?.只写一级,后面继续点- 现象:
user?.address.city在user存在但address为undefined时照样报错。 - 原因:
?.只对紧跟它的那一次访问短路,不保护整条链。 - 修法:链上每一级都要加
user?.address?.city;末尾再接?? '默认值',避免把undefined继续往下传。
- 现象:
方法调用的
?.位置写错- 现象:
obj?.get()在get不存在时报is not a function。 - 原因:
?.加在对象上只判断对象,加在小括号前才判断方法本身。 - 修法:只保护方法写
obj.get?.();对象和方法都要保护写obj?.get?.()。
- 现象:
把
&&=/||=/??=的左边写成表达式- 现象:
100 &&= 200、arr[i++] ||= x报错或行为不可控。 - 原因:这三个运算符有副作用,左侧必须是可赋值目标(变量或属性)。
- 修法:左侧只放变量或对象属性;需要先算下标/表达式的,先算出来存进变量再赋值。
- 现象:
**和一元负号直接连用- 现象:
-2 ** 2直接SyntaxError。 - 原因:一元运算符不能作为
**的左操作数。 - 修法:写
(-2) ** 2或-(2 ** 2),并想清楚要的是哪个结果。
- 现象:
以为
a ||= b就是“先算再赋”的普通写法- 现象:讨论“会不会多赋值一次”时说不清,或在左侧塞了有副作用的表达式。
- 原因:真实执行是短路的,只有左值不满足条件时才发生赋值。
- 修法:按“不满足条件才赋值”理解即可,结果与
a = a || b一致;但不要在左侧放有副作用的代码。
练习
默认值改造:给出一段用
||写默认值的配置合并函数,全部改成??,并列出在0、''、false、NaN、null五种输入下两者结果不同的地方。深层取值:写函数安全取出
data.user.profile.settings.theme。先用旧写法(层层if或三元)实现,再用?.+??实现,比较行数、可读性和异常情况下的返回值。方法调用保护:给定
const api = { get(){ return 1 } },分别写出api.get?.()、api?.get?.()、api?.post?.(),说明每条的返回值,以及什么情况下会得到undefined而不是报错。逻辑赋值重构:把下面三段改写成一行逻辑赋值,并说明各自为什么选这个运算符:
if (opts.timeout == null) opts.timeout = 3000if (!list.length) list = defaultListif (count) count = count + 1
优先级与合法性判断:判断下列写法是否合法、结果是什么,不合法的给出等价写法:
-2 ** 2、2 ** 3 ** 2、a ?? b || c、obj?.a.b。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 回顾 | 15:25 |
| 2 | 新增运算符-指数运算符 | 01:42 |
| 3 | 新增运算符-可选链运算符 | 13:38 |
| 4 | 新增运算符-空值判断运算符 | 06:17 |
| 5 | 新增运算符-逻辑赋值运算符 | 09:37 |
| 6 | 新增运算符-总结 | 03:20 |
小结
- 这批运算符的共同目标是消掉“层层判断 + 手动赋值”的样板代码,本质是语法糖,不改变原有运算规则。
- 按副作用一刀切:
**、?.、??无副作用,可以随意组合在读取侧;&&=、||=、??=有副作用,只出现在写入侧。 ??与||的分水岭是“空”和“假”,这是默认值场景最容易出错的地方,选之前先问业务要哪一种。?.只保护紧跟的那一次访问,链式调用必须逐级加;短路后整条表达式返回undefined,不是null,也不是报错。?.的三种形态位置固定:obj?.prop、obj?.[expr]、obj.method?.(),位置错一位就保护不到。- 三个逻辑赋值运算符是短路的,只在条件不满足时才真正赋值;左侧必须是变量或属性,且不要写有副作用的表达式。
**是这批里唯一对操作数类型有硬要求的运算符,且不能直接接一元负号。- 读别人代码时先找“副作用点”和“短路点”,比背运算符表格更能预判行为。