Skip to content

运算符相关

in

prop in object如果指定的属性prop在指定的对象object或其原型链中,则 in 运算符返回 true,否则返回false。对于prop,非 symbol 类型且非string类型将会强制转为字符串再判断。对于object,必须是对象。如果对象object上的属性prop被delete运算符删除,则in返回false,如果只是将属性的值赋值为undefined,则in仍返回true。

delete

delete object.prop或delete object['prop']delete 会从某个对象上object移除指定自身属性prop,成功删除的时候会返回 true,否则返回 false。delete操作符与直接释放内存无关。

  1. 如果属性prop不存在于object自身上,delete将不起作用,但仍然返回true。
  2. 任何使用 var 声明的属性或函数不能从全局作用域或函数的作用域中删除,并且返回false,在严格模式下直接删除变量标识符会抛出SyntaxError错误。
  3. 任何用let或const声明的属性以及函数参数不能够从它被声明的作用域中删除,并且返回false,在严格模式下直接删除变量标识符会抛出SyntaxError错误。
  4. 不可配置的属性不能被删除(var、let、const声明的属性不能删除也是因为它们是不可配置的),并且返回false,在严格模式下会抛出TypeError错误。
  5. 使用delete删除一个数组元素时,数组的长度不受影响。

void

void expression 对给定的表达式expression进行求值,然后返回 undefined 原始值

  1. 在使用IIFE(立即调用函数表达式)时,如果function 关键字直接位于语句开头会导致该表达式被解析为函数声明,如果是匿名函数,就会立即产生SyntaxError错误;如果不是匿名函数,则会在解析到代表调用的括号时产生SyntaxError错误。使用void或一对圆括号均能使得IIFE被解析为表达式,区别在于void返回值为undefined,圆括号为表达式的值。
  2. 以 javascript: 开头的 URI 被点击时,它会执行 URI 中 javascript: 后面的代码,然后用返回的值替换页面内容,除非返回的值是 undefined,比如javascript:void。但是不推荐利用 javascript: 伪协议来执行 JavaScript 代码,而是应该为链接元素绑定事件。
  3. 在箭头函数中,当函数返回值不会被用到的时候,应该使用 void 运算符,来确保 API 改变时,并不会改变箭头函数的行为。

自增&自减

自增或自减运算符只能应用于引用的操作数(变量和对象属性),自增或自减运算符的计算结果是一个值,而不是对一个值的引用,因此不能将多个自增或自减运算符直接链接在一起。

x++或++x,自增(++)运算符对其操作数进行自增(加一),如果使用后缀式(x++),操作数会加一,然后返回加一之前的值。如果使用前缀式(++x),操作数会加一,然后返回加一之后的值。

x--或--x,自减(--)运算符对其操作数进行自减(减一),如果使用后缀式(x--),操作数会减一,然后返回减一之前的值。如果使用前缀式(--x),操作数会减一,然后返回减一之后的值。

位运算符

按位或(|)运算符在其中一个或两个操作数对应的二进制位为 1 时,该位的结果值为 1。操作数被转换为 32 位整数(如果转换结果是NaN或Infinity或-Infinity,则操作数为0)并由一系列二进制位(0 和 1)表示,超过 32 位的数字会丢弃其最高有效位,而且小数部分直接丢弃。

按位异或(^)运算符在两个操作数有且仅有一个对应的二进制位为 1 时,该位的结果值为 1。如同其他位运算符一样,它将操作数转化为 32 位的有符号整型。判定两个数符号是否相同:a ^ b > = 0;交换变量a,b的值

  1. a = a ^ b; a = a ^ b; a = a ^ b;
  2. [a, b] = [b, a];
  3. a = a + b; b = a - b; a = a - b;
  4. temp = a; a = b; b = temp;

按位与(&)运算符在两个操作数对应的二次位都1为时,该位的结果才为1。如同其他位运算符一样,它将操作数转化为 32 位的有符号整型。

按位非(~)运算符将操作数的位反转。如同其他位运算符一样,它将操作数转化为 32 位的有符号整型。32 位有符号整数操作数根据补码运算规则进行反转,也就是说,最高有效位表示负数。按位非运算时,任何数字 x 的运算结果都是 -(x + 1),因此,连续两个按位非可以对x快速取整:~~x = -(-x-1) -1 = x。请注意,由于数字 ~-1 和 ~4294967295(232 - 1)均使用 32 位表示形式,它们的运算结果均为 0。

左移操作符 (<<) 将第一个操作数向左移动指定位数,左边超出的位数将会被清除,右边将会补零。移动任意数字 x 至左边 y 位,得出 x * 2 ** y。

