四 运算符

1 算数运算符

https://wangdoc.com/javascript/operators/index.html

1.1 概述

指数:x ** y
余数:x % y
自增:++x x++
自减:x-- --x

1.2 加法运算符

1.2.1 基本规则

JS 允许非数值的相加

true + true // 2
1+ true // 2

如果是两个字符串相加,则会拼接。如果1个是字符串,另外一个是非字符串,则会先把非字符串转化成字符串。

1 + "2" // "12"

加号这种有时表示相加,有时表示拼接的,这种现象叫做重载。

3+4+"5" // "75"

1.2.2 对象的相加

如果运算子是对象,必须先转成原始类型的值,再相加。

let obj = {p:1};
obj + 2;
// "[object object]2"

上面代码中,对象 obj 转成原始类型的值是 [object object],再加2就得到了上面的结果。
对象转成原始类型的值,规则如下。
首先,自动调用对象的 valueOf 方法。

var obj = {p: 1};
obj.valueOf() 
// {p : 1}

一般而言,valueOf() 总是返回对象自身,这时再自动调用对象的 toString 方法,将其转为字符串。

obj.valueOf().toString() 
// "[object object]"

知道这个规则后,就可以自定义 valueOf 方法或toString方法,得到想要的结果。—— 这一点是和Java 相同的,在Java 中调用 equal / toString 方法,很多时候是需要自己重新定义的。

var obj = {
  valueOf : function(){
    return 1;
  }
};
obj + 2 // 3

由于 obj 返回了一个原始类型值的方法,所以不再调用 toString 方法。
自定义 toString 方法。

var obj = {
  toString : ()=>{
    return "hello";
  }
}
obj + 1 // "hello1"

1.3 余数运算符

余数的值是由第一个运算子决定的。

-5 % 2 // -1

也就是说,余数在 JS 可能是负数的。Python 中也会出现负数的:

5 % (-2) // -1

负数的余数,这个是个数学问题,具体怎么实现的,应该按照语言来。
所以为了得到负数的正确余数值,应该先使用绝对值:

function isOdd(n){
  return Math.abs(n % 2) === 1;
}
isOdd(-5) // true

1.4 自增和自减运算符

++放在变量之后,会先返回变量操作前的值,再进行自增运算;
++放在变量之前,会先进行自增运算,然后再返回变量值。

var x = 1;
var y = 1;
console.log(x++); // 1
console.log(++x); // 2

1.5 数值运算符,负数值运算符

数值运算符(+)同样使用加号,但是是一元运算符(只需要一个操作数),而加法运算符是二元运算符(需要两个操作数)。

数值运算符的作用在于可以将任何值转为数值(与 Number函数的作用相同)。

+true // 1
+[] // 1
+{} // NaN

1.6 指数运算符

**

1.7 赋值运算符

就是等号

2 比较元素符

教程:https://wangdoc.com/javascript/operators/comparison.html

2.1 概述

运算符分为两类:相等比较和非相等比较。对于非相等比较,算法是比较两个是否是字符串,如果是,按照字典顺序比较(实际上是 Unicode);否则,会把两个运算子都转成数值,再比较数值的大小。

2.2 非相等运算符:字符串的比较

"cat" > "dog" // false

2.3 非相等运算符:非字符串的比较

如果两个运算子都不是字符串,分为三种情况:
(1) 原始类型值
如果两个值都是原始类型的值,那么先转成数值再比较。

true > false // true
2 > true // true 

(2) 如果运算子是对象,会转为原始类型的值,再比较。
对象转化为原始类型的值,算法会先调用 valueOf 方法,如果返回的还是对象,再调用 toString 方法

var  x = [2];
x > "3" // false;
// 等同于
x.valueOf().toString() > "3"

x.valueOf = ()=>{
  return 100;
}
x > 99; // true

两个对象之间的比较也是如此。

{a:2} >= {b:1} // true
// 等同于
({a:2}).valueOf().toString() >= ({b:3}).valueOf().toString()

(3)复合值类型

2.4 严格相等运算符

相等运算符(==)比较两个值是否相等,严格相等运算符(===)比较它们是否是“同一个值”。
如果两个值不是同一类型的值,严格相等运算符会直接返回 false,而相等运算符会把他们转化为同一个类型在进行比较。

(1) 不同类型的值

如果两个值的类型不同,直接返回 false

(2) 同一类型的原始值

同一类型的原始值,比较值是否相同

(3) 符合类型值

