JavaScript 类型转换篇总结

类型系统

JavaScript 存在两套类型系统,其一是基础类型系统,可以通过 typeof 来检测,该类型系统包括8种类型:

  • undefined 值
  • Number 类型
  • BigInt 大数类型
  • String 类型
  • Boolean 类型
  • Symbol 符号类型
  • Object 类型
  • function 类型

注:null 值是特殊存在,因为 typeof null === ‘object’。

其二是对象类型系统,可以通过 instanceof 来检测,对象类型系统是基础类型系统“object”的一个分支,例如:Object、Function、Array、RexExp 、Date 和 Promise 等。

类型检测

检测数据类型可以有以下几种方法:

  • typeof适合检测基础类型,对于基础类型来说,除了 null 都可以调用 typeof 显示正确的类型。但对于引用数据类型,除了函数之外,都会显示"object"。* instanceof用来检测引用数据类型,本质上是基于原型链判断。* constructor返回构造函数。除了 undefined 和 null 之外,其他类型都可以通过 constructor 属性来判断类型。* ** Object.prototype.toString.call()**返回格式为 [object xxx],是判断一个变量的类型最准确的方法。注:

一道面试题:为什么是Object.prototype.toString.call() ?

这是因为 toString 为 Object 的原型方法,而 Array ,function 等类型作为 Object 的实例,都重写了 toString 方法。不同的对象类型调用 toString 方法时,根据原型链的知识,调用的是对应的重写之后的 toString 方法( function 类型返回内容为函数体的字符串,Array 类型返回元素组成的字符串),而不会去调用 Object 上原型toString 方法(返回对象的具体类型),所以采用 obj.toString() 不能得到其对象类型,只能将 obj 转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用 Object 上原型 toString 方法。

包装类:面向对象的妥协

面向对象的语言通常认为“一切都是对象”。于是在“对象类型系统”中就出现了一个问题:如果是这样,那么 number 基础类型与 Number 对象类型,以及其他基础类型与相应的对象类型如何被统一呢?

为了实现“一切都是对象”的目标,JavaScript在类型系统上做出了一些妥协,其结果是:为部分基础类型系统中的“值类型”设定对应的包装类;然后通过包装类,将“值类型数据”作为对象来处理。

基础类型包装类
booleanBoolean
numberNumber
stringString
symbolSymbol

这样一来,基础类型数据通过包装类转换而来的结果,和对象类型系统中的每一个实例一样,都成了理论上的“对象”。

但需要注意的是,值类型数据经过"包装类"包装后得到的对象与原数据将不再是同一数据,只是两者具有等同的值而已。

显式包装

通过 Boolean()、Number()、String()、Symbol()、Object() 等显式的将四种值类型数据包装成对应的对象。

隐式包装

对于值类型数据来说,如果它用作普通求值运算或赋值运算,那么是以“非对象”的形式存在的。

'hello' + 'world!' 

但是当对值类型数据进行对象类型系统的相关运算时,比如成员属性的存取、方法的调用等,就需要包装成对象:

var x = 100;
​
// 成员属性的取
x.constructor
x['constructor']
​
// 方法调用
x.toString
​
// delete运算
delete x.toString 

但也有例外,比如:instanceof 运算不会对原数据进行包装。

对象 — 值类型的隐式转换

在进行数学运算的情况下,对象会被自动转换为原始值,然后对这些原始值进行运算,并得到运算结果。

这是一个重要的限制:因为 obj1 + obj2(或者其他数学运算)的结果不能是另一个对象!

例如,我们无法使用对象来表示向量或矩阵(或成就或其他),把它们相加并期望得到一个“总和”向量作为结果。这样的想法是行不通的。

因此,由于我们从技术上无法实现此类运算,所以在实际项目中不存在对对象的数学运算。如果你发现有,除了极少数例外,例如日期相减或比较(Date 对象),其他情况通常是写错了。

转换规则

