WebGL Tutorial
and more

操作符

撰写时间:2024-01-27

修订时间:2024-12-03

in

如果属性存在于特定对象及其prototype链中,in操作符返回true

对象

let obj = { name: 'Mike', age: 25 }; console.log('name' in obj); // true console.log('toString' in obj); // true console.log('speed' in obj); // false

这与for let in语句的作用相似,但for let in语句返回的属性名称中不包含不可列举的属性名称。

for (let propName in obj) { console.log(propName); }

结果:

name age

toString方法属于prototype链Object.proptype中的方法,属于不可列举的属性,因此不出现在遍历结果中。详见遍历prototype链中所有属性

数组

在JavaScript中,数组的类型是也object

let arr = ['a', 'b', 'c']; console.log(typeof arr); // object

作为对象,数组元素的索引值就是属性名称。

for (let propName in arr) { console.log(propName); }

显示:

0 1 2

in操作符应用于数组中,经常犯的错误是:

console.log('a' in arr); // false

in操作符只与对象的属性打交道,而数组元素的值不是对象属性名称!

作为对象,arr具有下列属性:

arr { 0: 'a', 1: 'b', 2: 'c', length: 3 }

因此,除了使用数组索引值访问数组元素之外,我们也可以使用数组属性名称来访问数组元素。

console.log(arr[0]); // 使用数组索引值 console.log(arr['0']); // 使用数组对象的属性名称

因此,当in操作符应用于数组时,正确的代码应是:

console.log('2' in arr); // true console.log('3' in arr); // false

上面,字符串2arr的属性名称。而数组更进一步,连索引数值也可作为属性名称使用:

console.log(2 in arr); // true

对于上面的代码,我们可以理解为:数组arr是否有2这个属性名称,或者也可理解为数组arr是否有第2个元素?

而当数组元素类型正好也是整数型时,这很容易带来混乱:

let arr = [10, 20, 30]; console.log(20 in arr); // false console.log(1 in arr); // true

一句话,当in操作符应用于数组时,我们只能使用数值元素索引值或其相应的字符串来进行测试,不能使用数组元素的值来进行测试。

Falsy及Truthy

在需要确定Boolean值的场合下,一个falsy值是指其值为false

以下为falsy:

  • null
  • undefined
  • false
  • NaN
  • 0
  • -0
  • 0n
  • "", '', ``: 空字符串
  • document.all

上面,nullundefined属于nullish空值)。正如上面,nullish值总为falsy

而非falsy值即为truthy

在中文环境下,当谈到布尔值的假值时,我们想到的最多的是false, 0,但除了这两个常见的值之外,在JavaScript中,上面的全都是中文环境下的假值,也即falsy

依空返回操作符 (??)

依空返回操作符Nullish Coalescing Operator??)根据一个变量是否空值而返回不同的值。其语法为:

x ?? y

如果x值为nullundefinded,则返回右边的y的值;否则,返回左边的x的值。相当于:

if (x === null || x === undefined) { return y; } else { return x; }

下面为常见的例子:

let x; // undefined let num = x ?? 50; console.log(num); // 50

意为,如果x为空值(nullish),则num被赋值为50。而如果x的值不为空值,则num被赋值为x的值。

更容易的理解为:我们准备根据x是否空值的情况来赋值。如果x为空值,则赋值一个默认值;否则,取自x的值。

注意与下面的代码区分:

let x = 0; let a = x ?? 50; console.log(a); // 0

x的值为0时,它的值虽然为false,但已经不再是nullish,因此,num被赋值为0

依空赋值操作符 (??=)

上面根据判断一个变量是否为空值来给另一个变量赋值,而如果根据一个变量为否为空值来决定该变量的值时,则可使用更方便的依空赋值操作符Nullish Coalescing Assignment??=)。

let x = undefined; x ??= 50; // 50 let y = 30; y ??= 50; // 30

相当于:

if (x === null || x === undefined) { x = 50; } if (y === null || y === undefined) { y = 50; }