右移操作符 (>>) 将一个操作数的二进制表示形式(运算符以二进制补码的形式对左操作数进行运算)向右移动指定位数,该操作数可以是数值或者 BigInt 类型(BigInt 在使用右移时不会产生截断现象)。右边移出位被丢弃,左边移出的空位补符号位(最左边那位)。该操作也称为“符号位传播右移”(sign-propagating right shift)或“算术右移”(arithmetic right shift),因为返回值的符号位与第一个操作数的符号位相同。>> 运算符针对这两种操作数的类型进行了重载:数值和 BigInt。对于数值,该运算符返回一个 32 位整数;对于 BigInt 类型,该运算符返回一个 BigInt。右移运算符首先将两个操作数强制转换为数值并测试它们的类型。如果两个操作数都转换成 BigInt,则执行 BigInt 右移;否则,它将两个操作数都转换为 32 位整数并执行数值右移。如果一个操作数变为 BigInt 而另一个变为数值,则会抛出 TypeError。虽然将任何数字 x 右移 0 可以将 x 转换为 32 位整数,但不要使用 >> 0 将数字截断为整数,而是使用 Math.trunc() 代替。

无符号右移运算符(>>>)(零填充右移)将左操作数计算为无符号数,并将该数字的二进制表示形式移位为右操作数指定的位数,取模 32。向右移动的多余位将被丢弃,零位从左移入。其符号位变为 0,因此结果始终为非负数。对于非Number类型会先转换为number再进行无符号右移,其中NaN >>> 0 === 0。

逗号运算符

expr1, expr2, expr3/* , … */,逗号运算符对它的每个操作数或表达式从左到右求值,并返回最后一个操作数或表达式的值。

扩展运算符

展开语法(...), 可以在函数调用/数组构造时,将可迭代对象比如数组表达式或者string在语法层面展开(myFunction(...iterableObj)与[...iterableObj, '4', ...'hello', 6]);还可以在构造字面量对象时,将对象表达式按 key-value 的方式展开({ ...obj })。展开语法和 Object.assign一样执行的都是浅拷贝,区别在于Object.assign() 函数会触发 setters,而展开语法则不会。展开语法和剩余参数的区别在于展开语法将数组展开为其中的各个元素,而剩余语法则是将多个元素收集起来并“凝聚”为单个元素。

解构赋值

解构赋值语法是一种 Javascript 表达式。可以将数组中的值或对象的属性取出,赋值给其他变量。每个解构属性都可以有一个默认值,当且属性不存在或值为 undefined 时,将使用默认值,默认值是惰性的,可以是任何表达式,仅在需要使用时才计算。在从赋值语句右侧指定的长度为 N 的数组解构的数组中,如果赋值语句左侧指定的变量数量大于 N,则只有前 N 个变量被赋值。其余变量的值将是 undefined。

对于对象和数组的解构,有两种解构模式:绑定模式和赋值模式

在绑定模式中,模式以声明关键字(var、let 或 const)开始。然后,每个单独的属性必须绑定到一个变量或进一步解构,所有变量共享相同的声明。

在赋值模式中,模式不以关键字(var、let 或 const)开头。每个解构属性都被赋值给一个赋值目标(指出现在赋值表达式左侧的东西)——这个赋值目标可以事先用 var 或 let 声明,也可以是另一个对象的属性。当使用对象字面量解构赋值而不带声明时,在赋值语句周围必须添加圆括号运算符。如果编码风格是不包括尾随分号,则圆括号运算符表达式前面需要有一个分号,否则它可能用于执行前一行的函数。

可以使用剩余属性(...rest)结束解构模式,此模式会将对象(或数组)的所有剩余属性存储到新的对象rest(或数组rest)中,剩余属性必须是模式中的最后一个,并且不能有尾随逗号。

可以在一个解构表达式中交换两个变量的值。没有解构赋值的情况下,交换两个变量需要一个临时变量。

数组解构时可以通过使用多余的逗号忽略部分值。数组解构赋值的剩余属性可以是另一个数组或对象绑定模式,这些绑定模式甚至可以嵌套,相反对象解构赋值只能有一个标识符作为剩余属性。

数组解构调用右侧的迭代协议(可迭代协议和迭代器协议)。因此,任何可迭代对象(不一定是数组)都可以解构,但不可迭代对象不能解构为数组。

至于对象赋值,解构语法允许新变量具有与原始属性相同或不同的名称,这尤其在被解构属性是不是有效的 JavaScript 标识符时很有用,并为原始对象未定义属性的情况分配默认值(对象解构const { p: foo = pDefault } = o,可以从对象o中提取属性p,并将其赋值给名称与对象属性不同的变量foo,指定一个默认值pDefault,以防获取的值为 undefined)。对象字面量中的计算属性名key,如const { [key]: a } = obj,可以被解构。传递给函数参数的对象也可以提取到变量中,然后可以在函数体内访问这些变量。

