资讯专栏INFORMATION COLUMN

【JS迷你书】类型转换之拆箱操作

fuyi501 / 2985人阅读

摘要:类型转换之装箱操作一文中说,因为是弱类型语言,我们可以像对待引用类型一样对基本类型数据进行引用类型才该有的属性获取操作。本文的主题关注相反的操作对引用类型进行那些基本类型才该有的操作时会怎样即,拆箱操作。

众所周知,JS 中共有 7 种数据类型:Undefined、Null、Boolean、Number、String、Symbol 和 Object。前 6 者是基本类型,Object 是引用类型。

《类型转换之装箱操作》一文中说,因为 JS 是弱类型语言,我们可以像对待引用类型一样对基本类型数据进行引用类型“才该有的”属性获取操作。

比如,如下的代码并不会报错:

var a = 1;
a.x = 2;

上述代码运行过程中,发生了“装箱操作”,通过阅读《ECMA-262》规范,我们知道浏览器内部是调用 ToObject 操作来实现的,它把基本类型包装成相应的引用类型。例如把 1 包装成了 new Number(1)。

本文的主题关注相反的操作:对引用类型进行那些基本类型“才该有的”操作时会怎样?即,“拆箱操作”。

比如,如下的代码并不会报错:

var a  = 1;
var b = {};
console.log(a - b);

对普通对象进行减法操作时,对象需要转化为数字类型。《Ecma-262 Edition 5.1》第11.6.2节对减法操作符规范如下:

The production AdditiveExpression : AdditiveExpression - MultiplicativeExpression is evaluated as follows:

  1. Let lref be the result of evaluating AdditiveExpression.

  2. Let lval be GetValue(lref).

  3. Let rref be the result of evaluating MultiplicativeExpression.

  4. Let rval be GetValue(rref).

  5. Let lnum be ToNumber(lval).

  6. Let rnum be ToNumber(rval).

  7. Return the result of applying the subtraction operation to lnum and rnum. See the note below 11.6.3.

上述操作中第 5、6 步比较关键,调用了内部操作 ToNumber:

Argument Type Result
Undefined NaN
Null +0
Boolean The result is 1 if the argument is true. The result is +0 if the argument is false.
Number The result equals the input argument (no conversion).
String See grammar and note below.
Object Apply the following steps:
1. Let primValue be ToPrimitive(input argument, hint Number).
2. Return ToNumber(primValue).

最后一行,处理Object时,经历两步:1. ToPrimitive。2. ToNumber。

ToPrimitive 操作正与 ToObject 相对,表示转化为基本类型:

Input Type Result
Undefined The result equals the input argument (no conversion).
Null The result equals the input argument (no conversion).
Boolean The result equals the input argument (no conversion).
Number The result equals the input argument (no conversion).
String The result equals the input argument (no conversion).
Object Return a default value for the Object. The default value of an object is retrieved by calling the [[DefaultValue]] internal method of the object, passing the optional hint PreferredType. The behaviour of the [[DefaultValue]] internal method is defined by this specification for all native ECMAScript objects in 8.12.8.

最后一行说,对象转化为基本类型时,是获取的对象的默认值。使用的是内部[[DefaultValue]](hint),规范原文引用如下(补充:本文中的英文都可以不看的,我都会仔细说明的):

When the [[DefaultValue]] internal method of O is called with hint String, the following steps are taken:

  1. Let toString be the result of calling the [[Get]] internal method of object O with argument "toString".

  2. If IsCallable(toString) is true then,
    1. Let str be the result of calling the [[Call]] internal method of toString, with O as the this value and an empty argument list.

    2. If str is a primitive value, return str.

  3. Let valueOf be the result of calling the [[Get]] internal method of object O with argument "valueOf".

  4. If IsCallable(valueOf) is true then,
    1. Let val be the result of calling the [[Call]] internal method of valueOf, with O as the this value and an empty argument list.

    2. If val is a primitive value, return val.

  5. Throw a TypeError exception.

When the [[DefaultValue]] internal method of O is called with hint Number, the following steps are taken:

  1. Let valueOf be the result of calling the [[Get]] internal method of object O with argument "valueOf".

  2. If IsCallable(valueOf) is true then,
    1. Let val be the result of calling the [[Call]] internal method of valueOf, with O as the this value and an empty argument list.

    2. If val is a primitive value, return val.

  3. Let toString be the result of calling the [[Get]] internal method of object O with argument "toString".

  4. If IsCallable(toString) is true then,
    1. Let str be the result of calling the [[Call]] internal method of toString, with O as the this value and an empty argument list.

    2. If str is a primitive value, return str.
      Throw a TypeError exception.

  5. When the [[DefaultValue]] internal method of O is called with no hint, then it behaves as if the hint were Number, unless O is a Date object (see 15.9.6), in which case it behaves as if the hint were String.

When the [[DefaultValue]] internal method of O is called with no hint, then it behaves as if the hint were Number, unless O is a Date object (see 15.9.6), in which case it behaves as if the hint were String.

上述算法是说,根据 hint 值采取不同的处理方式,比如 hint 是 String 时,优先调用对象的 toString 方法,如果返回值是基本类型值,返回该值,否则调用对象的 valueOf 方法,如果返回值是基本类型值,返回该值。否则报错。

