# JavaScript 表达式、操作符、位运算符

TIP

本节课我们来学习表达式和操作符。那什么是表达式和操作符呢?

我们来看一个例子:以下表达式是由两个操作数和一个操作符组合而成

image-20220907210715280

What is ?

  • 操作符: 操作符,也称为运算符,是用于实现赋值、比较值、执行算术运算等功能的符号
  • 表达式: 简单理解为是由数字、操作符、变量等组成的式子,并且这个式子能求得
  • 返回值: 表达式最终都会有一个返回结果,这个结果我们称为返回值

代码演示:

<script>
  console.log(1 + 5); // 1+5 是一个表达式,表达式的返回值是 6
  var a = 1 + 3;
  console.log(a); // 4;
  var num;
  var num1 = 1;
  var num2 = 2;
  num = num1 + num2; // num1+num2 是一个表达式,表达式的返回值赋值给变量num
  console.log(num); // 3
</script>

表达式的分类

  • 在 JS 中表达式的种类非常多,这里我们主要讲解以下 5 种表达式。
  • 每种表达式就有与之相匹配的操作符。

image-20220907215916017

接下来,我们就来学习下这 5 种表达式

# 一、算术表达式

TIP

  • 说到算术表达式就离不开算术运算符。
  • 算术运算符:用于执行两个变量的算术操作符。
算术运算符 描述
+
-
*
/
% 取余(取模)

# 1、加减乘除

TIP

加减的符号和数学一致,乘法是 * 号,除法是 /

<script>
  console.log(1 + 1); // 2
  console.log(2 - 1); // 1
  console.log(1 * 2); // 2
  console.log(4 / 2); // 2
</script>

# 2、%取余运算符

TIP

  • 取余运算也叫作 "求模运算" ,用百分号"%"表示
  • a % b 表示,求 a 除以 b 的余数,它不关心整数部分,只关心余数
13 % 5; // 3,因为13除以5余数是3
23 % 3; // 2,因为23除以3余数是2
8 % 2; // 0,因为能够整除,余数是0
9 % 3; // 0,因为能够整除,余数是0
3 % 9; // 因为商0,余数是3
5 % 10; // 因为商0,余数是5

任何数%模上大于他自身的数,结果是就是这个数本身

# 3、算术运算符优先级

TIP

  • 默认情况下,乘除取模的优先级要高于加减
  • 不过我们可以使用圆括号()来提升优先级,改变运算符的计算顺序。
  • 这里提升优先级只能用(),没有{}这一说
<script>
  console.log(1 + 2 * 3); // 7
  console.log(1 + (2 * 3) / 2); // 4
  console.log((1 + 2) * 3); // 9
  console.log(((1 + 2) * 3) / (2 + 1) / 3); // 1
</script>

# 4、+号的两种作用

TIP

  • 加号有 "加法" 和 "连字符" 两种作用
  • +号两边的数都是数值时,做加法运算,否则为"连字符"(字符串的拼接)
<script>
  console.log(1 + 1); // 2
  console.log(1 + "1"); // '11'
  console.log(1 + 1 + "1"); // '21'
</script>

# 5、隐式类型转换

TIP

  • 如果参与数学运算的某操作数不是数字类型,那么 JS 会自动将其转换为数字类型,然后再说计算。这一过程称为隐式转换
  • 隐式转换的本质是内部自动调用了相关的函数来转换。比如我们做的是算述运算,他就会自动调用Number()函数,帮我们把操作数转换成数字后,再做算术计算。
<script>
  console.log(1 * "2"); // 2
  console.log(4 / "2"); // 2
  console.log(5 % "4"); // 1
  console.log("2" - 1); // 1
  console.log(true + false); // 1
  console.log(2 + null); // 2
  // 任何类型与NaN做运算得到NaN,与字符串拼接除外
  console.log(1 + undefined); // NaN
</script>

注意事项

任何数NaN做算述运算,结果都是NaN,除与字符串拼接外

+号参于字符串计算,他不会做隐式转换,把操作数转换为数字,而是会当成字符串拼接来处理。

<script>
  console.log(1 + "true"); // '1true'  字符串拼接
  console.log(1 + "2"); // '12' 字符串拼接
</script>

# 6、显示转换

TIP

  • 我们之前讲过强制类型转换,其实就是显示类型转换
  • 也就是我们自己手动的调用相关函数或方法,比如前面讲过的Number()parseInt()parseFloat()来转换数据类型。
<script>
  //隐式转换
  console.log(1 + true); // 2
  //显示转换(强制类型转换)
  console.log(1 + Number(true)); // 2
  console.log(1 + Number("2")); // 3
</script>

数学运算时隐式转换自动调用的Number()函数将其它类型转换成数字,那 Number()实现不了的,就得手动调用其它方法来实现

<script>
  console.log(300 - "100px"); // NaN
  console.log(300 - parseInt("200px")); // 100
</script>

# 7、+ - 的特殊用法

TIP

  • 如果 Number 函数能把某个类型转换成数字,那+ 和-号也可以。
  • 不过要特别注意 -true-false -null这 3 个特殊情况,他们会将其转换成负数
<script>
  console.log(-true); // -1
  console.log(-false); // -0
  console.log(-null); // -0
  console.log(typeof -"2"); // number
  console.log(typeof +"2"); // number
  console.log(-"2"); // -2
  console.log(+true); // 1
  console.log(+false); // 0
  console.log(typeof +"0b10", +"0b10"); // number 2
</script>

# 8、浮点数(小数)丢失精度

TIP

在 Javascript 中,有些小数的数学运算不是很精准

<script>
  console.log(0.1 + 0.2); // 0.30000000000000004
  console.log(0.07 * 100); // 7.000000000000001
</script>

所以不要直接判断两个浮点数是否相等

<script>
  var a = 0.1 + 0.2;
  console.log(a == 0.3); // false
</script>

注:

JavaScript 使用了 IEEE754 二进制浮点数算术标淮,这会使一些个别的小数产生"丢失精度"问题。

IEEE754 二进制浮点数算术标淮是计算机底层编译标准,了解即可。

解决浮点数运算不精准办法

TIP

  • 小数运算时,运算后的结果,再调用toFixed()方法保留指定的小数位数;
  • toFixed()方法的返回值类型,是字符串类型
  • toFixed()在指小数位时,会采用 4 舍 5 入
<script>
  var a = 3.0558;
  console.log(a.toFixed(2)); // 3.06
  console.log(typeof a.toFixed(2)); // string
</script>
<script>
  var a = 0.1 + 0.2;
  console.log(a); // 0.30000000000000004
  console.log(a.toFixed(2)); // '0.30'
  var c = 1 + a.toFixed(2);
  console.log(c); // '10.30'
  console.log(typeof a.toFixed(2)); // string
  // 调用Number函数来强制转换
  var d = 1 + Number(a.toFixed(2));
  console.log(d); // 1.3
</script>

# 9、总结

TIP

