乐趣区

关于前端:细节决定成败聊聊JS的类型下

讲完了根本类型,咱们来介绍一个景象:类型转换。

因为 JS 是弱类型语言,所以类型转换产生十分频繁,大部分咱们相熟的运算都会先进行类型转换。大部分类型转换合乎人类的直觉,然而如果咱们不去了解类型转换的严格定义,很容易造成一些代码中的判断失误。其中最为臭名远扬的是 JavaScript 中的“==”运算,因为试图实现跨类型的比拟,它的规定简单到简直没人能够记住。这里咱们当然也不打算解说 == 的规定,它属于设计失误,并非语言中有价值的局部,很多实际中举荐禁止应用“==”,而要求程序员进行显式地类型转换后,用 === 比拟。其它运算,如加减乘除大于小于,也都会波及类型转换。

幸好的是,实际上大部分类型转换规定是非常简单的,如下表所示:

在这个外面,较为简单的局部是 Number 和 String 之间的转换,以及对象跟根本类型之间的转换。咱们别离来看一看这几种转换的规定。

StringToNumber

字符串到数字的类型转换,存在一个语法结构,类型转换反对十进制、二进制、八进制和十六进制,比方:

  • 30;
  • 0b111;
  • 0o13;
  • 0xFF。

此外,JavaScript 反对的字符串语法还包含正负号迷信计数法,能够应用大写或者小写的 e 来示意:

  • 1e3;
  • -1e-2。

须要留神的是,parseInt 和 parseFloat 并不应用这个转换,所以反对的语法跟这里不尽相同。

在不传入第二个参数的状况下,parseInt 只反对 16 进制前缀“0x”,而且会疏忽非数字字符,也不反对迷信计数法。

在一些古老的浏览器环境中,parseInt 还反对 0 结尾的数字作为 8 进制前缀,这是很多谬误的起源。所以在任何环境下,都倡议传入 parseInt 的第二个参数,而 parseFloat 则间接把原字符串作为十进制来解析,它不会引入任何的其余进制。

少数状况下,Number 是比 parseInt 和 parseFloat 更好的抉择。

NumberToString

在较小的范畴内,数字到字符串的转换是完全符合你直觉的十进制示意。当 Number 绝对值较大或者较小时,字符串示意则是应用迷信计数法示意的。这个算法细节繁多,咱们从理性的角度意识,它其实就是保障了产生的字符串不会过长。

具体的算法,你能够去参考 JavaScript 的语言规范。因为这个局部内容,我感觉在日常开发中很少用到,所以这里我就不去具体地解说了。
装箱转换
每一种根本类型 Number、String、Boolean、Symbol 在对象中都有对应的类,所谓装箱转换,正是把根本类型转换为对应的对象,它是类型转换中一种相当重要的品种。

前文提到,全局的 Symbol 函数无奈应用 new 来调用,但咱们仍能够利用装箱机制来失去一个 Symbol 对象,咱们能够利用一个函数的 call 办法来强制产生装箱。

咱们定义一个函数,函数外面只有 return this,而后咱们调用函数的 call 办法到一个 Symbol 类型的值上,这样就会产生一个 symbolObject。

咱们能够用 console.log 看一下这个货色的 type of,它的值是 object,咱们应用 symbolObject instanceof 能够看到,它是 Symbol 这个类的实例,咱们找它的 constructor 也是等于 Symbol 的,所以咱们无论从哪个角度看,它都是 Symbol 装箱过的对象:

var symbolObject = (function(){return this;}).call(Symbol("a"));
console.log(typeof symbolObject); //object
console.log(symbolObject instanceof Symbol); //true
console.log(symbolObject.constructor == Symbol); //true

装箱机制会频繁产生长期对象,在一些对性能要求较高的场景下,咱们应该尽量避免对根本类型做装箱转换。

应用内置的 Object 函数,咱们能够在 JavaScript 代码中显式调用装箱能力。

var symbolObject = Object(Symbol("a"));
console.log(typeof symbolObject); //object
console.log(symbolObject instanceof Symbol); //true
console.log(symbolObject.constructor == Symbol); //true

每一类装箱对象皆有公有的 Class 属性,这些属性能够用 Object.prototype.toString 获取:

var symbolObject = Object(Symbol("a"));
console.log(Object.prototype.toString.call(symbolObject)); //[object Symbol]

在 JavaScript 中,没有任何办法能够更改公有的 Class 属性,因而 Object.prototype.toString 是能够精确辨认对象对应的根本类型的办法,它比 instanceof 更加精确。

但须要留神的是,call 自身会产生装箱操作,所以须要配合 typeof 来辨别根本类型还是对象类型。

拆箱转换

在 JavaScript 规范中,规定了 ToPrimitive 函数,它是对象类型到根本类型的转换(即,拆箱转换)。

对象到 String 和 Number 的转换都遵循“先拆箱再转换”的规定。通过拆箱转换,把对象变成根本类型,再从根本类型转换为对应的 String 或者 Number。

拆箱转换会尝试调用 valueOf 和 toString 来取得拆箱后的根本类型。如果 valueOf 和 toString 都不存在,或者没有返回根本类型,则会产生类型谬误 TypeError。

var o = {valueOf : () => {console.log("valueOf"); return {}},
    toString : () => {console.log("toString"); return {}}
}
o * 2
// valueOf
// toString
// TypeError

咱们定义了一个对象 o,o 有 valueOf 和 toString 两个办法,这两个办法都返回一个对象,而后咱们进行 o*2 这个运算的时候,你会看见先执行了 valueOf,接下来是 toString,最初抛出了一个 TypeError,这就阐明了这个拆箱转换失败了。

到 String 的拆箱转换会优先调用 toString。咱们把方才的运算从 o*2 换成 String(o),那么你会看到调用程序就变了。

var o = {valueOf : () => {console.log("valueOf"); return {}},
    toString : () => {console.log("toString"); return {}}
}
String(o)
// toString
// valueOf
// TypeError

在 ES6 之后,还容许对象通过显式指定 @@toPrimitive Symbol 来笼罩原有的行为。

var o = {valueOf : () => {console.log("valueOf"); return {}},
    toString : () => {console.log("toString"); return {}}
}
o[Symbol.toPrimitive] = () => {console.log("toPrimitive"); return "hello"}
console.log(o + "")
// toPrimitive
// hello

结语

在本篇文章中,咱们介绍了 JavaScript 运行时的类型零碎。这里回顾一下明天解说的知识点。
除了这七种语言类型,还有一些语言的实现者更关怀的标准类型。

  • List 和 Record:用于形容函数传参过程。
  • Set:次要用于解释字符集等。
  • Completion Record:用于形容异样、跳出等语句执行过程。
  • Reference:用于形容对象属性拜访、delete 等。
  • Property Descriptor:用于形容对象的属性。
  • Lexical Environment 和 Environment Record:用于形容变量和作用域。
  • Data Block:用于形容二进制数据。

有一个说法是:程序 = 算法 + 数据结构,运行时类型蕴含了所有 JavaScript 执行时所须要的数据结构的定义,所以咱们要对它分外器重。

最初咱们留一个实际问题,如果咱们不必原生的 Number 和 parseInt,用 JavaScript 代码实现 String 到 Number 的转换,该怎么做呢?请你把本人的代码留言给我吧!

更多补充内容请看:开发者网站

退出移动版