两个复合类型(对象、数组、函数)的数据比较时,不是比较它们的值是否相等,而是比较它们是否指向同一地址。

{} === {} // false
[] === [] // false
(function(){} === function(){}) // false

这几个例子它们都指向不同的地址,所以都是 false。如果两个变量引用同一个对象,则它们相等。

var v1 = {};
var v2 = {};
v1 === v2 // true

对于两个对象的比较,严格相等运算符比较的是地址,而大于或小于运算符比较的值。

var obj1 = {};
var obj2 = {};
obj1 === obj2 // false
obj1 > obj2 // false
obj1 < obj2 // false
obj1 >= obj2 // true
(4) undefined 和 null

undefined 和 null 与自身严格相等。

undefined === undefined; // true
null === null // true

由于变量声明后默认值是 undefined,因此两个只声明未赋值的变量是相等的。

var v1;
var v2;
v1 === v2 // true

2.5 严格不相等运算符

!== 就是严格相等运算符的否定形式。

2.6 相等运算符

相等运算符用来比较相同类型的数据时,与严格相等运算符完全一样。

1.0 == 1
// 等同于
1 === 1.0
(1)原始类型值

原始类型的值会转换成数值再进行比较。

1 == true // true
// 等同于 1 == Number(true)
0 == false // false
// 等同于 0 === Number(false)
'1' == true // true
// 等同于 Number('1') == Number(true)
(2) 对象与原始类型值比较

对象(广义的对象,包括数组和函数)与原始类型的值比较时,对象转换成原始类型的值,再进行比较。

[1] == '1' // true
[1] == true // true
(3) undefinednull

undefinednull 与其他类型的值比较时,结果都为 false, 它们互相比较时结果为 true。

false == null; // false
false == undefined // false
0 == null // false
0 == undefined // false
undefined  == null // true
(4) 相等运算符的缺点

相等运算符隐藏的类型转化,会带来一些反直觉的结果

0 == ''; // true
0 == '0'; // true 
'' == '0'; // false
false == 'false'  // false
false == '0' // true

false == undefined // false 
'\t\r\n' == 0; // true

2.7 不相等运算符

不相等运算符(!=)的算法,先求相等运算符的结果,然后返回相反值。

1 != '1' // false
// 等同于
!(1 == '1')

3 布尔值运算符

https://wangdoc.com/javascript/operators/boolean.html

3.1 概述

3.2 取反运算符(!)

相当于 python 的 not,当前布尔值取反。
对于非布尔值,取反运算符会将其转为布尔值。以下六个值取反后为true,其他的值都为 false:

  • undefined
  • null
  • false
  • 0
  • NaN
  • 空字符串('')
    注意,空对象的布尔值是true
Boolean([]) // true
Boolean({}) // true

如果对一个值连续两次取反,效果跟使用 Boolean 函数一样。

!! x
// 等同于
Boolean(x)

3.3 且运算符(&&)

注意且运算符可以用来进行短路运算,即

let a = 0;
a && b
// 如果a为false,则直接返回,不会对b进行运算。
// 比如上面的 b 没有定义,但是编辑器根本不 care。

3.4 或运算符(||)

或运算也会短路。

3.5 三元条件运算符(? :)

Java 中 和这个一模一样,

condition ? a : b

注意三元表达式是一个表达式,会返回值。这是和 if ... else ... 不同的。

4 二进制运算符

https://wangdoc.com/javascript/operators/bit.html

4.1 概述

  • 二进制或运算符(or):符号为 |,表示若两个二进制都为0,则结果为0,否则为1
  • 二进制与运算符(and):符号为 &,表示如果两个二进制都为1,则结果为1,否则为0.
  • 二进制否运算符(not):符号为~, 表示对一个二进制取反。
  • 异或运算符(xor):符号为 ^,若两个二进制不相同,结果为1,否则为0.
  • 左移运算符(left shift), 符号为 << ,
  • 右移运算符 (right shift), 符号为 >>
  • 带符号位的右移运算符(zero filled right shift),符号为 >>>

这些位运算符直接处理每一个比特位(bit),所以是非常底层的运算,好处是速度快,坏处是不直观。

有一点需要特别注意,位运算符只对整数起作用,如果一个运算子不是整数,会自动转为整数后再执行。另外,虽然在 JS 内部,数值都是以 64 位浮点数的形式存储,但是做位运算的时候,是以 32 位带符号的整数进行运算的,并且返回值也是一个 32 位带符号的整数。

i = i | 0;