关于算述表达式,我们学了以下几个点

  • 算术操作符 (+ - * / %
  • 算述运算符优先级 (* / %优先级高于+-,可以通过添加( ) 来提升优先级,改变计算顺序)
  • +号的两种作用 ("加法"和"连字符")
  • 隐式类型转换 (参于算述运算的操作数,不是数字,JS 内部会自动调用Number()函数,讲其转换成数字,再计算,这个过程称为隐式转换)
  • 显示类型转换 (手动调用相关的函数或方法,来实现数据类型的转换,称为显示类型转换)
  • + - 的特殊用法 (如果Number()函数,能讲某个类型转换成数字,那+-也可以,这里指的是单操作数时)
  • 浮点数丢失精度问题 (JS 中的小数在参于运算时不是很精准,我们可以通过调用toFixed()方法来指定保留的小数位)

# 二、赋值表达式

TIP

赋值操作符:就是给变量赋值用的

赋值操作符 描述
= 赋值
+=、-=、*=、/=、%= 快捷赋值
++ 自增运算
-- 自减运算

# 1、= 赋值运算符

TIP

  • 赋值运算符:会将等号右边的数值,赋值给等号左边的变量
  • 如下图,将=等号右边的2赋值给左边的变量a
image-20220422191654531
<script>
  var a = 5;
  var b = 2 + 4 / 2;
  console.log(a, b); // 5  4;
</script>

# 1.1、 赋值运算也产生值

TIP

赋值运算也产生值,将等号后面的计算结果,作为“赋值运算的值”

<script>
  var a;
  console.log((a = 3)); // 3
  console.log((a = 1 + 4)); // 5
</script>

这就意味着,可以连续使用赋值运算符

<script>
  var a, b, c;
  a = b = c = 12 + 2;
  console.log(a, b, c); // 14 14 14
</script>

# 2、快捷赋值(+=、-=、*=、/=、%=)

TIP

快捷赋值运算符:表示在原数值基础上进一步计算

<script>
  var a = 1;
  a += 5; // 相当于 a = a + 5;
  console.log(a); // 6
  var b = 4;
  b *= 2; // 相当于 b = b * 2
  console.log(b); // 8
  b /= 2; // 相当于 b = b / 2
  console.log(b); // 4
  b %= 2; // 相当于 b = b % 2;
  console.log(b); // 0
</script>

以上快捷赋值操作符仅仅是简写语法,使用它们并不会提升性能。

# 3、++ 自增 和 -- 自减 运算符

TIP

  • ++ 自增:表示在自己的基础上+1
  • -- 自减:表示在自己的基础上-1
<script>
  var a = 1;
  a++; // a++ 相当于 a = a + 1
  console.log(a); // 2

  var b = 2;
  b--; // b-- 相当于 b = b - 1;
  console.log(b); // 1
</script>
  • ++-- 只能和变量搭配使用
<script>
  console.log(++5); // 报错
     console.log(--5); // 报错
</script>

# 3.1、 ++a 和 a++ 的区别

TIP

  • ++a 是先自增再赋值
  • a++ 是先赋值再自增
<script>
  var b = 3;
  var c = b++; // 选赋值,再自增,所以先把b的值3 赋值给变量c,然后再自增b=4
  console.log(c); // 3
  console.log(b); // 4
</script>
<script>
  var b = 3;
  var c = ++b; // 先自增,再赋值,所以b先自增1,得到b=4,然后把4赋值给变量c,所以c的值也是 4
  console.log(c); // 4
  console.log(b); // 4
  console.log(c++); // 4 先赋值,再自增,所以打印是4,c自增后是5
  console.log(++c); // 6 先自增,再赋值,c上面已经是5,再自增就是6,自增后再赋值,所以打印是6
</script>

# 3.2、--a 和 a--的区别

TIP

  • --a 是先自减再赋值
  • a-- 是先赋值再自减
<script>
  var a = 3;
  var c = a--; // 先赋值,再自减,所以a先把值3赋给c,然后自已再减1,所以c是3,a是2
  console.log(c); // 3
  console.log(a); // 2
</script>
<script>
  var a = 3;
  var c = --a; // 先自减,再赋值,所以a先减1,a的值是2,然后再把2赋值给变量c,所以a,c都是2
  console.log(a); // 2
  console.log(c); // 2
  console.log(a--); // 2 先赋值,再自减,所以打印是2,再自减后,a=1
  console.log(--a); // 0  先自减,再赋值,上一步计算得到a=1,a再自减得到0,然后再赋值,所以打印0
</script>

# 4、++ 与 -- 测试题

下面代码的运行结果是 ?

<script>
  var num1 = 1,
    num2 = 2;
  var num3 = ++num1 + num2; // ++num1先自增,得到num1=2,再赋值,则2+num2=2+2=4
  console.log(num1, num3); // 2  4
</script>

下面代码的运行结果是 ?

<script>
  var num1 = 1,
    num2 = 2;
  var num3 = num1 + num2++; // num2 先赋值,再自增,所以 num3 = 1+2=3 ,num2 = 3
  console.log(num2, num3); // 3  3
</script>

下面代码的运行结果是?

<script>
  var num1 = 2,
    num2 = 3;
  /* 
    --num1 先自减得到 num1 = 1,再赋值,则 num3 = 1+num2--; 
    num2-- 先赋值,则 num3 = 1+3=4,然后num2再自减,得到 num2=2
  */
  var num3 = --num1 + num2--;
  console.log(num1, num2, num3); // 1 2 4
</script>

# 三、关系表达式

TIP

  • 说到关系表达式,肯定就离不开关系操作符。
  • 关系操作符: 用来比较两个值之间的大小关系,如果关系成立它返回true,如果关系不成立则返回false
关系操作符 描述
> 大于
< 小于
>= 大于或等于
<= 小于或等于
== 等于
!= 不等于
=== 全等于(值和类型都比较)
!== 不全等于(其结果与===比较的结果正好相反)

# 1、> 和< 以及 >= 和<=

TIP

以上操作符主要是用来比较两个值的大小关系

  • 如果操作符两边的操作数,都是字符串,则不会将操作数转换成数字进行比较,而会分别比较字符串的Unicode编码
  • 除去操作符两边的操作数都是字符串这种情况外,其它情况在在作比较时,会先将非数字类型转换为数字 (隐式转换) ,然后再进行比较。 整个隐式转换过程是程序内部自动调用Number()函数来实现的

以上规则,只针对基本数据类型而言

# 1.1、操作符两边的数,不都是字符串

//  数字与数字作比较,最简单
1 > 2; // false
2 > 1; // true
2 >= 1; // true

// 数字与非数字作比较
1 > true; // false  等价于 1>1
1 > "2"; // false 等价于 1>2
1 <= "a"; // false 等价于 1<=NaN

// 字符串与布尔值作比较
"" <= false; // true  等价于 0<=0
// null 和 undefined与数字作比较
null >= 0; // true 等价于 0>=0
undefined <= 0; // false 等价于 NaN<=0

// 字符串与null 和undefined作比较
"" >= null; // true 等价于 0>=0
"" <= undefined; // false 等价于 0<=NaN

// null与undefined作比较
null >= undefined; // false 等价于 0>=NaN

# 1.2、字符串与字符串做比较

TIP

  • 字符串与字符串作比较时,不会将其转换成数字进行比较,而会分别比较字符串的Unicode编码
  • 比较字符编码时,是一位一位进行比较,如果两位一样,则比校下一位

英文字母对应的 Unicode 编码

TIP

  • A~Z 对应 65~ 90 也就是AUnicode编码是65ZUnicode编码是 90
  • a~z 对应 97~122
  • 0~9 对应 48~57
// 先把两边的第一位拿出来作比较,即 'a' < 'b' ,比较时比较的是Unicode编码,则 97<98,所以结果为 true
"abc" < "b"; // true;

// 先把两边的第一位拿出来作比较,即 '1' < '5' ,比较时比较的是Unicode编码,则49 < 53 ,所以结果为 true
"11" < "5"; // true;

// 先把两边的第一位拿出来作比较,如果两位一样,则比较下一位,所以拿第二位来比较,则'b'<'c',比较时比较的是Unicode编码,则 98 < 99 ,所以结果为 true
"abc" < "acd"; // true

# 2、JS 中没有连比

TIP

JS 中是没有连比的功能,我们来看下面的列子

<script>
  /*
   *  1<3<2 为什么会得到 true ?
   *  他是从左往右开始比较,1 < 3 这个表达式返回值为true
   *  再拿true与2作比较,那 true < 2
   *  数值与其它类型做比较时,会先将其转换成数字,再比较,true转数字转成 1
   *  即 1 < 2 吗 ?那肯定小于,所以返回结果就为 true
   */
  console.log(1 < 3 < 2); // true 但是本质上是错的,3不可能小于 2
</script>

那如果要判断一个数是不是> 1同时< 4,我们可以用后面学到的&&与操作符和||或操作符来实现

# 3、==!=

TIP

  • ==用来比较操作符两边值(隐式转换后)是否相等,在比较时,不会比较两边值的类型
  • 如果值(隐式转换后)相等,返回true,不相等,则返回false
  • 在比较时同样会做隐式类型转换,非数字类型会自动调用Number()函数,转成数字再比较
  • 以上规则,只适用于基本数据类型
1 == true; // true
0 == ""; // true
// ‘’ 转成数字是 0   false转成数字是 0 所以0==0 是true
"" == false; // true
  • !=是用来比较两个值(隐式转换后)是否不相等,如果相等返回 false,如果不相等,返回 true
1 != true; // false
0 != ""; // false
"" != false; // false
1 != "1"; // false

特殊情况: null == undefined 结果为true

# 4、===!==

TIP

  • ===用来比较两边的值是否全等,如果全等则为true,不全等则为false
  • 全等:不仅在比较是会比较值大小,还会比较值的类型
  • ====只操作符两边的数长的一模一样,才会是 true,否则就是 false

注意区分: 而前面讲的==只会比较两个值(隐式转换后)的大小,不会比较类型。

1 == "1"; // true 只比较值
1 === "1"; // false  同时比较值和类型
1 == true; // true 只比较值
1 === true; // false 同时比较值和类型

!== 不全等

  • !== 用来比较两边值是否不全等,如果是则返回true,不是返回false
  • !==的结果,正好是===结果的反面,如果===返回结果是true,那!==返回结果就是false
1!=true; // false
1!==true; // true
1==1; // false
1!='我'; // true
  • !==的结果,正好是===结果的反面。
1 !== 1; // false
1 !== "1"; // true
1 !== true; // true

# 5、 特殊的比较

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

NaN == NaN; // false
NaN === NaN; // false

NaN !== NaN; // true
NaN != NaN; // true

# 6、区分 = 、== 、=== 的区别

TIP

  • = 是赋值
  • == 是比较 但只比较值
  • === 是比较 同时比较值和类型
<script>
  var a = 3;
  console.log((a = "3")); // "3"   a = "3" 是赋值表达式,返回结果为=右边的值
  console.log(a == 3); // true a == 3 关系表达式 比较时,只比较值(隐式转换后值)
  console.log(a === "3"); // true a === "3" 关系表达式,比较时,比较值同时还比较类型
  console.log(a === 3); // false a === 3 关系表达式,比较时,比较值同时还比较类型
</script>

# 7、总结

操作符 重点
>、<、>=、<= 字符串与字符串之间的比较,是按位比较,比较的是字符的Unicode编码
其它基本数据类型之间的比较,在比较时会将非数字类型的数,自动调用Number函数隐式将数据类型转的为数字,再比较
注意:JS 中没有连比
== 、!= ==!=在比较时,只比较值,不比较类型
=== 、!== ===!===在比较时,除了比较值,还要比较类型
特殊比较:null==undefined值为true
null===undefined 值为false

重点: 弄清= 、==、===三者之间的区别

# 四、逻辑表达式

TIP

逻辑运算符用来表示日常交流中的 “并且”,“或者”,“非” 等思想。

逻辑运算符 描述
! 逻辑非 否定的意思
&& 逻辑与 并且的意思
|| 逻辑或 或者的意思

# 1、!非运算符

TIP

  • !非运算符也叫"取反运算符"
  • !非是一个单目运算符,所谓单目运算符,就是这个操作符只能有一个操作数
  • 操作数可以是任何类型的
  • !运算时也会用到隐式转换,如果操作数为非布尔值,其内部会自动调用Boolean函数,将其隐式转换为布尔类型的值后,再取反操作,最后将值返回。
  • 所以!非运算的结果一定是布尔值
!true; // false
!3; // false
!0; // true
!undefined; // true
!""; // true
!"我"; // false
!5 > 2; // false !5 转成布尔值是false,则 false > 2比较时,false 转成数字是 0,则 0 > 2,为 false
  • 我们可以通过对一个值两次取反操作,将其变为一个 Boolean 类型的值
!!3; // true;
!!undefined; // false

总结:将基本数据类型转换为布尔值的 2 种方法

  • 方法一:调用Boolean()函数来实现
  • 方法二:在一个数值或变量前加!!(两次取反)操作,也可以实现

# 2、&&与操作符

TIP

  • &&与操作符,表示并且的意思,可以对&&符号两侧的值进行与运算并返回结果
  • &&与操作符是一种短路操作符,他有一个非常重要计算规则,就是 &&与的短路计算

# 2.1、&&与的短路计算规则

TIP

  • 如果第一个操作数转为布尔值false,则就不会看第二个操作数了。返回结果为第一个操作数的返回结果
  • 如果第一个操作数转为布尔值true,则会看第二个操作数。返回结果为第二个操作数的返回结果
  • 上面提到的"操作数",可以是一个表达式、值、函数、对象等任何类型

如何记忆:

  • A && B,表示 A 和 B 两个都要满足条件(即都为真)才行。
  • 所以只要 A 为假,就不满足条件了,所以就没必要再看 B 了,直接把 A 的结果返回就好
  • 如果 A 为满足条件(为真),那我肯定要看下 B 是不是满足条件
  • 如果 B 不满足条(为假),那最后结果只能是假了,所以将 B 的结果返回
  • 如果 B 满足条件(为真),那最后结果是真,也将 B 的结果返回。

演示代码:

  • 两操作数都是布尔值
false && true; // false
true && false; // false
true && true; // true;

只要有一个是false,返回值就是false,只有两个都为true,才返回true

  • 两操作数,都非表达式
1 && 2; // 2  1转换为布尔值是true,所以看第二个操作数,返回值为第二个操作数 2
0 && 3; // 0  0转换为布尔值是false,所不看第二个操作数,返回值为第1个操作数 0
"" && 1; // ''  ‘’转换为布尔值是false,所不看第二个操作数,返回值为第1个操作数 ‘’
undefined && NaN; // undefined;  undefined转换为布尔值是false,所不看第二个操作数,返回值为第1个操作数 undefined
  • 两操作数,至少有一个是表达式
true && alert("我能出来喽"); // 页面显示弹窗
false && alert("我是不会出来的"); // false  第一个操作数为false,则不看第二个操作数,返回值为第一个操作数false
2 + 3 && 4 + 5; // 9  2+3结果为5,转换为布尔值是true,则看第二个操作数(4+5),返回值为第二个操作数,第二个操作数是表达式,所以返回值为第二个表达式的返回值 9
3 - 3 && 1; // 0  3-3结果为0,转换为布尔值是false,则不看第二个操作数,返回值为第一个操作数,第1个操作数是表达式,所以返回值为第1个表达式的返回值0

以上演示代码,直接把对应代码,在浏览器的 console 控制台输入,查看效果

# 2.2、测试题

以下代码的执行后的结果是多少?

<script>
  var a = 1,
    b = 2,
    c;
  c = a < b && a++;
  console.log(c, a);
  c = a > b && --a;
  console.log(c, a);
  a == b && alert("a和b相等了");
</script>
自己先分析,再点击查看正确答案

输出结果:

1 2
false 2
弹出 alert 弹窗

# 2.2、 如何判断一个数的范围

var a = 10;
console.log(a > 5 && a < 12); // true   这个逻辑表达示的含 义是: a>5 同时 a<12 ?
console.log(a > 5 && a < 8); // false

表达式都会有一个返回值,所以我们可以用一个变量来接受表达式的返回值

<script>
  var a = 10;
  var b;
  b = a > 5 && a < 12;
  console.log(b); // true
  b = a > 5 && a + 2;
  console.log(b); // 12
</script>

# 2.3 、 如何判断一个值是不是 NaN

  • NaN 是一个不是数字的,数字类型,利用这个特性来判断
var a = NaN;
var _isNaN = isNaN(a) && typeof a === "number";
consloe.log(_isNaN);
  • NaN 自己不等于自已利用 这个特性来判断(暂时看不懂代码没关系,了解即可)
<script>
  function _isNaN(n) {
    if (n !== n) {
      return true;
    } else {
      return false;
    }
  }
  console.log(_isNaN(NaN)); // true
</script>

# 3、|| 或 操作符

TIP

  • ||或操作符,表示或者的意思,可以对||符号两侧的值进行或运算并返回结果
  • ||或操作符是一种短路操作符,他一个非常重要计算规则,就是||或的短路计算

# 3.1、|| 或的短路计算规则

TIP

  • 第一个操作数转换为布尔值是true,则就不会看第二个操作数。返回结果为第一个操作数的返回结果
  • 第一个操作数转换为布尔值是false,则就会看第二个操作数。返回结果为第二个操作数的返回结果
  • 上面提到的"操作数",可以是一个表达式、值、函数、对象等任何类型

如何记忆:

  • A || B,表示 A 或 B 中只有一个满足条件就可以
  • 所以只要 A 为真,就满足条件了,所以就没必要再看 B 了,所以就把 A 的结果返回
  • 如果 A 不满足条件,那我肯定要看下 B 是否满足条件
  • 如果 B 满足条件(为真),那最后肯定是真了,所以把 B 的结果返回
  • 如果 B 不满足条件(为假),那最后肯定是假了,所以把 B 的结果返回。

演示代码:

  • 两操作数都是布尔值
true || true; // true   第1个操作数是true,则不看第2个操作数,将第1个操作数作为结果返回 true
true || false; // true  第1个操作数是true,则不看第2个操作数,将第1个操作数作为结果返回 true
false || true; // true  第1个操作数是false,则看第二个操作数,将第2个操作数作为结果返回 true
false || false; // false; 第1个操作数是false,则看第二个操作数,将第2个操作数作为结果返回 false
  • 两操作数,都非表达式
1 || 2; // 1  第1个操作数转boolean值是true,则不看第2个操作数,将第1个操作数作为结果返回 1
0 || 3; // 3  第1个操作数转boolean值是false,则看第2个操作数,将第2个操作数作为结果返回 3
"" || 1; // 1  第1个操作数转boolean值是false,则看第2个操作数,将第2个操作数作为结果返回 1
undefined || NaN; // NaN  第1个操作数转boolean值是false,则看第2个操作数,将第2个操作数作为结果返回 NaN
true || alert("我是不会出来的"); // 不会弹出弹窗
false || alert("我肯定能出来喽"); // 弹出弹窗
var a = 10;
a > 3 || a < 12; // true  第一个操作数a>3返回值为true,则不看第二个操作数,把第1个操作数的结果作为结果返回
a > 11 || a - 10; // 0  第一个操作数a>11返回值为false,则看第二个操作数,把第2个操作数的结果作为结果返回 0

# 3.2、测试题

以下代码,输出的结果?

<script>
  var a = 1,
    b = 2,
    c;
  c = a < b || a++;
  console.log(c); // true
  c = a > b || a--;
  console.log(a, c); // 0 1
</script>

# 4、逻辑操作符优先级

TIP

逻辑操作符优先级是: 非 --> && 与 --> || 或 (从左到右,优先级从高到低)

/*
 * 逻辑表达式 1 && false  ||  4 && 5;  表示 1 && false 和 4 && 5中只要有一个成立,就成立
 * 由于 ||或操作符的短路特性,所以他需要先计算||或左边的值,再判断是否要看操作符右边值
 * 先算 1 && false 得到 false || 4 && 5 , ||左边是false,则要看第二个操作数,得到 false || 5
 * 再计算 false || 5   的结果,得到5
 */
(1 && false) || (4 && 5); // 5
(1 && 2) || (4 && 5); // 2
(1 && true) || (!"" && 2); // true

# 五、综合表达式

TIP

  • 综合表达式:就是 算术操作符赋值操作符关系操作符逻辑操作符出现在同一个表达式中。
  • 那这些操作符混在一起使用,他们的优先级就显得很重要。
  • 操作符的优先级,从上往下,优先级从高到低,如下:
    • ++ 和 -- 运算符
    • 非运算符(!)
    • 算术运算符( %、/、* 、+、-)
    • 关系运算符(>、<、>=、<=、== 、!=、===、!==)
    • 逻辑运算符(&& 、||)
    • 赋值运算符(=、+=、-=、/=、%=)

综合表达式的计算规则

  • 如果操作符优先级一样,则从左往右算
  • 可以用()来改变优先级,改变计算顺序
  • 为了提高代码可读性,在实际开发中,我们都会添加(),这样能更直接的知道代码的执行顺序

# 1、测试题

以下代码,最终输出的结果是 ?

<script>
  var a = 0,
    c;
  c = !3 + 4 / 2 > 5 && ++a;
  console.log(c, a); // false 0
</script>

image-20220908171818639

以下代码,最终的输出结果是 ?

<script>
  var a = 10;
  var b = (a++ && a < 11) || a++;
  var c = a + 1 && a + 2;
  console.log(b, c, a); // 11  14  12
</script>
image-20220423154727969
自己先分析,再点击查看正确答案

输出结果:11 14 12

# 2、所有操作符优先级

TIP

  • 关于每一个运算符的详细优先级,可以参考下面这张图
  • 在下图中,越在最上面的,优先级越高,越优先计算。

# 六、三元(条件)运算符

TIP

  • JavaScript 中提供了一种叫做 "三元运算" 的语法形式,让我们可以方便地实现选择
  • 他更想是if..else语句的紧凑版
条件表达式 ? 表达式1 : 表达式2;

执行流程

  • 首先对条件表达式进行求值,条件表达式的值会被
  • 如果条件表达式的值为 true,则执行语句 1,并返回执行结果
  • 如果条件表达式的值为 false,则执行语句 2,并返回执行结果
2 > 1 ? 1 + 1 : 2 + 3; // 2
<script>
  var a = 6;
  // 首先判断a>5为真吗?这里肯定为真,所以就会执行a--,a--是先赋值,再自减
  var b = a > 5 ? a-- : a++;
  console.log(a, b); // 5 6
</script>

注意事项

三元运算符,虽然回有返回结果,但我们并不一定要用一个变量来接受,有些时候,我们并不关心他的返回值

var a = 2;
// 这种情况下,我们更关心,满足条件要做什么事,不满足条件做什么事,并不关心返回结果
a > 1 ? alert(a + "大于1") : alert(a + "小于1");
// 同时这种情况,不管 a>1 是真是假,最终返回结果都是undefined,因为 alert() 方法返回值是undefined
var b = a > 1 ? alert(a + "大于1") : alert(a + "小于1");
console.log(b); // undefined

更多实际应用,在扩展知识中会讲到

实战案例:补 0 操作

TIP

  • 当我们获取当前日期时,如果计算得到的日,月小于 10 的时候,都会以一位数的方式显示,如:2022年8月17日,而我们希望以两位的方式显示,如:2022年08月17日
  • 这个时候就会涉及到补 0 的问题了。
// 未补0
var date = new Date();
var year = date.getFullYear(); // 获取完整的年份(4位)
var month = date.getMonth() + 1; // date.getMonth()获取当前月份(0-11,0代表1月),所以要加1
var day = date.getDate(); // 获取当前日(1-31)
var currentDate = year + "年" + month + "月" + day + "日";
console.log(currentDate); // 2022年8月17日
// 完整的补0后效果
// var date = new Date("2019/1/3");
var date = new Date();
var year = date.getFullYear(); // 获取完整的年份(4位)
var month = date.getMonth() + 1; // date.getMonth()获取当前月份(0-11,0代表1月),所以要加1
var day = date.getDate(); // 获取当前日(1-31)
month = month < 10 ? "0" + month : month; // 月份小于10,数字前补0
day = day < 10 ? "0" + day : day; // 日小于10,数字前补0
var currentDate = year + "年" + month + "月" + day + "日";
console.log(currentDate); // 2022年08月17日

# 七、重点难点总结

TIP

总结本章重难点知识,理清思路,把握重难点。并能轻松回答以下问题,说明自己就真正的掌握了。

用于故而知新,快速复习。

# 1、表达式种类,每种表达式分别有哪些操作符

算术表达式 赋值表达式 关系表达式 逻辑表达式
8f851432b55457f8c4680c0f9c823ca dd261545a8759c54f5bb5fd4f372356 aa54572457174edd5f9eb91dbdd90ad-16510356239953

当然还有综合表达式,是所有上述表达式中对应的操作符的综合应用。

# 2、每种表达式中运算顺序是什么? 综合运算顺序是什么?

注:

在做运算时,要特别注意 &&||的短路计算,还有++aa++--aa--的特性。

可以用下面这道题再来检验下自己是否理解了。

<script>
  var a = 0;
  var b = 2;
  var c = (!3 && false) || (--a && b++); // b++ 先赋值,再自增
  console.log(a, b, c); // -1 3 2
</script>

# 3、隐式转换和显示(强制)类型转换

TIP

  • 如果参与数学运算的某操作数不是数字类型,那么 JS 会自动将其转换为数字类型,然后再计算。这一过程称为隐式转换
  • 数学运算时,其 JS 内部自动调用的是 Number()来转换
  • +、-运算符在做类型转换时,内部也是自动调用的Number()来实现的
  • !非运算符在做为型转换时,其内部是自动调用的Boolean()函数来实现转换
  • !!两次取返操作,把值转换成对应的 boolean 值,其内部也是调用的Boolean()函数来实现的
// 隐式转换
"" >= true; // false
// + 做数据类型转换
typeof +"1"; // 'number'
// !!两次取反,可以把
!!5; // true

手动的调用相关函数或方法来实现数据类型转换

比如:前面讲过的Number()parseInt()parseFloat()来转换数据类型,这种方式,称为 显示类型转换

# 4、什么 &&与短路计算 和 || 或短路计算 ?

&& 与 运算规则

  • 如果第一个操作数转为布尔值false,则就不会看第二个操作数了。返回结果为第一个操作数的返回结果
  • 如果第一个操作数转为布尔值true,则会看第二个操作数。返回结果为第二个操作数的返回结果
  • 上面提到的"操作数",可以是一个表达式、值、函数、对象等任何类型

|| 或运算符规则

  • 第一个操作数转换为布尔值是true,则就不会看第二个操作数。返回结果为第一个操作数的返回结果
  • 第一个操作数转换为布尔值是false,则就会看第二个操作数。返回结果为第二个操作数的返回结果
  • 上面提到的"操作数",可以是一个表达式、值、函数、对象等任何类型
1 && 2; // 2
1 || 2; // 1

# 5、a++ 和 ++a 及 a-- 与 --a 的区别

TIP

  • a++是先赋值,再自增 ++a是先自增再赋值
  • a--是先赋值,再自减 --a 是先自减,再赋值
var a = 0;
console.log(a++); // 0
console.log(++a); // 2
console.log(a--); // 2
console.log(--a); // 0

# 6、= 和 == 和 === 三者的区别

TIP

  • = 赋值
  • == 比较,只比较两个值的大小
  • === 比较,比较值的同时还比较类型
<script>
  var a;
  console.log((a = 5)); // 5
  console.log(a == "5"); // true
  console.log(a === "5"); // false
  console.log(a === 5); // true
</script>
  • 特殊的 null 与 undefined 与 NaN
null == undefined; // true
null === undefined; // false
NaN == NaN; // false
NaN === NaN; // false
NaN != NaN; // true
NaN !== NaN; // true

# 八、综合案例

判断当前输入年份,是否是闰年 ?

需求分析:

  • 公历闰年的简单计算方法(符合以下条件之一即可)
    • 能被 4 整除且不能被 100 整除
    • 能被 100 整除也能被 400 整除
  • 1950-2050 年之间的闰年有: 1952、1956、1960、1964、1968、1972、1976、1980、1984、1988、1992、1996、2000、2004、2008、2012、2016、2020、2024、2028、2032、2036

代码实现思路:

  • 利用 prompt() 弹出输入框,让用户输入年份
  • 定义变量 var year 来接受,用户输入的年份
  • 对接收到的值做判断,判断条件就是需求中提到的,两个条件中有一个满足就可,所以选择||操作符
  • 判断表达式: 左边条件 1 ||右边条件 2
<script>
  var year = parseInt(prompt("请输入年份"));
  var isRun =
    (year % 4 == 0 && year % 100 != 0) || (year % 100 == 0 && year % 400 == 0);
  alert(year + "是闰年吗?" + isRun);
</script>

# 九、作业

  • 1、完成以下几道测试题,并写明计算的整个过程
<script>
  var a = 0;
  var b = 2;
  (3 && true) || (--a && b++);
  console.log(a, b);
</script>
<script>
  var a = 0;
  var b = 2;
  var c = (3 && false) || (--a && b++); // b++ 先赋值,再自增
  console.log(a, b, c);
</script>
<script>
  var num1 = 1,
    num2 = 3;
  var num3 = ++num1 + --num2;
</script>
  • 2、把课堂最后的综合案例,按先分析需求,再思考代码实现的方式来思考,最后把代码写出来
  • 3、把课堂上老师讲的知识点,都自己手动敲一遍。
  • 4、把课堂上的测试题,自己在不看答案的情况下,重做一遍

# 十、位运算符(难点-大厂必考)

TIP

  • 在这里我们扩展位运算方面的知识,这些知识点相对比较底层,理解起来也有一定的难度,但很重要,是大厂面试必考知识点
  • 我们很多时候在网上看大神写的代码,经常会出现位操作符,很多人看到位操作符,整个人就蒙圈了
  • 但我保证,我给你讲完位操作符后,你肯定通透的很,对于位运算符,将再也不恐惧了。此处来点掌声

为了让大家能听得懂,在讲解位运算符之前,我们要把二进制相关的内容了解透彻,比如以下三点

  • 正十进制数如何转二进制
  • 二进制如何转十进制
  • 负十进制数如何转二进制
  • 如何一眼区分二进制数是正数还是负数
  • 最终版,二进制如何转换成十进制

进制转换工具:https://tool.lu/hexconvert/ (opens new window) 可以用来校验进制转换后的效果

了解了以上三方面内容后,我们再来学习位运算。

# 1、正十进制如何转二进制

TIP

要理解 10 进制如何转二进制,我们可以来思考下面这个问题:

如何得到 153 对应 百,十,个位上的数呢 ?

image-20211217203754752

转换方法,用当前数 /10 取余数的方式得到。

  • 153 / 10 = 1 5 余数是 3
  • 15 / 10 =1 余数是 5
  • 1 / 10 = 0 余数是1

最后从下往上分别对应 百位 1 十位 5 个位 3

# 十进制转二进制计算公式

TIP

  • 10 进制转 2 进制,就是用当前数除 2 取余数的方式得到的
  • 如求 5 的二进制
    • 5/2=2 余 1 得到右边第 1 位
    • 2/2=1 余 0 得到 右边第 2 位
    • 1/2=0 余 1 得到 右边 第 3 位

最后商为 0 时, 把所从上往下的余数从右往左写出来就是最后的二进制数 101

求 10 的二进制数

10/2=5  // 余 0
5/2=2   // 余 1
2/2=1   // 余 0
1/2=0   // 余 1

最后得以 10 的二进制数为1010

# 2、二进制如何转换成十进制


image-20220914192828992

# 二进制转十进制计算公式

TIP

image-20220913235332821

  • b0表示二进制右边第1位上的数字
  • b1表示二进制右边第2位上的数字
  • b2表示二进制右边第3位上的数字
  • ...... 依次类推

5 的二进制计算过程

5 的二进制数: 00000000000000000000000000000101 可以简写成101转换成 10 进制,计算过程如下:

image-20220913235657873

从上面可以得到,当前位上的数字如果是 0,计算得到的结果就是 0

  • 1010 对应 10 进制 0+1*21+ 0 + 1*23 =2+8=10
  • 11101 对应 10 进制 1+0+1*22+1*23+1*24=1+0+4+8+16=29

# 3、负十进制数如何转换成二进制

TIP

  • 负数和正数的存储方式不一样,负数是以一种二补数(或补码)的二进制编码存储。
  • 我们来看下,负数是如何转成对应二进制数,然后存储的。这里以-5来为例

image-20220914195720810

-5 的二进制计算过程

  • 第一步:先确定 5 的二进制,得到 00000000000000000000000000000101
  • 第二步:反转每一位的二进制数,即 1 变成 0,0 变成 1 ,得到 11111111111111111111111111111010
  • 第三步:把上面反转得到二进制+1,就得到了最后负数的二进制,即 11111111111111111111111111111010+ 1 = 11111111111111111111111111111011
  • 最后-5的二进制数是 11111111111111111111111111111011
// 我们来验证下二进制`11111111111111111111111111111011` 转换成10进制,是不是-5
var str = 0b11111111111111111111111111111011; // js中2进制数以0b开头
console.log(str >> 0); // -5

>> 右移操作符,这里你简单理解为把二进制转成 10 进制数,在后面我们马上就会学习到他。

image-20220914194804921

负数对应二进制如下:

十进制 对应正数二进制 对应负数二进制
3 11 11111111111111111111111111111101
9 1001 11111111111111111111111111110111
12 1100 11111111111111111111111111110100

image-20220914200727218

# 4、如何一眼区分二进制数是正数还是负数

TIP

  • 有符号整数使用 32 位的前 31 位表示整数值,第 32 位表示数值的符号,如果 32 位是 0,表示正数,如果是 1 表示是负数。
  • 第 32 位称为符号位,他的值决定了数值其余部分的格式。
  • 正数以真正的二进制格式存储,而负数是以我们上面提到的补码的二进制编码存储的。

接下来我们看下面几个二进制数,如果第 32 位是 0,表示正数,如果为 1,表示负数

var num1 = 11111111111111111111111111111101; // 负数
var num2 = 01111111111111111111111111111101; // 正数
var num3 = 10000000000000000000000000000001; // 负数
var num4 = 00000000000000000000000000011001; // 正数

# 5、最终版,二进制如何转换成十进制

TIP

  • 拿到一个二进制数,首先看第 32 位是 0 还是 1
  • 如果是 0,就按正二进制转十进制方式转
  • 如果是 1,则就按负十进数转二进制的方式,反转回去。

正二进制转十进制

案例如下:

var num4 = 00000000000000000000000000011001;
  • 1、num4 的二进制,第 32 位是 0,则是一个正数,按正常的正二进制转十进制方式转 1
  • 2、num4 对应 10 进制计算公式= 1+0+0+1*23+1*24 = 1+8+16 = 25

image-20220914203523933

负二进制转十进制

案例如下:

var num1 = 11111111111111111111111111111101;
  • 1、num1 的二进制第 32 位是 1,则是一个负数,负数就要以补码的方式反转回去
  • 2、先拿 二进制 11111111111111111111111111111101-1 得到 11111111111111111111111111111100
  • 3、再把二上面得到的二进制反码回去,0 变 1,1 变 0,得到00000000000000000000000000000011
  • 4、最后得到的二进制是 11,转换对应 10 进制是 3,因为是负数,所以最后结果为-3

image-20220914204151774

# 6、位运算的基础知识

TIP

  • 位运算的操作数,都会被转成 32 位bit的整数(32 位的二进制数),再做运算
  • 速度是 T0(最高,速度最快)级别的,因为是在二进制下进行运算的。

# 7、按位& 操作符

TIP

  • &与位操作符会先把值转换为 32 位整数(二进制数),然后再进行位操作。
  • 按位&就是将两个操作数的每一位对齐,然后按下表中的规则,对每一位执行相应的操作
第一个数值的位 第二个数值的位 结果
1 1 1
1 0 0
0 1 0
0 0 0

按位与操作的两个位数都是 1 是返回 1,只要两个中有一个是 0,则返回 0

# 7.1、& 运算过程

TIP

  • 我们来看下面这个与&运算的运算过程
var result = 5 & 3;
console.log(result); // 1
  • 先把 5 和 3 都转换为对应的 32 位二进制数,然后再 1 位 1 位的比较,最后结果为 1

image-20220914004422990

# 7.2、按位&操作符判断奇偶数 (经典面试题)

TIP

  • 如果 一个数 & 1 == 1 这个数是奇数
  • 如果 一个数 & 1 == 0 这个数是偶数
(5 & 1) == 1; // 5 是奇数
(4 & 1) == 0; // 4 偶数

我们来分析下,其背后的逻辑是什么 ?

TIP

让我们再来回顾下,二进制转 10 进制的公式:

image-20220913235332821

通过上面公式,我们知道,除第 1 位之外的每一位上的值都是 2 的倍数。

也就是第1位上如果是0就是偶数,如果是1就是奇数

10 进制 3 4 5 6 7 8 9 10
二进制 11 100 101 110 111 1000 1001 1010
  • 如果一个数是奇数,他的第 1 位一定是 1 ,奇数 & 1 永远得到 1
  • 如果一个数是奇数,他的第 1 位是 1,这个数 & 1 永远得到 1

计算过程如下:

image-20220914144220631

# 8、按位或 | 操作符

TIP

  • 按位或|操作符会先把值转换为 32 位整数(二进制数),然后再进行位操作
  • 按位或|就是将两个操作数的每一位对齐,然后按下表中的规则,对每一位执行相应的操作
第一个数值的位 第二个数值的位 结果
1 1 1
1 0 1
0 1 1
0 0 0

按位或操作的两个位数,只要有一个是 1 就返回 1,两位都是 0 时返回 0

# 8.1、| 或运算过程

TIP

我们来看下面这个|或运算的整个计算过程

var result = 5 | 3;
console.log(result); // 7
  • 先把 5 和 3 都转换为对应的 32 位二进制数,然后再 1 位 1 位的比较

image-20220914144720116

# 8.2、| 或运算用将一个数取整

TIP

var num = 5.467;
console.log(num | 0); // 5
  • 一个数在按位或运算时,会先将其转换为 32 位的整数(二进制),这个过程就会把小数转换为整数
  • 然后这个整数 | 0 永远得到这个整数。因为 0 和 1 与 0 做或运算,都得到自身。

image-20220914145822293

结论:任何数与 0 做或|运算,最后结果都为这个数的整数部分。

# 9、按位非 ~ 操作符

TIP

  • 按位非~操作符会先把值转换为 32 位整数(二进制数),再运算
  • 按位非~操作符,用来反转操作数对应的位数。当前位是 0,就变成 1,1 就变成 0
  • 其最终结果的呈现是将一个数取反减 1
var num1 = 5;
console.log(~num1); // 5取反为-5 ,然后再减1,得到-6

var num2 = -5;
console.log(~num2); // -5取反为5,然后再减1,得到4

~ 5 的计算的过程

  • 5 的 二进制是 00000000000000000000000000000101
  • 反转后是:11111111111111111111111111111010
  • 反转后是一个负数,负数转换为 10 进制
    • 先减 1,得到 11111111111111111111111111111001
    • 再反转,得到00000000000000000000000000000110
    • 最后结果就是 6,因为是负数,所 最后结果是 -6

# 按位非 ~ 应用

TIP

将一个数(整数)两次按位非运算,就能将这个数取反

推导过程如下:

~~ x = -(-x-1)-1 = x+1-1 = x

var num1 = 5.432;
console.log(~~num1);
// 第一次取反 先将 5.432 转成 整数5 ,再取返为-5,再-1,得到-6
// 第二次取反,-6取反得到6,6再-1 ,得到 5

取得一个数的相反数~x + 1

var a = 5;
console.log(~a + 1); // -5

# 10、按位异或 ^

TIP

  • 按位异或^操作符,会先把值转为 32 位整数(二进制数),再运算
  • 按位异或^在做运算时,就是将两个操作数的每一位对齐,然后按下表中的规则,对每一位执行相应的操作
第一个数的位 第二个数的位 结果
1 1 0
1 0 1
0 1 1
0 0 0

只有当两个数对应的位都是 1 或都是 0 时,返回 0,其它都返回 1

# 10.1、按位异或^ 运算过程

TIP

  • 我们来看下面两个数的按位异或^的运算过程
var result = 5 ^ 3;
console.log(result); // 6
  • 先把 5 和 3 转换为二进制数,再一位一位来运算,如下

image-20220914160448946

得到二进制 110,其对应 10 过制是 6

# 10.2、按位异或^ (归零律)

TIP

  • 归零律: 一个数异或自已得到 0 ,即 a ^ a=0 ,因为只有两个数上对应位数是一样时,才会得到 0
  • 用来判两个数是否相等,如果 a^b=0,则 a=b
5 ^ 5; // 0
3 ^ 3; // 0

image-20220914161808327

# 10.3、按位异或^ (恒等律)

TIP

  • 恒等律:a ^ 0= a (整数),自已异或 0,得到自己
var num1 = 5;
console.log(num1 ^ 0); // 5
var num2 = 6.3;
console.log(num2 ^ 0); // 6

# 10.4、按位异或 ^ (自反)

TIP

  • 自反:a ^ a ^ a= 0 ^ a= a ,一个(整数)异或自身 2 次,得到自身。
5 ^ 5 ^ 5; // 5
  • 结合律 : a ^ b ^c = c ^ b ^ a ,计算结果一样,与先后顺序无关

# 10.5、按位异或 ^ (用来交换两个数值变量的值)

经典面试题

变量为数字,在不增加临时变量时,交换两个变量的值

var a = 5;
var b = 10;
a ^= b;
b ^= a;
a ^= b;
console.log(a, b); // 10 5
/*
 * 整个推演过程如下:
 * 1、 a ^ = b 得到 a = a ^ b
 * 2、 b ^ = a 得到 b = b ^ a 在第1步得到a = a ^ b,则推倒出b = b ^ a ^ b=b ^ b ^ a =0 ^ a =a
 * 3、 a ^ = b 得到 a = a ^ b ,在第1步得到a = a^b,第2步得到b = a,则推倒出: a = a ^ b ^ a = b ^ b ^ a = 0 ^ b = b
 *
 */

var a = 10.55;
var b = 20.66;
a ^= b; // a = a ^ b
b ^= a; // b = b ^ a = b ^ a ^ b =b ^  b ^ a= 0 ^  a= a
a ^= b; // a= a ^ a = a ^ b ^ a =b
console.log(a, b); // 20 10

# 11、位移操作符(>><<>>>

TIP

前面我们知道了解如何将一个数转换为二进制,是基于这个数是一个正数,那如果是一个负数呢 ?其对应的二进是如何转换的呢 ?比如:

  • 5 的二进制是:101
  • -5的二进制是多少 ? 可不能简单的认为是-101,这样看,那肯定是错的。

# 11.1、<< 左移操作符

TIP

  • 左移操作符用两个小于号<< 表示,会按指定的位数将数值的所有位向左移动。
  • 左移后,左边移出去的 5 位去掉,右端空出的位数会以 0 来填充这些空位。
// 5 左移 5位
5 << 5; // 160
  • 5 << 5 的整个运算过程如下图

先将 5 转成二进制,再左移 5 位,把左移的 5 位去掉,右边空出的 5 位用 0 来填充

image-20220914172825585

注:

在有符号整数中,第 32 位中的第 32 位是 符号位

  • 如果是 0 表示正数
  • 如果是 1 表示负数,在左移时,会保留操作数的符号

# 11.2、>> 有符号 右移操作符

TIP

  • 有符号右移由两个大于号>>表示,会将数值的所有 32 位都向右移。同时保留符号(正和负)
  • 有符号右移,左边空出的位会在左侧,在符号位后符号位的值来填充这些空位。

案例一:

160 >> 5 有符号右移 5 位的运算过程如下

160 >> 5; // 5

160 转换成二进制数是 10100000 ,右移后,左边空出 5 位,空出 5 位在符号位后,用符号位的值来填充,这里符号位的值是 0,所以就用 0 来填充

image-20220914182021192

案例二:

-5 >> 5 有符号右移 5 位的运算过程如下

-5 >> 5;
  • 首先要计算得到-5 的二进制数
  • 5 的二进制00000000000000000000000000000101 补码后11111111111111111111111111111010
  • 补码后 +1 得到 11111111111111111111111111111011
  • -5 的二进制,最终是 11111111111111111111111111111011
  • 然后向右移动 5 位,内部计算过程如下:
  • 先右移,再左侧符号位后补 5 个 1,得到最后二进制 11111111111111111111111111111111
  • 上面得到的二进制是一个负数,所以要先-1,再以补码,最后得到00000000000000000000000000000001
  • 所以结果为 1,因为是负数,所以最后是 - 1

image-20220914211504288

image-20220914213351718

# 11.3、>>> 无符号右移操作符

TIP

  • 无符号右移会将数值的所有 32 位都向右移,位移造成的左侧空位全补 0.
  • 所以对于正数,>>>无符号右移 和有符号右移>>的结果是相同的。因为正数的符号位是 0,所以两者都是补 0 的方式来填充右移造成的空位。
  • 但是负数,就完全不一样了。

案例 一:

55 无符号右移 5 位运算过程如下

55 >>> 5;
  • 55 的二进制是:00000000000000000000000000110111
  • >>>无符号右移 5,左侧造成的空位 0 来补,则得到 00000000000000000000000000000001

最后得到的值是1

image-20220914213443280

案例 二:

-55 无符号右移 5 位运算过程如下

-55 >>> 5;
  • -55 的二进制是:11111111111111111111111111001001
  • >>>无符号右称 5 位,左侧造成空位 0 来补,则得到00000111111111111111111111111110
  • 最终结果为00000111111111111111111111111110 得到对应的 10 进制是134217726

负数,在无符号位移后(至少 1 位),会被转换成一个正数

image-20220914214241501

# 12、位移操作符的应用

TIP

在接下来的位操作符中会用到随机函数,所以这里我们先来学习下随机数函数

# 12.1、随机数函数

TIP

Math.random()方法,可以得到 0-1 之间的小数

Math.random(); // 输出0~1之间的随机数

image-20211218223539723

  • 得到 [a , b] 区间的整数,公式如下
parseInt(Math.random() * (b - a + 1)) + a;
  • 得到[1 ,5]区间的整数
parseInt(Math.random() * 5) + 1;

image-20211218224225733

  • 得到[5 ,15]区间的整数
parseInt(Math.random() * 11) + 5;

image-20220910214116295

# 12.2、 如何随机生成随机色(经典面试题)

// rgb颜色随机
function rgb() {
  var r = Math.floor(Math.random() * 256);
  var g = Math.floor(Math.random() * 256);
  var b = Math.floor(Math.random() * 256);
  var rgb = "(" + r + "," + g + "," + b + ")";
  return rgb;
}

// 十六进制颜色
var randomHex = function () {
  return (
    "#" +
    Math.round(Math.random() * 0xffffff)
      .toString(16)
      .padEnd(6, "0")
  );
};
console.log(randomHex());

// 十六进制颜色
const randomColor = function () {
  return "#" + Math.random().toString(16).substr(2, 6);
};
console.log(randomColor());

image-20220914234921389

image-20220914235714403

# 12.3、 GRB 颜色 转 16 进制颜色

RGB R 对应范围 G 对应范围 B 对应范围
rgb(0,24,255) 0-255 0-255 0-255
16 进制 前两位对应 R,取值范围 中间两位对应 G,取值范围 最后两位对应 B,取值范围
#05f3df 00-ff 00-ff 00-ff

image-20220921192210649

RGB 颜色转 16 进制原理

将 rgb 的值,转成 32 位的二进制,然后再将 32 位二进制转成对应的 16 进制

image-20220922150943892

过程分析:

  • rgb(2,33,55) 转 16 进制,本质就是
    • r的值要被转成 16 进制的前两位上的值,要丢高位,丢 16 位,具体看下图
    • g'的值要被转成 16 进制中间两位上的值,要丢高位,丢 8 位,具体看下图
    • b的值要被转成 16 进制后两位上的值,不动
  • 然后三者|运算,得到对应 二进制,然后再把二进制转成对应 16 进制值。

image-20220917151435092

// rgb颜色转 16进制颜色
function colorRGBToHex(rgb) {
  // rgbArr=['','2','33','55']
  var rgbArr = rgb.split(/[^\d]+/);
  // r 移掉丢掉高位   g移掉高位  b 不变
  var color = (rgbArr[1] << 16) | (rgbArr[2] << 8) | rgbArr[3];
  // color.toString(16) 的值,有可能不足6位,则需要向前补0
  var _color = color.toString(16); // 转换成16进制
  // padStart(6,'0'); // 不足6位,前面补0
  return "#" + _color.padStart(6, "0");
}
var hexColor = colorRGBToHex("rgb(2,33,55)");
console.log(hexColor); // #022137

toString 方法的三个作用

  • 将其它类型转换为字符串类型
true.toString(); // 'true'
var a = 10;
a.toString(); // '10'
  • 检测对象的类型
Object.prototype.toString.call(arr) === "[object Array]";
  • 返回该数字对应进制的字符串
(10).toString(2); // 10 专为2进制是 '1010'
(10).toString(16); // 10 转为 16制进是 'a'

# 12.4、16 进制转 RGB 颜色

TIP

16f 进制转 RGB 颜色,本质就是要把对应 16 进制的

  • 前 2 位转成 r 的值,
  • 中间 2 位转成 g 的值,
  • 后两位转成 b 的值

转换思路如下:

  • 把 16 进制转换成对应 32 位的 2 进制数,只要做位移运算,就会自动把操作数转成 32 位二进制。
  • 二进制右移 16 位,丢掉低 16 位,得到对应r的二进制,赋值时自动转成 10 进制
  • 二进制右移 8 位,丢掉低 8 位,然后 & 0xff 得到对应 g 的二进制,赋值时自动转成 10 进制
  • 二进制 & 0xff 得到对应 b 的二进制,赋值时自动转成 10 进制

image-20220917161007502

// 16进制颜色,转rgb
function colorHexToRGB(hex) {
  var newHex = hex.replace("#", "0x");
  var r = newHex >> 16;
  var g = (newHex >> 8) & 0xff;
  var b = newHex & 0xff;
  return "rgb(" + r + "," + g + "," + b + ")";
}
console.log(colorHexToRGB("#022137")); // rgb(2,33,55)
上次更新时间: 6/11/2023, 1:44:02 AM

大厂最新技术学习分享群

大厂最新技术学习分享群

微信扫一扫进群,获取资料

X