JavaScript运算符规则与隐式类型转换详解

本文中涉及的参考资料全部声明在了JavaScript 数据结构学习与实践资料索引 。

隐式类型转换

在 JavaScript 中,当我们进行比较操作或者加减乘除四则运算操作时,常常会触发 JavaScript
的隐式类型转换机制;而这部分也往往是令人迷惑的地方。譬如浏览器中的 console.log
操作常常会将任何值都转化为字符串然后展示,而数学运算则会首先将值转化为数值类型(除了 Date 类型对象)然后进行操作。

我们首先来看几组典型的 JavaScript 中运算符操作结果,希望阅读完本部分之后能够对每一个条目都能进行合理解释:


  1. // 比较 
  2. [] == ![] // true 
  3. NaN !== NaN // true 
  4.  
  5. 1 == true // true 
  6. 2 == true // false 
  7. "2" == true // flase 
  8.  
  9. null > 0 // false 
  10. null < 0 // false 
  11. null == 0 // false 
  12. null >= 0 // true 
  13.  
  14. // 加法 
  15. true + 1 // 1 
  16. undefined + 1 // NaN 
  17.  
  18. let obj = {}; 
  19.  
  20. {} + 1 // 1,这里的 {} 被当成了代码块 
  21. { 1 + 1 } + 1 // 1 
  22.  
  23. obj + 1 // [object Object]1 
  24. {} + {} // Chrome 上显示 "[object Object][object Object]",Firefox 显示 NaN 
  25.  
  26. [] + {} // [object Object] 
  27. [] + a // [object Object] 
  28. + [] // 等价于 + "" => 0 
  29. {} + [] // 0 
  30. a + [] // [object Object] 
  31.  
  32. [2,3] + [1,2] // '2,31,2' 
  33. [2] + 1 // '21' 
  34. [2] + (-1) // "2-1" 
  35.  
  36. // 减法或其他操作,无法进行字符串连接,因此在错误的字符串格式下返回 NaN 
  37. [2] - 1 // 1 
  38. [2,3] - 1 // NaN 
  39. {} - 1 // -1 

原始类型间转换

JavaScript 中我们常说的原始类型包括了数值类型、字符串类型、布尔类型与空类型这几种;而我们常用的原始类型之间的转换函数就是 String、Number 与 Boolean:


  1. // String 
  2. let value = true; 
  3. console.log(typeof value); // boolean  
  4. value = String(value); // now value is a string "true" 
  5. console.log(typeof value); // string  
  6. // Number 
  7. let str = "123"; 
  8. console.log(typeof str); // string 
  9.  
  10. let num = Number(str); // becomes a number 123  
  11. console.log(typeof num); // number  
  12. let age = Number("an arbitrary string instead of a number");  
  13. console.log(age); // NaN, conversion failed  
  14. // Boolean 
  15. console.log( Boolean(1) ); // true 
  16. console.log( Boolean(0) ); // false  
  17. console.log( Boolean("hello") ); // true 
  18. console.log( Boolean("") ); // false 

最终,我们可以得到如下的 JavaScript 原始类型转换表(包括复合类型向原始类型转换的范例):

在比较运算与加法运算中,都会涉及到将运算符两侧的操作对象转化为原始对象的步骤;而 JavaScript 中这种转化实际上都是由
ToPrimitive 函数执行的。实际上,当某个对象出现在了需要原始类型才能进行操作的上下文时,JavaScript 会自动调用
ToPrimitive 函数将对象转化为原始类型;譬如上文介绍的 alert 函数、数学运算符、作为对象的键都是典型场景,该函数的签名如下:


  1. ToPrimitive(input, PreferredType?) 