对象解构几乎等同于属性访问。这意味着,如果尝试解构基本类型的值,该值将被包装到相应的包装器对象中,并且在包装器对象上访问该属性。与访问属性相同,解构 null 或 undefined 会抛出 TypeError错误,即使左侧是空模式(比如const {} = null)。而且,当解构一个对象时,如果属性本身没有被访问,它将沿着原型链继续查找。

for...in 和 for...of 循环中的循环变量、函数参数、catch 绑定变量等语法中也可以使用解构模式。

利用数组解构交换两个变量的值

逻辑运算符

逻辑或运算符(“OR”即 || )、逻辑与(“AND”即 &&)运算符、空值合并运算符(??)、可选链(?.)运算符和条件(三元)运算符均是“短路的”,短路是指后续表达式或操作数不计算或不执行。

逻辑或运算符(expr1 || expr2),如果 expr1 可以转换为 true,则返回 expr1;否则,返回 expr2,而且逻辑或运算符被truthy值短路。要将其返回值显式转换为相应的布尔值,需要使用双 NOT 运算符(!!)或Boolean构造函数。逻辑与运算符的优先级高于逻辑或。bCondition1 && bCondition2等同于!(!bCondition1 || !bCondition2);bCondition1 || bCondition2等同于!(!bCondition1 && !bCondition2);bCondition1 && (bCondition2 || bCondition3)等同于bCondition1 && (bCondition2 || bCondition3)。

逻辑与(expr1 && expr2),如果 expr1 可以转换为真,则返回 expr2;否则,返回 expr1,而且逻辑与运算符被falsy值短路。由于逻辑与运算符的优先级高于逻辑或,因此bCondition1 || (bCondition2 && bCondition3)等同于bCondition1 || bCondition2 && bCondition3。

空值合并运算符(leftExpr ?? rightExpr),当左侧的操作数为 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数,而且空值合并运算符在左侧操作数不是 null 或者 undefined时短路。与逻辑或运算符的区别在于,逻辑或运算符会在左侧操作数为falsy值时返回右侧操作数。如果不使用圆括号运算符明确优先级,空值合并运算符不能与逻辑与运算符、逻辑或运算符一起使用,否则会抛出 SyntaxError异常,原因可能是空值合并运算符和其他逻辑运算符之间的运算优先级/运算顺序是未定义的。

可选链(obj.val?.prop、obj.val?.[expr]、obj.func?.(args))运算符,允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用(obj.val、obj.val、obj.func)是否有效,具体来说是可选链左侧的引用为空(null或undefined)的情况下不会引起错误,表达式短路计算并返回undefined。因此,对于函数调用,如果左侧的引用(obj.func)存在但不是一个函数,则仍会抛出TypeError异常(is not a function)。可选链运算符不能用于赋值。空值合并运算符可以在可选链表达式返回undefined时设置默认值。

条件(三元)运算符,condition ? exprIfTrue : exprIfFalse是 JavaScript 唯一使用三个操作数的运算符,如果条件condition为真值,则执行 exprIfTrue表达式;若条件为假值,则执行exprIfFalse表达式。条件运算符的结果是执行对应表达式的结果。条件运算符可以形成条件运算符链:

算数运算符

x + y,加法(+)运算符计算数字操作数或字符串连接的总和。在求值时,它首先将两个操作数强制转换为基本类型。然后,如果有一方是字符串,另一方则会被转换为字符串,并且它们连接起来;如果双方都是 BigInt,则执行 BigInt 加法。如果一方是 BigInt 而另一方不是,会抛出 TypeError;否则,双方都会被转换为数字,执行数字加法。加法强制将表达式转为基本类型,如果表达式有 @@toPrimitive 方法,字符串连接时会用 "default" 作为 hint 调用它,如果没有@@toPrimitive则优先调用 valueOf();模板字符串和 String.prototype.concat() 则强制将表达式转为字符串,如果表达式有 @@toPrimitive 方法,模板字符串则用 "string"作为 hint 调用它,如果没有 @@toPrimitive 方法,优先调用 toString()。。建议不要使用 "" + x 来执行字符串强制转换。

x - y,减法(-)运算符将两个操作数相减,并产生两者之差。减法运算符将两个操作数转换为数值,并根据两个操作数的类型执行数字减法或 BigInt 减法。不能在减法中混合使用 BigInt 和数字操作数,否则抛出 TypeError。

