JavaScript中运算符规则和隐式类型转换示例详解


Posted in Javascript onSeptember 06, 2017

前言

本文主要给大家介绍了关于JavaScript运算符规则和隐式类型转换的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧。

隐式类型转换

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

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

// 比较
[] == ![] // true
NaN !== NaN // true

1 == true // true
2 == true // false
"2" == true // flase

null > 0 // false
null < 0 // false
null == 0 // false
null >= 0 // true

// 加法
true + 1 // 1
undefined + 1 // NaN

let obj = {};

{} + 1 // 1,这里的 {} 被当成了代码块
{ 1 + 1 } + 1 // 1

obj + 1 // [object Object]1
{} + {} // Chrome 上显示 "[object Object][object Object]",Firefox 显示 NaN

[] + {} // [object Object]
[] + a // [object Object]
+ [] // 等价于 + "" => 0
{} + [] // 0
a + [] // [object Object]

[2,3] + [1,2] // '2,31,2'
[2] + 1 // '21'
[2] + (-1) // "2-1"

// 减法或其他操作,无法进行字符串连接,因此在错误的字符串格式下返回 NaN
[2] - 1 // 1
[2,3] - 1 // NaN
{} - 1 // -1

原始类型间转换

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

// String
let value = true;
console.log(typeof value); // boolean

value = String(value); // now value is a string "true"
console.log(typeof value); // string

// Number
let str = "123";
console.log(typeof str); // string

let num = Number(str); // becomes a number 123

console.log(typeof num); // number

let age = Number("an arbitrary string instead of a number");

console.log(age); // NaN, conversion failed

// Boolean
console.log( Boolean(1) ); // true
console.log( Boolean(0) ); // false

console.log( Boolean("hello") ); // true
console.log( Boolean("") ); // false

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

原始值 转化为数值类型 转化为字符串类型 转化为 Boolean 类型
false 0 "false" false
true 1 "true" true
0 0 "0" false
1 1 "1" true
"0" 0 "0" true
"1" 1 "1" true
NaN NaN "NaN" false
Infinity Infinity "Infinity" true
-Infinity -Infinity "-Infinity" true
"" 0 "" false
"20" 20 "20" true
"twenty" NaN "twenty" true
[ ] 0 "" true
[20] 20 "20" true
[10,20] NaN "10,20" true
["twenty"] NaN "twenty" true
["ten","twenty"] NaN "ten,twenty" true
function(){} NaN "function(){}" true
{ } NaN "[object Object]" true
null 0 "null" false
undefined NaN "undefined" false

ToPrimitive

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

ToPrimitive(input, PreferredType?)

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

var ToPrimitive = function(obj,preferredType){
 var APIs = {
 typeOf: function(obj){
  return Object.prototype.toString.call(obj).slice(8,-1);
 },
 isPrimitive: function(obj){
  var _this = this,
   types = ['Null','Undefined','String','Boolean','Number'];
  return types.indexOf(_this.typeOf(obj)) !== -1; 
 }
 };
 // 如果 obj 本身已经是原始对象,则直接返回
 if(APIs.isPrimitive(obj)) {return obj;}
 
 // 对于 Date 类型,会优先使用其 toString 方法;否则优先使用 valueOf 方法
 preferredType = (preferredType === 'String' || APIs.typeOf(obj) === 'Date' ) ? 'String' : 'Number';
 if(preferredType==='Number'){
 if(APIs.isPrimitive(obj.valueOf())) { return obj.valueOf()};
 if(APIs.isPrimitive(obj.toString())) { return obj.toString()};
 }else{
 if(APIs.isPrimitive(obj.toString())) { return obj.toString()};
 if(APIs.isPrimitive(obj.valueOf())) { return obj.valueOf()};
 }
 throw new TypeError('TypeError');
}

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

let obj = {
 valueOf:() => {
  return 0;
 }
}

obj + 1 // 1

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

obj = {
  valueOf: function () {
   console.log("valueOf");
   return {}; // not a primitive
  },
  toString: function () {
   console.log("toString");
   return {}; // not a primitive
  }
 }

obj + 1
// error
Uncaught TypeError: Cannot convert object to primitive value
 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] 方法的签名为:

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

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

user = {
 name: "John",
 money: 1000,

 [Symbol.toPrimitive](hint) {
 console.log(`hint: ${hint}`);
 return hint == "string" ? `{name: "${this.name}"}` : this.money;
 }
};