这种情况下,如果变量的值不为空值,则没有任何额外的赋值动作。

可以理解为:拒绝空值(nullish guarding)。如果为空值,则赋于一个默认值。

??=操作符经常用于为类型为对象的函数参数提供全方位的默认值:

listConfig({name: 'Mike'}); function listConfig(options) { options.name ??= 'Tom'; options.age ??= 25; options.gender ??= 'male'; console.log(options); }

listConfig函数希望传入的实参options中包括足够的信息,但又必须让客户端灵活地只挑选其认为最重要的选项,未提供的可取自默认值。

上面客户端的实参只设定了name的值,而在listConfig函数中,数据不全可能造成程序无法运行,故在该函数内部,对每一个必要的选项均使用??=操作符进行了补充性的默认值设置。

结果输出:

{ name: "Mike", age: 25, gender: "male" }

但这种方法有点小问题,如果客户端很懒,其实参为空值呢?

listConfig(); function listConfig(options) { options.name ??= 'Tom'; options.age ??= 25; options.gender ??= 'male'; }

程序将抛出异常,因为参数options为空值,我们不能在空值上引用各个属性名:

options.name ??= 'Tom'; // Error: null.name options.age ??= 25; // Error: null.age options.gender ??= 'male'; // Error: null.gender

解决的方法有3种。第一种是在使用带有默认值的函数参数:

function listConfig(options = {}) { options.name ??= 'Tom'; options.age ??= 25; options.gender ??= 'male'; }

第二种解决的方法是在函数体内的前面添加一个空值判断赋值语句:

function listConfig(options) { options ??= {}; options.name ??= 'Tom'; options.age ??= 25; options.gender ??= 'male'; }

代码:

options ??= {};

先判断参数options是否为空,若为空值,则先为其赋值于一个空对象。下面再进一步为这个空对象设置各个属性值。

第三种解决的方法是使用解构。

listConfig({name: 'Mike'}); function listConfig(options) { const { name = 'Tom', age = 25, gender = 'male' } = options ??= {}; console.log(options); // {name: "Mike"} console.log(name); // "Mike" console.log(age); // 25 console.log(gender); // "male" }

这种代码显得很紧凑、美观。唯一需注意的是,使用解构时,实参options可能不是完整的,但在使用默认值解构过程中,我们得到了所有必要的变量。

可选链操作符

可选链接操作符optional chaining operator?.)可在特定对象存在时才调用其特定方法。

let element = document.querySelector('#test'); element?.addEventListener('click', (evt) => { console.log('Hello'); });

如果页面上不存在element这个对象,则不会调用addEventListener方法。只有在该对象存在时才会调用。其效等于:

let element = document.querySelector('#test'); if (element) { element.addEventListener('click', (evt) => { console.log('Hello'); }); }

展开操作符

展开操作符Spread operator)的作用在于,将一个可遍历对象(iterable object)的各个子元素分别提取为独立的多个个体。这个可遍历对象可为数组,字符串,或Object。简而言之,将一拆分为多。语法为:

(...obj)

数组元素

展开

let arr = [1, 2, 3]; console.log(...arr); // 1 2 3

数组中的3个元素,被提取出为独立的3个整数。

但不能这样使用:

let arr = [1, 2, 3]; let a = ...arr; // Error console.log(a);

...arr是独立的3个整数,不能赋值于单一变量。

函数参数