为了更好地理解其工作原理,我们可以用 JavaScript 进行简单地实现:


  1. var ToPrimitive = function(obj,preferredType){ 
  2.   var APIs = { 
  3.     typeOf: function(obj){ 
  4.       return Object.prototype.toString.call(obj).slice(8,-1); 
  5.     }, 
  6.     isPrimitive: function(obj){ 
  7.       var _this = this, 
  8.           types = ['Null','Undefined','String','Boolean','Number']; 
  9.       return types.indexOf(_this.typeOf(obj)) !== -1;  
  10.     } 
  11.   }; 
  12.   // 如果 obj 本身已经是原始对象,则直接返回 
  13.   if(APIs.isPrimitive(obj)) {return obj;} 
  14.    
  15.   // 对于 Date 类型,会优先使用其 toString 方法;否则优先使用 valueOf 方法 
  16.   preferredType = (preferredType === 'String' || APIs.typeOf(obj) === 'Date' ) ? 'String' : 'Number'; 
  17.   if(preferredType==='Number'){ 
  18.     if(APIs.isPrimitive(obj.valueOf())) { return obj.valueOf()}; 
  19.     if(APIs.isPrimitive(obj.toString())) { return obj.toString()}; 
  20.   }else{ 
  21.     if(APIs.isPrimitive(obj.toString())) { return obj.toString()}; 
  22.     if(APIs.isPrimitive(obj.valueOf())) { return obj.valueOf()}; 
  23.   } 
  24.   throw new TypeError('TypeError'); 

我们可以简单覆写某个对象的 valueOf 方法,即可以发现其运算结果发生了变化:


  1. let obj = { 
  2.     valueOf:() => { 
  3.         return 0; 
  4.     } 
  5.  
  6. obj + 1 // 1 

如果我们强制将某个对象的 valueOf 与 toString 方法都覆写为返回值为对象的方法,则会直接抛出异常。


  1. obj = { 
  2.         valueOf: function () { 
  3.             console.log("valueOf"); 
  4.             return {}; // not a primitive 
  5.         }, 
  6.         toString: function () { 
  7.             console.log("toString"); 
  8.             return {}; // not a primitive 
  9.         } 
  10.     } 
  11.  
  12. obj + 1 
  13. // error 
  14. Uncaught TypeError: Cannot convert object to primitive value 
  15.     at <anonymous>:1:5 

值得一提的是对于数值类型的 valueOf() 函数的调用结果仍为数组,因此数组类型的隐式类型转换结果是字符串。而在 ES6 中引入
Symbol 类型之后,JavaScript 会优先调用对象的 [Symbol.toPrimitive]
方法来将该对象转化为原始类型,那么方法的调用顺序就变为了:

  • 当 obj[Symbol.toPrimitive](preferredType) 方法存在时,优先调用该方法;
  • 如果 preferredType 参数为 String,则依次尝试 obj.toString() 与 obj.valueOf() ;
  • 如果 preferredType 参数为 Number 或者默认值,则依次尝试 obj.valueOf() 与 obj.toString() 。

而 [Symbol.toPrimitive] 方法的签名为:


  1. obj[Symbol.toPrimitive] = function(hint) { 
  2.   // return a primitive value 
  3.   // hint = one of "string", "number", "default" 

我们同样可以通过覆写该方法来修改对象的运算表现:


  1. user = { 
  2.   name: "John", 
  3.   money: 1000, 
  4.  
  5.   [Symbol.toPrimitive](hint) { 
  6.     console.log(`hint: ${hint}`); 
  7.     return hint == "string" ? `{name: "${this.name}"}` : this.money; 
  8.   } 
  9. }; 
  10.  
  11. // conversions demo: 
  12. console.log(user); // hint: string -> {name: "John"} 
  13. console.log(+user); // hint: number -> 1000 
  14. console.log(user + 500); // hint: default -> 1500 

比较运算

JavaScript
为我们提供了严格比较与类型转换比较两种模式,严格比较(===)只会在操作符两侧的操作对象类型一致,并且内容一致时才会返回为 true,否则返回
false。而更为广泛使用的 == 操作符则会首先将操作对象转化为相同类型,再进行比较。对于 <=
等运算,则会首先转化为原始对象(Primitives),然后再进行对比。

标准的相等性操作符(== 与 !=)使用了 Abstract Equality Comparison Algorithm 来比较操作符两侧的操作对象(x == y),该算法流程要点提取如下:

  • 如果 x 或 y 中有一个为 NaN,则返回 false;
  • 如果 x 与 y 皆为 null 或 undefined 中的一种类型,则返回 true(null == undefined // true);否则返回 false(null == 0 // false);
  • 如果 x,y 类型不一致,且 x,y 为 String、Number、Boolean 中的某一类型,则将 x,y 使用 toNumber 函数转化为 Number 类型再进行比较;
  • 如果 x,y 中有一个为 Object,则首先使用 ToPrimitive 函数将其转化为原始类型,再进行比较。

我们再来回顾下文首提出的 [] == ![] 这个比较运算,首先 [] 为对象,则调用 ToPrimitive 函数将其转化为字符串 ""
;对于右侧的 ![] ,首先会进行显式类型转换,将其转化为
false。然后在比较运算中,会将运算符两侧的运算对象都转化为数值类型,即都转化为了 0,因此最终的比较结果为 true。在上文中还介绍了
null >= 0 为 true 的这种比较结果,在 ECMAScript 中还规定,如果 < 为 false,则 >= 为
true。

加法运算

对于加法运算而言,JavaScript 首先会将操作符两侧的对象转换为 Primitive
类型;然后当适当的隐式类型转换能得出有意义的值的前提下,JavaScript 会先进行隐式类型转换,再进行运算。譬如 value1 +
value2 这个表达式,首先会调用 ToPrimitive 函数将两个操作数转化为原始类型:


  1. prim1 := ToPrimitive(value1) 
  2. prim2 := ToPrimitive(value2) 

这里将会优先调用除了 Date 类型之外对象的 valueOf 方法,而因为数组的 valueOf
方法的返回值仍为数组类型,则会返回其字符串表示。而经过转换之后的 prim1 与 prim2
中的任一个为字符串,则会优先进行字符串连接;否则进行加法计算。

作者:佚名

来源:51CTO

时间: 2025-01-01 08:24:13

JavaScript运算符规则与隐式类型转换详解的相关文章

简单介绍JavaScript数据类型之隐式类型转换_javascript技巧

JavaScript的数据类型分为六种,分别为null,undefined,boolean,string,number,object.object是引用类型,其它的五种是基本类型或者是原始类型.我们可以用typeof方法打印来某个是属于哪个类型的.不同类型的变量比较要先转类型,叫做类型转换,类型转换也叫隐式转换.隐式转换通常发生在运算符加减乘除,等于,还有小于,大于等.. typeof '11' //string typeof(11) //number '11' < 4 //false 本章节单

浅析JavaScript中的隐式类型转换

        这篇文章主要是对JavaScript中的隐式类型转换进行了详细分析介绍,需要的朋友可以过来参考下,希望对大家有所帮助 如果把通过函数或方法调用,明确的将某种类型转换成另一种类型称为显示转换 ,相反则称为隐式类型转换 .google和维基百科中没有找到"显示类型转换","隐式类型转换"的字眼.暂且这么称呼.   一. 运算中存在的隐式类型转换    1, "+"运算符     代码如下: var a = 11, b = '22'; 

JavaScript隐式类型转换_javascript技巧

JavaScript的数据类型是非常弱的(不然不会叫它做弱类型语言了)!在使用算术运算符时,运算符两边的数据类型可以是任意的,比如,一个字符串可以和数字相加.之所以不同的数据类型之间可以做运算,是因为JavaScript引擎在运算之前会悄悄的把他们进行了隐式类型转换的,如下是数值类型和布尔类型的相加: 复制代码 代码如下: 3 + true; // 4 结果是一个数值型!如果是在C或者Java环境的话,上面的运算肯定会因为运算符两边的数据类型不一致而导致报错的!但是,在JavaScript中,只

总结Javascript中的隐式类型转换_javascript技巧

JavaScript的数据类型分为六种,分别为null,undefined,boolean,string,number,object.object是引用类型,其它的五种是基本类型或者是原始类型. 比如像是Number() ,还是parseInt() .parseFloat()都属于显示类型转换(强制类型转换): 这一节我们来看一下隐式类型转换(自动转换). 数值自动转换为字符串 var a = 123; alert(a+'456'); // 输出 123456 "+"号为连接符 字符串

浅析JavaScript中的隐式类型转换_javascript技巧

如果把通过函数或方法调用,明确的将某种类型转换成另一种类型称为显示转换 ,相反则称为隐式类型转换 .google和维基百科中没有找到"显示类型转换","隐式类型转换"的字眼.暂且这么称呼. 一. 运算中存在的隐式类型转换 1, "+"运算符 复制代码 代码如下: var a = 11, b = '22'; var c = a + b; 这里引擎将会先把a变成字符串"11"再与b进行连接,变成了"1122".

小心MySQL的隐式类型转换陷阱

1. 隐式类型转换实例 今天生产库上突然出现MySQL线程数告警,IOPS很高,实例会话里面出现许多类似下面的sql:(修改了相关字段和值) SELECT f_col3_id,f_qq1_id FROM d_dbname.t_tb1 WHERE f_col1_id=1226391 and f_col2_id=1244378 and f_qq1_id in (12345,23456,34567,45678,56789,67890,78901,89012,90123,901231,901232,90

C#的隐式类型转换

在C#语言中,一些预定义的数据类型之间存在着预定义的转换.比如,从int类型转换到long类型.C#语言中数据类型的转换可以用分为两类:隐式转换(implicit conversions)和显式转换(explicit conversions).本章我们将详细介绍这两类转换. 6.1 隐式类型转换 隐式转换就是系统默认的.不需要加以声明就可以进行的转换.在隐式转换过程中,编译器无需对转换进行详细检查就能够安全地执行转换.比如从int类型转换到long类型就是一种隐式转换.隐式转换一般不会失败,转换

C++中通过重载避免隐式类型转换

以下是一段代码,如果没有什么不寻常的原因,实在看不出什么东西: class UPInt { // unlimited precision public: // integers 类 UPInt(); UPInt(int value); ...};//有关为什么返回值是const的解释,参见Effective C++ 条款21const UPInt operator+(const UPInt& lhs, const UPInt& rhs);UPInt upi1, upi2;...UPInt

MySQL 的隐式类型转换

换 十六进制的值和非数字做比较时,会被当做二进制串,和数字做比较时会按下面的规则处理 有一个参数是 TIMESTAMP 或 DATETIME,并且另外一个参数是常量,常量会被转换为 timestamp 有一个参数是 decimal 类型,如果另外一个参数是 decimal 或者整数,会将整数转换为 decimal 后进行比较,如果另外一个参数是浮点数,则会把 decimal 转换为浮点数进行比较 所有其他情况下,两个参数都会被转换为浮点数再进行比较 注意一个安全问题:假如 password 类型