现今各种框架、工具‘横行’,到处在讲原理和源码,更有跨端技术需要我们去探索,但如果基本功不好,学什么都是事倍功半,效果很不好,花费时间的同时打击自信心。此篇文章,为我所计划的【轻聊前端】系列第四篇,旨在系统地、逻辑性地把原生JavaScript知识分享给大家,帮助各位较为轻松地理清知识体系,更好地理解和记忆,我尽力而为,望不负期待。
凡有数据参与且得出某种结果的,都叫运算——灵感
本系列文章讲求循序渐进,说完变量,聊完对象,是时候讲运算了,前文说过,程序就是在做“数据存储”和“数据处理”,存储说完就是处理,运算便是处理的方式。
四则运算(之外)
说起运算,不得不让人联想到数学,最熟悉的就是“加、减、乘、除”四则运算,你一定能想到,JavaScript当中也有这几种运算,因为有数字类型,但它远不止这四种,而且就这四种而言,跟数学当中也不完全一样,在某些场景下,会发生“神奇的转换“。为什么?别忘了,作为计算机,对给它的任何一行代码,一个字符,都需要做出反应,特别是JavaScript这种动态弱类型语言,相当宽容,会在不知道变量是什么的情况下尽量做出反应。
暂把“神奇的转换”留后,四则运算也不做赘述,先看四则运算之外它还有什么。
两元运算
什么是两元,“元”就是参与运算的数据的个数,两元即有两个数据参与运算。
(完全)等于/不等于
先说等于,这很简单,但新手程序员都需要一段时间适应,即JavaScript中的等于和数学中的不一样,需要用“==”符号,而不是“=”,“=”在JavaScript中代表“赋值”。
1 | if(a = 3){ |
这是把3赋给a,而不是判断两者是否相等,须多加注意。
另外,数学当中只有等于、大于、小于之类,没有”不等于“,而程序里加入了“不等于”,这就可以用一个运算统一两种情况。写法如下:
1 | a!=b |
按照功能来说,这么写就够了,但并不保险,因为一个小小的是否相等,浏览器都会试图对它进行类型转换之后再比较。比如
1 | 0 == ' ' //true 这里是空串儿,不是'' |
0和空格字符显然是两个概念,null 和 undefined 也不能等同。不能判定为相等,但返回是true,所以,在判断两个值是否相等时,建议使用“完全运算符”。示例如下:
1 | 0 === ‘ ’ //false |
完全运算符会在比较的同时检查类型,类型不一样就肯定不等。完全不等即“!==”。
这样就能准确判定了。
取模(余数)
相比之下,取模(%)运算不是很常见,它的效果是这样的:
1 | let result = 55 % 10; // 5 |
即看两数是否能整除,如果不能,得到余数。
应用场景有:判断是不是某个数的整数倍(本职),通过这个本职,可以判断一个数字是不是2的整数倍,进而判断“奇/偶”。
指数
以前,我们求一个数字的多少次方,可以用上一篇文提到的Math对象的pow方法:
1 | let result = Math.pow(3,2); //9 |
ES7之后引进了新的操作符——**,于是可以写成下面这样。
1 | let result = 3**2; |
书写上更简洁。
赋值
前面说了“=”是赋值,但除此之外还有一系列有关运算后赋值的简化写法。统一的表达式是:变量 运算符= 变量(常量)。
意思就是,将某变量进行运算之后再把值赋给原变量。示例如下:
1 | a += 1; //等同于 a = a+1 下同 |
逻辑操作
什么是逻辑?在JavaScript中逻辑运算得到的结果是“真/假”、“是/否”,即判断一个条件成立与否。
“&&”——与操作,均为真,结果才是真。
最为直观的:
1 | let result = true && false; //false |
但平时一般不会这么直接操作布尔值,而是使用其他运算得出的结果。比如:
1 | let a = 4; |
“||”——或操作,有一个为真即为真,否则是假。
1 | let a = 4; |
这两种运算符为日常开发的很多场景提供了便捷的判断方式,常用于流程控制中的条件判断,但有时候会被自己绕进去,比如,当需要a和b两种场景都适用时,可能会因为这个“和”字,本能地选择了“与”运算,即“&&”,这就不对了,都适用的意思是“a”或者“b”,要用“||”才对。
另外,逻辑运算符不仅仅能用来得到“是”或“否”,还有一种常见的用法,称为“短路运算”。
先看一段代码:
1 | let result; |
第一种情况,当a有值且不为false(包括隐式类型转换),result得到b,否则得到a。
第二种情况,当a有值且不为false(包括类型转换),就不往后看了,result得到a,否则得到b。
但上面的写法显得略繁琐,如果使用“短路运算”,可以像下面这样:
1 | let result = a && b; |
效果是一样的。
短路的写法为我们提供了赋值的“优先级”。逻辑运算的思路并没有改变,只是会发生赋值的动作。
所以,当我们要在两个值之间做取舍时,就可以考虑使用“短路算法”,一行代码搞定,简洁、清晰。
一元运算
先说两元是因为两元大家都熟悉,易接受,一元也很好理解,一个操作符,一个操作数。
+/-
“+”和”-“除了作为两元操作,同样可作为一元使用,最常见的就是“正负数”。
1 | +1 |
但是,如果不是直接放在数字的前面,而是变量的前面,就会有不同。
比如:
1 | let a = '1'; |
这段代码里,a原本是字符串的“1”,在前面加上了“+”或“-”号之后,就转变成了数字,为什么呢?因为在字符串前面加代表“正负”的符号是没有意义的,JavaScript会试图使其变得合理。
注意两个字“试图”,说明并不总成功,如果值没法转换成数字,就会失败。
1 | let a = 'a'; |
虽然一元操作符有这么一个功能,仍属于隐式转换的范畴,只有当拿到的值和想要的值的类型不匹配的时候才需要用到,如果对自己的操作很确定,且从代码的易读性出发的话,可以直接使用Number()方法进行显式转换。
++/–
说完一个的,说说两个的,两个叫“自增”或“自减”,因为没有另外的数据跟它结合,也正因为没有数据结合,每次都只能“自增”或“自减”1。
1 | a++ //自增1 |
功能简单,但放的位置就有讲究了,放前放后有区别。
1 | let result = a++; // 符号在后,先赋值后增加 |
这两种运算符常被用在需要重复执行的代码段,经过多次运算后达到一个临界值,再进行其他操作,或停止操作,最常见的就是for循环。
1 | for(let i = 0;i<=length;i++){ |
每循环一圈儿,i加1,直到达到length停止。
!
“!”叫“取反”运算符,按说它也属于逻辑运算,是对结果的值进行取反,结果是真,取反后是假,结果是假,取反后是真。
1 | let a = false; |
你可以在任何需要的时候使用“!”运算符,一个常用场景就是状态切换。
1 | let on = true; |
给需要切换状态的元素绑定点击事件,然后执行函数changeStatus,每点击一次,就会变为相反的值,从而达到反复点击切换状态的效果。
三元运算
三元运算,又叫“条件运算”。JavaScript当中三元运算符也很常见。格式如下:
表达式?值1:值2
示例:
1 | let a = 3; |
这段代码表达的是,当a的值大于2,取前值,否则取后值。即根据表达式是否成立来决定值是什么。
这个运算和前面聊的“||”运算的效果有相似的地方,各位可根据具体情况选择使用。
需要注意的点
+的连接性
关于“+”,我们习惯的作用是数字相加,但到了JavaScript领域,“+”还有另一个常见的作用是“连接字符串”。如下:
1 | let result = 'a'+'b'; // "ab" |
虽然字符串本身也有连接的方法,相比之下,“+”更直接和简洁。
但如果仅此而已,就没什么可注意的,JavaScript世界最大的不确定就是“拿到的变量是什么”。
如果是这样:
1 | let result = 1 + 'b'; |
数字和字符串相加,得到的什么呢?是“1b”。
可能你会说了,后面那个是b,没法转换成数字,才有的这个结果。那换一下:
1 | let result = 1 + '2'; |
结果是“12”,显然上面的猜测并不对。
当使用“+”号进行二元运算的时候,任何一方的数据是字符串类型,或者被转换成了字符串类型,结果都是字符串类型。比如:
1 | let result = ""; |
为什么单拎“+”号呢,因为只有“+”号有这个特殊作用,其他运算符没有。
字符串比较
在业务需求中,常有给一组数据按照大小排序的情况,这时候就有一个陷阱在等着我们——字符串的比较。
字符串比较有两个算法:
- 逐位比较,直到得出结果
- 比较的是ASCII码值
这样的算法就会出现如下情况:
1 | 100 < 20 //true |
事实上这个结果是不对的,我故意这样写,因为有时候,用眼睛看着是数字的,其类型可能是字符串,可能比较的是“100”和“20”,就会出现上面的情况。
用 charCodeAt() 方法可以查看字符的ASCII码值,’1’是49,而’2’是50。比较完第一位就已经分出大小了。
再看个例子:
1 | '13' > '123' //true |
首位1是相等的,第二位3的ASCII码比2的ASCII码大,出结果。
所以,在进行数据排序的时候,一定要注意类型,否则可能和预期不一致,数字如此,字母或其他特殊符号也是一样,不再赘述。
对象的隐式转换
面试题放送时间到~
问“什么情况下,a == 1 && a == 2 && a == 3 是成立的?“
这…看起来很不合常理,不是故意刁难吗?
先别急,我们讲一下上面一直没提过的东西。上面一直在说基本类型会转换,而没说对象。
拿值和对象作比较看似没有实用的地方,但程序依然允许运行,且会有相应的处理机制。
对象到数字(number)和字符串(string)类型值的转换,是直接与valueOf()以及toString()方法相关的。规则如下:
如果试图转换为字符串,则先尝试toString()方法,然后再尝试valueOf()方法。
否则,先尝试调用valueOf()方法,再尝试调用toString()。
有人要问了,这俩方法哪来的?还记得上篇文章的内容吗?它们是来自原型的内置方法。
于是,上面的题目便找到了使其成立的途径:
1 | let a = { |
可以利用类型转换时会调用valueOf(也可以是toString)方法的特性,在里面写我们想要的其他效果,就可以了。
明白了这个道理,是不是一点都不怕了?
当然,这道题不仅这一种方案,只是另一种方案稍微偏离主题,我们后面聊到对象的时候再深挖~
总结
JavaScript中的运算,说多不多,说少也不少,特别是隐式类型转换的处理机制,需要较多经验的积累,大坑没有,小坑不断。
鉴于篇幅原因,这里把不常用的和一些细节略了,但不管哪种情况,可概括为两条:
- 取值:不论数学计算,字符串连接,甚至“短路算法”,目的都是获取一个值,当无法得到合法值时,就会返回NaN或者报错之类期望之外的结果。
- 逻辑判断:使用“&&、||、!”或者隐式转换的情况,作为流程控制的条件判断。
这样以来,我们就只需要关心自己要做什么,每碰到一种意外情况就记下,慢慢就都清楚了。
说完变量(值的存储)、对象系统(编程的土壤)、运算(数据处理的方式)之后,接下来就是对不同的类型进行各个击破了。
下篇见~