上行代码的意思,就是将 i(不管是整数还是小数) 转成 32 位整数。

利用这个特性,就可以写出一个函数,将任意数值转化为 32 位整数。

function toInt32(x){
  return x | 0;
}

上面这个函数将任意值与 0 进行一次或运算,这个位运算会自动将一个值转化为 32 位的整数。

toInt32(1.7) // 1
toInt32(-1) // 1

对于大于 2 的 32 次方的整数,大于 32 位的数位都会被舍弃。

4.2 二进制或运算符

二进制或运算符(|)逐位比较两个运算子,两个二进制位之中只要有一个为1 ,就返回1,否则返回0

0 | 3 // 3

上面的代码中,03 的二进制分别是 0011,所以二进制运算会得到 11(即 3)。—— 这段解释就是为啥二进制或运算能返回整数。概述中的解释,让我误以为只能返回 0 或 1.

4.3 二进制与运算符

二进制与运算符(&)的规则是逐位比较两个运算子,两个二进制之中只要有一个位是0,就返回0,否则返回 1.

0 & 3 // 0

4.4 二进制否运算符

二进制的否运算符(~)将每个二进制位都变为相反值(0 变成1,1 变成0.)。它的返回结果有时比较难理解,因为涉及到计算机内部的数值表示机制。

-3 // -4

3 的 32 位整数形式是 0000000000000000000000011,二进制否运算以后得到 111111111111111111100。由于第一位(第一位是符号位)是1,所以这个数是一个负数。JS 内部采用补码的形式表示负数,即需要将这个数减去 1,再取一次反,然后加上负号,才能得到这个负数对应的 10 进制值。这个数减去 1 等于 111111111111111111011,再取反得到0000000000000000000100,再加上负号就是 -4. 考虑到这样的过程比较麻烦,可以简单记忆,一个数与自身的取反值相加,等于 -1

~ -3 // 2

一个数连续进行两次取反,会得到它自身。

~~ 3 // 3

所有的位运算都只对整数有效。当二进制否运算遇到小数时,会将小数部分舍去,只保留整数部分。所以,对一个小数连续进行两次二进制否运算,能得到取整效果。

~~ 2.9 // 2

二进制否运算取整,是所有取整方法中最快的一种。
对字符串进行二进制否运算,JS 引擎会先调用 Number 函数,将字符串转为数值。

~"1.2" // 1

4.5 异或运算符

异或运算 (^) 在两个二进制位不同时返回1, 相同时返回0:

0 ^ 3 // 3

上面表达式中,0(二进制00)与3(二进制11)进行异或运算,它们每一个二进制位都不同,所以得到11(即3)。

“异或运算”有一个特殊运用,连续对两个数ab 进行三次异或运算,它们每一个二进制都不同,所以得到 113)。

“异或运算” 有一个特殊运用,连续对两个数ab进行三次异或运算,a ^= b; b ^= a; a ^= b;,可以互换它们的值。这意味着,使用“异或运算”可以在不引入临时变量的前提下,互换两个变量的值。

var a  = 10;
var b = 99;
a ^= b, b ^= a, a ^= b;

a // 99
b // 10

这是互换两个变量的值的最快方法。
异或运算也可以用来取整。

12.9 ^ 0 // 12

4.6 左移运算符

左移运算符(<<)表示将一个数的二进制值向左移动指定的位数,尾部补0,乘以 2的指定次方。

// 4 的二进制形式为 100
// 左移一位为 `1000` (即十进制的 8)
// 相当于乘以2的1次方
4 << 1
// 8
-4 << 1
// -8

4.7 右移运算符

右移运算符(>>)表示将一个数的二进制向右移动指定的位数,头部补0,除以2的指定次方。

4 >> 1
// 2
// 因为 4 的二进制是 000000000000000000000100,
// 右移动得到 00000000000000000010,
// 这是十进制的 2
-4 >> 1 
// -2
// -4 的二进制是 1111111111111111111111111100,
// 右移一位,头部补1,得到 111111111111111111111110,
即为十进制的 -2.

右移可以模拟 2 的整除运算。

5 >> 2
// 2
21 >> 2
// 21 / 4 = 5

4.8 带符号位的右移运算符

带符号位的右移运算符(>>>)表示将一个数的二进制形式向右移动,包括符号位也参与移动。所以,该运算总是能得到正值。对于正数,该运算的结果与右移运算符(>>)完全一致,区别在于负数。