x / y,除法(/)运算符计算两个操作数的商,其中左操作数是被除数,右操作数是除数。

x * y, 乘法(*)运算符计算操作数的乘积。

x % y,取余(%)运算符返回左侧操作数除以右侧操作数的余数。它总是与被除数的符号保持一致。如果其中的任意一个操作数为 NaN,或 n 为正负无穷(±Infinity),又或者 d 为 ±0,则该运算返回 NaN。否则,如果 d 为正负无穷(±Infinity),或 n 为 ±0,则返回被除数 n。在JavaScript 中没有取模运算符,取余运算与取模运算的区别在于,对于不同号的两个操作数,取模运算结果的符号和总是与除数同号:

  1. 取余计算公式:x % y = x - y * p,其中p是 x / y 的整数部分。
  2. 取模:x mod y = x - y * p,其中p是 x / y 的向下取整,即p = Math.floor(x/y)。

x ** y,幂(**)运算符返回第一个操作数取第二个操作数的幂的结果。它等价于 Math.pow(),不同之处在于,它还接受 BigInt 作为操作数。在JavaScript中,不能将一元运算符(+/-/~/!/delete/void/typeof)放在底数之前,否则会导致语法错误。NaN ** 0(或Math.pow(NaN, 0))是 NaN 不通过数学运算传播的唯一情况——尽管操作数是 NaN,但它返回 1。此外,base 为 1 且 exponent 为非有限(±Infinity 或 NaN)的行为与 IEEE 754 指定结果应为 1,而 JavaScript 返回 NaN 以保持与其原始行为的向后兼容性。

一元加(+)/减(-)运算符

-x,一元减(-)运算符在其操作数之前,并对其正负性取反。如果操作数不是数字会先将其转换为数字。

+x,一元加(+)运算符(和Number(x)效果同)在其操作数之前。如果操作数不是数字会先将其转换为数字,如果无法转换操作数,它将计算为 NaN。对 BigInt 值使用该运算符会引发 TypeError。

赋值运算符(=)

简单赋值运算符(=)用于给变量赋值,运算顺序是从右到左。赋值表达式本身的值为要赋值的值。因此,给多个变量赋相同值可以链式使用赋值运算符(比如x=y=z=2)。

比较运算符

小于(<)运算符在左操作数比右操作数小时返回 true,否则返回 false。

大于运算符(>)在左操作数大于右操作数时返回 true,否则返回 false。

小于等于运算符(<=)在左操作数小于等于右操作数时返回 true,否则返回 false。

大于等于运算符(>=)在左操作数大于等于右操作数时返回 true,否则返回 false。

比较运算符(<, > , <=, >=)经过多轮强制比较它的操作数(操作数可以是数字,字符串,逻辑,对象值)并返回一个基于表达式是否为真的布尔值。强制比较总结如下:

  1. 首先,通过依次调用其 @@toPrimitive(以 "number" 作为提示)、valueOf() 和 toString() 方法,将对象转换为原始类型。左边的操作数总是在右边的操作数之前被强制转换。请注意,尽管 @@toPrimitive 被调用时带有 "number" 的提示(意味着有一点倾向于将对象变成数字),但返回值并没有转换为数字,因为字符串仍然被特别处理”
  2. 如果两个值都是字符串,则根据它们所包含的 Unicode 码位的值,将它们作为字符串进行比较。
  3. 否则,JavaScript 会尝试将非数值类型转化为数值类型:
    1. 布尔值 true 和 false 分别转化为 1 和 0。
    2. null 转化为 0。
    3. undefined 转化为 NaN。
    4. 字符串根据其包含的值进行转换,如果不包含数字值,则转换为 NaN。
  4. 如果任意一个值为 NaN,则运算符返回 false。
  5. 否则,这些值将作为数值进行比较。BigInt 和数值可以一起比较。
  6. 如果其中一个操作数被转换为 BigInt,而另一个被转换为无法转换为 BigInt 值的字符串(当传递给 BigInt()时,它会抛出语法错误)。
  7. 如果其中一个操作数被转化为 NaN(比如不能转化为数字的字符串或 undefined)。

比较运算符(<, > , <=, >=, ===, ==, !==, !=)表达式由于始终返回boolean,因此可以简化以下赋值:

for...in 和 for...of

for...in 语句以任意顺序迭代一个对象的除Symbol以外的可枚举属性,包括继承的可枚举属性。for...of 语句遍历可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象、NodeList(和其他 DOM 集合)、生成器函数生成的生成器,以及用户自己定义的可迭代对象)定义要迭代的数据。

运算符优先级

运算符的优先级决定了表达式中运算执行的先后顺序。优先级高的运算符会作为优先级低的运算符的操作数。