# JavaScript 表达式、操作符、位运算符
TIP
本节课我们来学习表达式和操作符。那什么是表达式和操作符呢?
我们来看一个例子:以下表达式是由两个操作数和一个操作符组合而成
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 种表达式。
- 每种表达式就有与之相匹配的操作符。
接下来,我们就来学习下这 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
<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 也就是A
的Unicode
编码是65
、Z
的Unicode
编码是90
a~z
对应 97~1220~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>
以下代码,最终的输出结果是 ?
<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>
自己先分析,再点击查看正确答案
输出结果: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、表达式种类,每种表达式分别有哪些操作符
算术表达式 | 赋值表达式 | 关系表达式 | 逻辑表达式 |
---|---|---|---|
当然还有综合表达式,是所有上述表达式中对应的操作符的综合应用。
# 2、每种表达式中运算顺序是什么? 综合运算顺序是什么?
注:
在做运算时,要特别注意 &&
和||
的短路计算,还有++a
、a++
、--a
、a--
的特性。
可以用下面这道题再来检验下自己是否理解了。
<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 对应 百,十,个位上的数呢 ?
转换方法,用当前数 /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、二进制如何转换成十进制
# 二进制转十进制计算公式
TIP
b0
表示二进制右边第1
位上的数字b1
表示二进制右边第2
位上的数字b2
表示二进制右边第3
位上的数字- ...... 依次类推
5 的二进制计算过程
5 的二进制数: 00000000000000000000000000000101
可以简写成101
转换成 10 进制,计算过程如下:
从上面可以得到,当前位上的数字如果是 0,计算得到的结果就是 0
1010
对应 10 进制 0+1*21+ 0 + 1*23 =2+8=1011101
对应 10 进制 1+0+1*22+1*23+1*24=1+0+4+8+16=29
# 3、负十进制数如何转换成二进制
TIP
- 负数和正数的存储方式不一样,负数是以一种二补数(或补码)的二进制编码存储。
- 我们来看下,负数是如何转成对应二进制数,然后存储的。这里以
-5
来为例
-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 进制数,在后面我们马上就会学习到他。
负数对应二进制如下:
十进制 | 对应正数二进制 | 对应负数二进制 |
---|---|---|
3 | 11 | 11111111111111111111111111111101 |
9 | 1001 | 11111111111111111111111111110111 |
12 | 1100 | 11111111111111111111111111110100 |
# 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
负二进制转十进制
案例如下:
var num1 = 11111111111111111111111111111101;
- 1、num1 的二进制第 32 位是 1,则是一个负数,负数就要以补码的方式反转回去
- 2、先拿 二进制
11111111111111111111111111111101-1
得到11111111111111111111111111111100
- 3、再把二上面得到的二进制反码回去,0 变 1,1 变 0,得到
00000000000000000000000000000011
- 4、最后得到的二进制是 11,转换对应 10 进制是 3,因为是负数,所以最后结果为
-3
# 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
# 7.2、按位&操作符判断奇偶数 (经典面试题)
TIP
- 如果 一个数 & 1 == 1 这个数是奇数
- 如果 一个数 & 1 == 0 这个数是偶数
(5 & 1) == 1; // 5 是奇数
(4 & 1) == 0; // 4 偶数
我们来分析下,其背后的逻辑是什么 ?
TIP
让我们再来回顾下,二进制转 10 进制的公式:
通过上面公式,我们知道,除第 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
计算过程如下:
# 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 位的比较
# 8.2、|
或运算用将一个数取整
TIP
var num = 5.467;
console.log(num | 0); // 5
- 一个数在按位或运算时,会先将其转换为 32 位的整数(二进制),这个过程就会把小数转换为整数
- 然后这个整数 | 0 永远得到这个整数。因为 0 和 1 与 0 做或运算,都得到自身。
结论:任何数与 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
- 先减 1,得到
# 按位非 ~ 应用
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 转换为二进制数,再一位一位来运算,如下
得到二进制 110,其对应 10 过制是 6
# 10.2、按位异或^ (归零律)
TIP
- 归零律: 一个数异或自已得到 0 ,即
a ^ a=0
,因为只有两个数上对应位数是一样时,才会得到 0 - 用来判两个数是否相等,如果 a^b=0,则 a=b
5 ^ 5; // 0
3 ^ 3; // 0
# 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 来填充
注:
在有符号整数中,第 32 位中的第 32 位是 符号位
- 如果是 0 表示正数
- 如果是 1 表示负数,在左移时,会保留操作数的符号
# 11.2、>> 有符号 右移操作符
TIP
- 有符号右移由两个大于号
>>
表示,会将数值的所有 32 位都向右移。同时保留符号(正和负) - 有符号右移,左边空出的位会在左侧,在符号位后用符号位的值来填充这些空位。
案例一:
160 >> 5 有符号右移 5 位的运算过程如下
160 >> 5; // 5
160 转换成二进制数是 10100000
,右移后,左边空出 5 位,空出 5 位在符号位后,用符号位的值来填充,这里符号位的值是 0,所以就用 0 来填充
案例二:
-5 >> 5 有符号右移 5 位的运算过程如下
-5 >> 5;
- 首先要计算得到-5 的二进制数
- 5 的二进制
00000000000000000000000000000101
补码后11111111111111111111111111111010
- 补码后 +1 得到
11111111111111111111111111111011
- -5 的二进制,最终是 11111111111111111111111111111011
- 然后向右移动 5 位,内部计算过程如下:
- 先右移,再左侧符号位后补 5 个 1,得到最后二进制
11111111111111111111111111111111
- 上面得到的二进制是一个负数,所以要先-1,再以补码,最后得到
00000000000000000000000000000001
- 所以结果为 1,因为是负数,所以最后是
- 1
# 11.3、>>> 无符号右移操作符
TIP
- 无符号右移会将数值的所有 32 位都向右移,位移造成的左侧空位全补 0.
- 所以对于正数,
>>>
无符号右移 和有符号右移>>的结果是相同的。因为正数的符号位是 0,所以两者都是补 0 的方式来填充右移造成的空位。 - 但是负数,就完全不一样了。
案例 一:
55 无符号右移 5 位运算过程如下
55 >>> 5;
- 55 的二进制是:
00000000000000000000000000110111
>>>
无符号右移 5,左侧造成的空位 0 来补,则得到00000000000000000000000000000001
最后得到的值是
1
案例 二:
-55 无符号右移 5 位运算过程如下
-55 >>> 5;
- -55 的二进制是:
11111111111111111111111111001001
>>>
无符号右称 5 位,左侧造成空位 0 来补,则得到00000111111111111111111111111110
- 最终结果为
00000111111111111111111111111110
得到对应的 10 进制是134217726
负数,在无符号位移后(至少 1 位),会被转换成一个正数
# 12、位移操作符的应用
TIP
在接下来的位操作符中会用到随机函数,所以这里我们先来学习下随机数函数
# 12.1、随机数函数
TIP
Math.random()方法,可以得到 0-1 之间的小数
Math.random(); // 输出0~1之间的随机数
- 得到 [a , b] 区间的整数,公式如下
parseInt(Math.random() * (b - a + 1)) + a;
- 得到[1 ,5]区间的整数
parseInt(Math.random() * 5) + 1;
- 得到[5 ,15]区间的整数
parseInt(Math.random() * 11) + 5;
# 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());
# 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 |
RGB 颜色转 16 进制原理
将 rgb 的值,转成 32 位的二进制,然后再将 32 位二进制转成对应的 16 进制
过程分析:
rgb(2,33,55)
转 16 进制,本质就是r
的值要被转成 16 进制的前两位上的值,要丢高位,丢 16 位,具体看下图g'
的值要被转成 16 进制中间两位上的值,要丢高位,丢 8 位,具体看下图b
的值要被转成 16 进制后两位上的值,不动
- 然后三者
|
运算,得到对应 二进制,然后再把二进制转成对应 16 进制值。
// 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 进制
// 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)
大厂最新技术学习分享群
微信扫一扫进群,获取资料
X