// conversions demo:
console.log(user); // hint: string -> {name: "John"}
console.log(+user); // hint: number -> 1000
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 函数将两个操作数转化为原始类型:

prim1 := ToPrimitive(value1)
prim2 := ToPrimitive(value2)

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

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
JavaScript中this关键字使用方法详解
Mar 08 Javascript
JavaScript ( (__ = !$ + $)[+$] + ({} + $)[_/_] +({} + $)[_/_] )
Feb 25 Javascript
模仿百度三维地图的js数据分享
May 12 Javascript
基于JavaScript 声明全局变量的三种方式详解
May 07 Javascript
javascript内存管理详细解析
Nov 11 Javascript
JavaScript 学习笔记之变量及其作用域
Jan 14 Javascript
jquery实现textarea输入框限制字数的方法
Jan 15 Javascript
jquery地址栏链接与a标签链接匹配之特效代码总结
Aug 24 Javascript
详解JavaScript设计模式开发中的桥接模式使用
May 18 Javascript
jQuery自定义插件详解及实例代码
Dec 29 Javascript
vue自动化表单实例分析
May 06 Javascript
浅谈Vue的响应式原理
May 30 Javascript
详解Vue.js组件可复用性的混合(mixin)方式和自定义指令
Sep 06 #Javascript
轻松玩转BootstrapTable(后端使用SpringMVC+Hibernate)
Sep 06 #Javascript
vue mixins组件复用的几种方式(小结)
Sep 06 #Javascript
JavaScript 中Date对象的格式化代码方法汇总
Sep 06 #Javascript
jquery实现限制textarea输入字数的方法
Sep 06 #jQuery
Vue的百度地图插件尝试使用
Sep 06 #Javascript
表格展示利器 Bootstrap Table实例代码
Sep 06 #Javascript
You might like
详解PHP用substr函数截取字符串中的某部分
2016/12/03 PHP
jquery 如何动态添加、删除class样式方法介绍
2012/11/07 Javascript
Jquery刷新页面背景图片随机变换的实现方法
2013/03/15 Javascript
使图片旋转的3种解决方案
2013/11/21 Javascript
JavaScript检测上传文件大小的方法
2015/07/22 Javascript
JavaScript使用encodeURI()和decodeURI()获取字符串值的方法
2015/08/04 Javascript
js实现textarea限制输入字数
2017/02/13 Javascript
js实现楼层导航功能
2017/02/23 Javascript
jQuery插件FusionCharts绘制的2D帕累托图效果示例【附demo源码】
2017/03/28 jQuery
详解webpack+express多页站点开发
2017/12/22 Javascript
Node.js实现注册邮箱激活功能的方法示例
2018/03/23 Javascript
详解Nodejs mongoose
2018/06/10 NodeJs
jQuery+PHP实现上传裁剪图片
2020/06/29 jQuery
在vue中使用G2图表的示例代码
2019/03/19 Javascript
Vue 中可以定义组件模版的几种方式
2019/08/06 Javascript
vue实现轮播图帧率播放
2021/01/26 Vue.js
Python中创建字典的几种方法总结(推荐)
2017/04/27 Python
微信小程序跳一跳游戏 python脚本跳一跳刷高分技巧
2018/01/04 Python
pandas的object对象转时间对象的方法
2018/04/11 Python
python频繁写入文件时提速的方法
2019/06/26 Python
python-Web-flask-视图内容和模板知识点西宁街
2019/08/23 Python
python使用pymongo与MongoDB基本交互操作示例
2020/04/09 Python
解决Python中导入自己写的类,被划红线,但不影响执行的问题
2020/07/13 Python
浅谈对python中if、elif、else的误解
2020/08/20 Python
运行python提示no module named sklearn的解决方法
2020/11/29 Python
美国高档百货Nordstrom的折扣店:Nordstrom Rack
2017/11/13 全球购物
结构和类有什么异同
2012/07/16 面试题
交通专业个人自荐信格式
2013/09/23 职场文书
模具设计与制造专业应届生求职信
2013/10/18 职场文书
机械设计及其自动化专业推荐信
2013/10/31 职场文书
教师业务学习制度
2014/01/25 职场文书
甜品蛋糕店创业计划书范文
2014/02/06 职场文书
材料加工工程求职信
2014/02/19 职场文书
国际贸易实务实训报告
2014/11/05 职场文书
业务员岗位职责范本
2015/04/03 职场文书
使用pandas或numpy处理数据中的空值(np.isnan()/pd.isnull())
2021/05/14 Python