hint 是 Number 时,顺序是反过来的,优先调用 valueOf,如果其返回值不是基本类型,再调用 toString。另外,除了日期对象外,如果没传 hint 的话,其默认值是 Number,因此 JS 中类型转化时,更偏爱 Number。

下面我们举几个例子看看:

var a = {
  toString() {
    return 3
  },
  valueOf() {
    return "30"
  }
};
console.log(a - 5); // 25

这里使用的是减法操作,此时 hint 是 Number,因此先调用对象 a 的 valueOf 方法,其返回值 "30" 是字符串类型,是基本类型。因此 a - 5 变成了 "30" - 5。

再看:

var a = {
  toString() {
    return {}
  },
  valueOfnull
};
console.log(a - 5); // Uncaught TypeError: Cannot convert object to primitive value

对象 a,其方法 valueOf 不是函数,因而看其 toString 方法,而该方法返回的是一个空对象,不是基本类型。因而报错。

再如:

var o = {
  toString() {
    return "now is: "
  },
  valueOffunction({
    return "时间是:"
  }
};
var d = new Date();
console.log(o + d); // 时间是:Mon May 06 2019 13:56:39 GMT+0800 (中国标准时间)

这里使用了加法操作:

The production AdditiveExpression : AdditiveExpression + MultiplicativeExpression is evaluated as follows:

  1. Let lref be the result of evaluating AdditiveExpression.

  2. Let lval be GetValue(lref).

  3. Let rref be the result of evaluating MultiplicativeExpression.

  4. Let rval be GetValue(rref).

  5. Let lprim be ToPrimitive(lval).

  6. Let rprim be ToPrimitive(rval).

  7. If Type(lprim) is String or Type(rprim) is String, then
    Return the String that is the result of concatenating ToString(lprim) followed by ToString(rprim)

  8. Return the result of applying the addition operation to ToNumber(lprim) and ToNumber(rprim). See the Note below 11.6.3.

其中第 5、6 步直接获取加号两边的基本类型。此时没有都传递 hint,o 是普通对象,因此默认 hint 是 Number,使用的 valueOf 的返回值。而 d 是日期对象,默认 hint 是 String,优先调用的是其 toString 方法。然后根据第 7 步,采用的是字符串拼接方法。

加法操作,这里再举一例:

var o = {
   toStringfunction({
    return 2
  }
};
console.log(o + o); // 4

这里不过多解释了。

ToPrimitive 除了在四则运算中大量用到外,关系运算中也经常使用。比如 == 操作。其他类型转换等相关知识留给后续文章吧。

至此,“拆箱”已经说完了。

本文完。

《JavaScript 迷你书》传送门,全面夯实基础

掘金收藏

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/7325.html

相关文章

  • 关于Promise

    摘要:反之,操作失败,对象由状态转换为状态,此时回调函数会执行方法。这里需要注意的是,虽然在之后便执行了方法,但是并不是意味着往后的对象不执行了,其他的还是对象还是要执行的,只是不会再调用函数。 在 掘金上看见一篇写promise的文章,感觉作者写的很棒,文章链接在这:八段代码彻底掌握 Promise 。看完之后感觉学到了很多,所以又重新把JavaScript Promise迷你书(中文版)...

    546669204 评论0 收藏0
  • JavaScript隐式类型转换

    摘要:所谓装箱转换,正是把基本类型转换为对应的对象,他是类型转换中一种相当重要的种类。拆箱转换在标准中,规定了函数,它是对象类型到基本类型的转换即,拆箱转换。拆箱转换会尝试调用和来获得拆箱后的基本类型。 JavaScript隐式类型转换 基本数据类型 ECMAScript 一共定义了七种 build-in types,其中六种为 Primitive Value,Null, Undefined...

    bingo 评论0 收藏0
  • Java编译期优化思维导图

    摘要:本文参考自来自周志明深入理解虚拟机第版,拓展内容建议读者可以阅读下这本书。和构造方法一一对应,是同一概念在两个级别的含义收敛的操作自动保证执行父类的执行语句块初始化类变量字符串加操作替换为或的操作 showImg(https://segmentfault.com/img/remote/1460000016240419?w=3876&h=3614); 本文参考自来自周志明《深入理解Jav...

    sorra 评论0 收藏0
  • JS进阶】你真的掌握变量和类型了吗

    摘要:本文从底层原理到实际应用详细介绍了中的变量和类型相关知识。内存空间又被分为两种,栈内存与堆内存。一个值能作为对象属性的标识符这是该数据类型仅有的目的。 导读 变量和类型是学习JavaScript最先接触到的东西,但是往往看起来最简单的东西往往还隐藏着很多你不了解、或者容易犯错的知识,比如下面几个问题: JavaScript中的变量在内存中的具体存储形式是什么? 0.1+0.2为什...

    fuyi501 评论0 收藏0
  • 正则表达式迷你-笔记

    摘要:使用看完你就会正则表达式了四种操作验证切分提取替换第一章正则表达式字符匹配攻略正则表达式是匹配模式,要么匹配字符,要么匹配位置至少,至多匹配中的任一个字符范围表示法如果要匹配则要么要么要么通配符,表示几乎任意 API 使用 String#search String#split String#match String#replace RegExp#test Reg...

    IamDLY 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<