let arr = [1, 2, 3]; listArgs(...arr); function listArgs(a, b, c) { console.log(a); // 1 console.log(b); // 2 console.log(c); // 3 }

arr展开为3个整数,作为实参依序传递给listArgs函数。

数组字面符

数组字面符支持展开操作符,这使得数组操作更加容易、直观。

let arr1 = [3, 4, 5]; let arr2 = [1, 2, ...arr1, 6, 7]; console.log(arr2); // [1, 2, 3, 4, 5, 6, 7]

数据展开方式是浅复制的方式。

const a = [ [1], [2], [3] ]; const b = [...a]; console.log(b); // [[1], [2], [3]] a[0][0] = 5; console.log(b); // [[5], [2], [3]]

展开a后,b数组中的每个元素都是a数组的子元素的数组对象引用。因此,当a数组的子元素的值发生改变时,也将影响到b数组的值。

对象字面符

对象字面符中可支持展开操作符。

let a = { name: 'Mike', gender: 'male' }; let b = {age: 25}; let c = {...a, ...b}; console.log(c); // {name: 'Mike', gender: 'male', age: 25}

当两个具有相同属性名称的对象合并时,后面对象的属性值将覆盖前面对象的属性值。

let a = {name: 'Mike'}; let b = {name: 'Tom'}; let c = {...a, ...b}; console.log(c); // {name: 'Tom'};

字符串字面符

字符串字面符也支持展开操作符。

let a = "Mike"; let b = "Hello"; let c = {...b, ...a}; console.log(c); // {0: 'M', 1: 'i', 2: 'k', 3: 'e', 4: 'o'}

这个结果为何这么奇怪?

JavaScript引擎在展开字符串时,是将字符串当作对象来展开的。等同于下面代码:

let a = {0: 'M', 1: 'i', 2: 'k', 3: 'e'}; let b = {0: 'H', 1: 'e', 2: 'l', 3: 'l', 4: 'o'}; let c = {...b, ...a}; console.log(c);

根据上节,只要是属性名称相同的值,都会被后面对象的值覆盖。因此就出现了上面的结果。类似于蒙板效果,将a覆盖在b上面,b只有比a长的部分才会露出来。

操作符分类

增加及减少操作符

A++
后缀增加操作符
A--
后缀减少操作符
++A
前缀增加操作符
--A
前缀减少操作符

一元操作符

delete
删除操作符,用以从对象中删除属性
void
计算表达式并废弃其返回结果
typeof
返回特定对象的类型
+
一元加号操作符将操作数转换为Number
-
一元减号操作符将操作数转换为Number型后取反
~
位域取反
!
逻辑取反
await
暂停并恢复一个async函数,并等待Promise的完成或拒绝。

算术操作符

**
*
乘以
/
除以
%
求余
+
-

关系操作符

<
小于
>
大于
<=
小于等于
>=
大于等于
instanceof
返回某个对象是否其他对象的实例
in
对象是否具有特定属性

相等操作符

==
等于
!=
不等于
===
严格等于
!==
严格不等于

位域移动操作符

<<
位左移
>>
位右移
>>>
位无符号右移

二进制位域操作符

&
位与
|
位或
^
位异或

逻辑操作符

&&
||
!
??
依空返回操作符(nullish coalescing operator

三元条件操作符

(condition ? ifTrue: ifFalse)
当前置条件为真时,返回第1个结果;否则,返回第2个结果。

赋值操作符

=
赋值操作符
*=
乘以赋值操作符
/=
除以赋值操作符
%=
求余赋值操作符
+=
相加赋值操作符
-=
相减赋值操作符
<<=
左移赋值操作符
>>=
右移赋值操作符
>>>=
无符号右移赋值操作符
&=
位与赋值操作符
^=
位异或赋值操作符
|=
位或赋值操作符
**=
幂赋值操作符
&&=
与赋值操作符
||=
或赋值操作符
??=
依空赋值操作符(nullish coalescing assignment
[a, b] = arr, {a, b} = obj
解构赋值操作符

Yield操作符

yield
暂停并恢复一个生成器函数
yield*
交由另一个生成器函数或可遍历对象代理

展开操作符

...obj
将一个可遍历的对象,如数组或字符串,拆分为多个独立个体。

逗号操作符

,
逗号操作符允许多个表达式并存于一个语句,且返回最后一个表达式的结果。

可选链操作符

?.
访问对象属性或调用函数。

参考资源

  1. ECMA 262: String Object
  2. MDN: Expressions and operators
  3. MDN: Destructuring assignment