隐藏在"=="后面的JavaScript隐式转换

  逛遍各大程序社区论坛,不少自称编程“大牛”的人最喜欢调侃的语言就是JavaScript。这门被一周创建出来的动态语言被嘲讽没有任何编程的严谨性,其中最为被大家们津津乐道的就是JS中的"=="号。实际上你在使用时,经常会发现这样的情况:

// 一个数字既不是true也不是false
40 == true // false
40 == false // false
// 扑朔迷离的字符0
null == false // false
'0' ==  null // false
'0' ==  false // true

  乍一看非常荒谬且不合逻辑的背后,实际上是有一套严谨且规范的隐式转换规则。今天就来总结整理一下隐藏在"=="后面的JavaScript隐式类型转换的规律。

隐式与显式转换的概念

  所谓类型的隐式转换的概念,是相对于显式转换而产生的一个概念,不明显的,隐藏的类型转换,是动态语言独有的类型转换的概念。比如我现在要进行一个加法运算,3 + 3。正常情况下,在加号两边进行运算的应该是两个Number类型的值进行计算。而在JS中,加号两边实际上是可以允许任何类型的值进行计算的,比如字符串,数组。不是Number类型的值,甚至不是一个类型的值可以进行加法运算,是因为JS在运算的过程中自动的把加号两边的值进行类型转换,使得两边的值可以进行加法的处理。这种自动的类型转换,就被成为隐式的类型转换。而在其他静态语言当中,实际上需要手动把加好两边的值转换为Number类型,不然计算就会产生错误。这种手动的去操作值进行类型转换就被成为显式的类型转换。

引用类型到基本型的转换

  在整理隐式类型转换的规则前,需要简单过一下引用类型与基本型之间转换规则。JS当中所有的引用类型的prototype都是继承自Object,当引用类型需要转换为基本类型的时候,都会调用到Object上面的Symbol.toPrimitive方法进行类型转换,先把对象转换为字符串类型,再进行对应的基本类型转换。我们来看简单的例子:

// 声明数组变量
const demoArray = [1, 2];
// 把对象转换为字符串类型
demoArray + ''; // "1,2"

  我们现在来改写demoArray的Symbol.toPrimitive。

// @param {string} type 将要转换的数据类型,string类型将会为default
demoArray[Symbol.toPrimitive] = function(type) {
    console.log(type, 'type');
    return 'changedValue';
};

// 转换为字符串
demoArray + '';
// default type
// changedValue

// 转换为数字
Number(demoArray);
// number type
// NaN

  我们可以看到,无论引用类型是转换为字符串还是数字,都会调用Symbol.toPrimitive方法,该方法会接收到一个参数(将要转换为的类型),并且返回转换的结果。默认的Symbol.toPrimitive方法的逻辑,将会先调用引用类型原型链上的的valueOf方法,并返回基本型类的值。如果该方法返回了一个引用类型,将继续调用toString方法,并返回其值。我们来通过修改demoArray的valueOftoString方法来验证这一段逻辑。

demoArray.valueOf = function() {
    console.log('valueOf');
    return 'changedValueOf';
}
demoArray.toString = function() {
    console.log('toString');
    return 'changedtoString';
}

// 转换为字符串,会发现仅调用了 valueOf 方法
demoArray + '';
// valueOf
// changedValueOf

// 修改valueOf方法返回值为引用类型
demoArray.valueOf = function() {
    console.log('ObjectValueOf');
    return {};
}

// 继续转为字符串
demoArray + '';
// ObjectValueOf
// 调动到了toString
// toString
// changedtoString

'=='的转换规律

  '=='的转换规律,简单而言可以按照下面顺序去记性记忆:

  1. 引用类型与基本类型进行比较时,引用类型永远先使用Symbol.toPrimitive方法转换为基本类型。
  2. 基本类型下,非布尔值与布尔值比较,布尔值自身永远转换为数字类型。
  3. 数字类型和非数字类型比较,非数字类型永远转为数字类型进行比较。

  根据准则一,我们来简单分析比较一下空数组[]和空对象{}会和哪些值相等。

  首先等号两端均为引用类型,需要转换为基本类型。我们先来调用数组的Symbol.toPrimitive转换。数组上面valueOf方法总是返回它自身,toString方法,能够返回数组的内容项。因此空数组[],在调用Symbol.toPrimitive之后得到的是一个空的字符串’’;而对象在进行Symbol.toPrimitive转换时,valueOf返回对象自身,toString方法是返回一个'[object type]'的一个字符串。因此两端在进行基本类型转换之后,实际上是'''[object Object]'。接下来,根据这个转换原则,我们可以轻松得出[] == '''[object Object]' == {}这两个式值的答案是true。

  我们再来使用'' == false,来使用一下规则2和规则3。根据第二条原则,false首先需要转为数字类型,Number(false)为0。比较的两端为变更为'' == 0。我们再根据规则3,把数字和其他类型比较时,其他类型需要转换为数字类型,来把空字符串变为数字类型Numer('')0。这样比较的两端为0 == 0,最终结果为true。

  我们现在来综合三条规则,来推测一下[] == ![]的结果。这是一个很有趣的等式,根据常理![]应该是[]的相反值,所以按照常理这个等式结果应该是false。但是根据规则,首先应该计算等式右侧,也就是![]的值,所有引用类型转换为布尔值均为ture,因此![]的结果为false。等式两边比较变更为[] == false。根据规则1,我们需要把引用类型变更为基本类型来和基本类型比较,[]通过Symbol.toPrimitive转换为空字符串。等式两边变更为'' == false。根据规则2,布尔值需要转换为数字。等式两边变更为'' == 0。根据规则3,我们需要把空字符串变更为数字0,因此这道结果是0 == 0为true。

  记得在高中学习的时候,数学老师最常对我们说得话就是让我们不要想当然,一切题目的答案都需要建立在严谨的计算结果和推理上。在不了解严谨规范和定义的情况下,凭着自己其他语言所谓的经验进行理所当然的想当然,而肆意在各个公开场所嘲讽诟病JS的“所谓大牛”,不管其技术如何,在做人方面已经已经有所欠缺了。

  JavaScript这门语言动态语言在发展过程中,还需要背负着沉重的历史包袱,每次迭代和更新标准,都需要做到向下兼容,从严格模式引入开始就不难看出这一点。每天数亿计的程序和网页在使用JS解释器进行运转,JS不能像Python一样,能够随随便便就放弃掉2升级到3。作为一个前端开发者,要不断深入到JS的规范和原理当中去,发现JS看似动态不严谨的毛病下都是有着严谨的规范和成熟的思考的,要为自己所使用的的语言发声和感到骄傲。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值