4 >>> 1
// 2
-4 >>> 1 
// 2147483646
// 因为 -4 的二进制形式为 111111111111111111111111111100,
// 带符号位的右移一位,得到 011111111111111111111111110,
// 即十进制的 2147483646

这个运算实际上将一个值转为 32 位无符号整数位。
查看一个负整数在计算机内部的存储形式,最快的方法就是使用这个方法。

-1 >>> 0
// 4294967295

4.9 开关作用

位运算符可以用设置对象属性的开关。
假定某个对象有四个开关,每个开关都是一个变量。那么,可以设置一个四位的二进制数,它的每个位对应一个开关。

var FLAG_A = 1; // 0001
var FLAG_B = 2; // 0010
var FLAG_C = 4; // 0100
var FLAG_D = 8; // 1000

上面设置了 A、B C D 四个开关,每个开关分别占有一个二进制位。
然后,就可以用二进制与运算检验,当前设置是否打开了指定开关。

var flags = 5; // 0101
if (flags && FLAG_C){
  ....
}
// 0101 & 0100 => 0100 => true

上面代码检验是否打开了开关 C。如果打开了,返回 true, 反之返回 false。
现在假设需要打开 A、B、C三个开关,我们可以构造一个掩码变量。

var mask = FLAG_A | FLAG_B | FLAG_C;
// 0001 | 0010 | 0100 => 1011

有了掩码,二进制或运算可以确保打开指定的开关。
// 没看明白。。。

5 其他运算符,运算顺序

5.1 void 运算符

void 的作用是执行一个表达式,然后不返回任何值,或者说返回的是undefined

void 0 // undefined
void(0) // undefined

这两种写法都会,但是建议使用带括号的。因为 void 运算符的优先级很高,如不使用括号,容易造成错误。比如,void 4 + 7 实际上是,(void 4) + 7
这个运算符的主要用途是 浏览器的书签工具,以及在超级链接中插入代码防止网页跳转。

<script>
function f(){
  console.log("hello world");
}
</script>
<a href="http://example.com" onclick="f(); return false;">点击</a>

上面的代码,点击链接后,会先执行 onclick 的代码,由于 onclick 返回 false,所以浏览器不会跳转。
void 运算符可以取代上面的写法。

<a href = "javascript: void(f())">文字</a>

下面是一个更实际的例子,用户点击链接提交表单,但是不会产生页面跳转。

<a href="javascript: void(document.form.submit())">
提交
</a>

5.2 逗号运算符

逗号运算符用于对两个表达式求值,并返回后一个表达式的值。

1,2 // 2
var x = 0;
var y = (x++, 10);
x // 1
y // 10

逗号运算符的一个用途是,在返回一个值前,进行一些辅助性的操作。
—— 最典型的是用到 for 循环里

var value = (console.log("HI"), true);
// hi
value // true

5.3 运算顺序

5.3.1 优先级

运算符是有优先级的。

5.3.2 圆括号的作用

圆括号可以用来提升优先级,圆括号的优先级是最高的。
圆括号不是运算符,而是一种语法结构。一共有两种用法:一种是把表达式放在圆括号之中;另外一种是跟在函数后面,作用是调用函数。

5.3.3 左结合与右结合

一般是从左向右运算。
但也有例外,

w  = x = y = z;
q = a ? b : c ? d : e ? f : g;

上面的代码相当于:

w  = (x = (y = z));
q  = a ? b : (c ? d : (e ? f : g));
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,881评论 4 368
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,052评论 1 301
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,598评论 0 250
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,407评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,823评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,872评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,037评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,778评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,505评论 1 247
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,745评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,233评论 1 264
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,568评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,231评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,141评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,939评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,954评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,784评论 2 275

推荐阅读更多精彩内容

  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,059评论 0 13
  • 运算符是处理数据的基本方法,用来从现有的值得到新的值。JavaScript 提供了多种运算符,本章逐一介绍这些运算...
    许先生__阅读 567评论 0 3
  • 运算符是处理数据的基本方法,用来从现有的值得到新的值。JavaScript 提供了多种运算符,本章逐一介绍这些运算...
    徵羽kid阅读 625评论 0 0
  • 本章将会介绍 模块和源文件访问级别访问控制语法自定义类型子类常量、变量、属性、下标构造器协议扩展泛型类型别名位运算...
    寒桥阅读 844评论 0 2
  • “失恋了怎么办?再去爱一个人,如果还是不行就再换一个,世上总有一个不会让你哭的人。” 毕业那一年,她为了照顾行动不...
    我愛一個幼稚鬼阅读 240评论 0 1