1.转换为布尔值——所有的对象在布尔上下文(context)中均为 true,因此,只需关注字符串和数字转换。
2.转换为数值——发生在对象相减或应用数学函数时。
3.转换为字符串——发生在像 alert(obj) 这样输出一个对象和类似的上下文中。

如何决定应用哪种转换?

类型转换在各种情况下有三种变体。它们被称为 “hint”,在规范所述:

"string"

对象到字符串的转换,发生在当我们对期望一个字符串的对象执行操作时,如 “alert”,将对象作为属性键之类的情况下。

转换方法:obj[Symbol.toPrimitive](hint) --> obj.toString() --> obj.valueOf()

// 输出
alert(obj);
​
// 将对象作为属性键
anotherObj[obj] = 123; 

"number"

对象到数字的转换,例如当我们进行数学运算时(除了二元加法),以及大多数内建的数学函数。

转换方法:obj[Symbol.toPrimitive](hint) -->obj.valueOf() --> obj.toString()

// 显式转换
let num = Number(obj);
​
// 数学运算(除了二元加法)
let n = +obj; // 一元加法
let delta = date1 - date2;
​
// 小于/大于的比较
let greater = user1 > user2; 

"default"

在少数情况下发生,当运算符“不确定”期望值的类型时。

例如,二元加法 + 可用于字符串连接(当运算元包含字符串时),也可以用于数字(相加)。因此,当二元加法得到对象类型的参数时,它将依据 "default" hint 来对其进行转换。

此外,如果对象被用于与字符串、数字或 symbol 进行 == 比较,这时到底应该进行哪种转换也不是很明确,因此使用 "default" hint。

“default” 的转换方法与“number”一致,obj[Symbol.toPrimitive](hint) -->obj.valueOf() --> obj.toString()

// 二元加法使用默认 hint
let total = obj1 + obj2;
​
// obj == number 使用默认 hint
if (user == 1) { ... }; 

总结

为了进行转换,JavaScript 尝试查找并调用三个对象方法:

1.调用 obj[Symbol.toPrimitive](hint) ,如果这个方法存在的话,就不再需要 obj.valueOf()obj.toString()了。
2.否则,如果 hint 是 "string" —— 先尝试调用 obj.toString() ,不行的话调用 obj.valueOf()
3.否则,如果 hint 是 "number""default" —— 先尝试调用 obj.valueOf() ,不行的话调用 obj.toString()

Symbol.toPrimitive

任何对象都可以通过 Symbol.toPrimitive 这个符号属性来改变它作为值得效果。一旦对象具有 Symbol.toPrimitive 属性,那么 value() 和 toString() 在值运算的隐式转换中就无效了。

let x = new Number(100);
​
x[Symbol.toPrimitive] = () => 0;
​
console.log(x); // 0 

通常,它被实现转换方法,像这样:

let user = {
  name: "John",
  money: 1000,
​[Symbol.toPrimitive](hint) {
    alert(`hint: ${hint}`);
    return hint == "string" ? `{name: "${this.name}"}` : this.money;}
};
​
// 转换演示:
alert(user); // hint: string -> {name: "John"}
alert(+user); // hint: number -> 1000
alert(user + 500); // hint: default -> 1500 

从代码中我们可以看到,user[Symbol.toPrimitive] 方法处理了所有的转换情况。

toString/valueOf

如果没有 Symbol.toPrimitive,那么 JavaScript 将尝试寻找 toStringvalueOf 方法。

这些方法必须返回一个原始值。如果 toStringvalueOf 返回了一个对象,那么返回值会被忽略(和这里没有方法的时候相同)。

默认情况下,普通对象toStringvalueOf 方法:

  • toString 方法返回一个字符串 "[object Object]"。函数、数组等对象重写了 toString 方法,表现方式有所不同。
  • valueOf 方法返回对象自身。
let a = () => {};
a.toString(); // '() => {}'
a.valueOf(); // () => {}
​
let b = [1, 2];
b.toString(); // '1, 2'
b.valueOf(); // [1, 2]
​
let c = {'c': 1};
c.toString(); // '[Object Object]'
c.valueOf(); // {'c': 1} 

注:

由于历史原因,发生隐式转换时,如果 toStringvalueOf 返回一个对象,则不会出现 error,但是这种值会被忽略(就像这种方法根本不存在)。这是因为在 JavaScript 语言发展初期,没有很好的 “error” 的概念。

相反,Symbol.toPrimitive 更严格,它 必须 返回一个原始值,否则就会出现 error。

动态类型 — 值类型的隐式转换

JavaScript 中数据的类型并不是由变量声明来决定的,它的类型将会延迟到它绑定一个数据才能确定。而“变量声明时没有类型含义”也带来了一个问题:既然解释器并不知道源代码中的变量类型,那么也就无法检错。例如:

value_3 = value_1 + value_2; 

在引擎对这行源代码做语法解释期间,并不能确知二者能否进行“+”运算,更无法确定“+”运算所表达的是算术上的“求和”还是字符串的“连接”。

由于类型只能在代码执行过程中才能获知,所以 JavaScript 也就只能采用“运算过程中执行某种类型转换规则”来解决不同类型间的运算问题。

问题是:无论是 toString()、valueOf() 还是 Symbol.toPrimitive,这些转换规则背后的逻辑都只确保了“返回结果是 [ 值 ]”,并没有确保这些 “[ 值 ]” 是表达式运算所预期的那种类型。

所以在 JavaScript 的表达式运算中发生的,最终必然是以值类型为基础的类型转换。

换句话,“引用->值” 和 “值 -> 值“的类型转换是 JavaScript 中类型转换的终极目标。

总结

从上面,我们知道了类型转换经过两个运算阶段:

1.对象类型——值类型。
2.值类型——值类型。

例如:

let obj = {
  // toString 在没有其他方法的情况下处理所有转换
  toString() {
    return "2";}
};
​
alert(obj * 2); // 4,对象被转换为原始值字符串 "2",之后它被乘法转换为数字 2。 

1.乘法 obj * 2 首先将对象转换为原始值(字符串 “2”)。
2.之后 "2" * 2 变为 2 * 2(字符串被转换为数字)。

二元加法在同样的情况下会将其连接成字符串,因为它更愿意接受字符串:

let obj = {
  toString() {
    return "2";}
};
​
alert(obj + 2); // 22("2" + 2)被转换为原始值字符串 => 级联 

值类型 — 值类型的隐式转换

参考表格所列。

ToNumber

值类型转换结果
undefinedNaN
Null0
true1
false0
string根据内部规则来进行转换
Object先尝试调用 toPrimitive,否则 valueOf()
symbol报错

注:number 到 string 的转换内部规则比较复杂,如有必要,建议 Number() 显式转换。

ToString

值类型转换结果
undefined’Undefined‘
boolean‘true’ or ‘false’
number对应的字符串类型
Object先尝试调用 toPrimitive,否则 toString()
symbol报错

ToBoolean

值类型转换结果
undefinedfalse
number0 和 NaN 返回 false,其他返回 true
Objecttrue
symboltrue

对象 — 值类型的显式转换

对象与值类型之间存在一种简单、显式的转换方法。

x\转换到Objectnumberbooleanstringsymbol
值或对象Object(x)Number(x)Boolean()String()Symbol()

值类型 — 值类型的显式转换

ToNumber

包括 parseInt() 、parseFloat(),但不建议使用。一般来说,使用 Number() 来进行转换,将是更加安全的策略。

ToString

包括 String()、toString() 方法,甚至是类似 toLocaleSting() 等的方法。一般来说,建议使用 String() 方法。

ToBoolean

使用”!!“确实可以用来显式转换为布尔值。但推荐使用 Boolean() 方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值