【图解面试】深入解析数据类型转换

将值从一种数据类型转换到另一种数据类型通常称为数据类型转换。在面试过程中大多数都是以代码输出题出现,但是要了解到具体的转换规则,彻底搞懂底层原理,才能应对变来变去的值类型~

转布尔类型

Boolean类型有两个字面值: true / false (注意:区分大小写,True/False 不是有效的布尔值)

转换方式

其他数据类型转为布尔值,使用Boolean函数

  • Null || Undefined 转 Boolean
let a = null; // false
let b = undefined; // false
  • String 转 Boolean
//  String -> Boolean
let a = 'abc'; // true
let b = ''; // false
  • Number 转 Boolean
// Number -> Boolean
let n1 = 123; // true
let n2 = 12.33; // true
let n3 = +0; // false
let n4 = -0; // false
let n5 = NaN; // false
let n6 = +Infinity; // true
let n7 = -Infinity; // true
  • Object 转 Boolean
let o1 = {}; // true
let o2 = { a: 123 }; // true
let o3 = null; // false
let o4 = []; // true
let o5 = [1, 2, 3]; // true

图解执行流程

【图解面试】深入解析数据类型转换_第1张图片

转数值类型

Number数值类型,可以表示八进制(以0开头)、十进制、十六进制(0x开头)的数值。

转换方式

其他数据类型转换成数值类型有三种方式:

  • Number()

  • parseInt()

  • parseFloat()

Number()

Number()函数支持将各种数据类型转换为Number,根据ECMA-262,当执行Number(value)时,主要执行了以下流程:

  1. 检测value是否存在,如果不存在的话,定义 n = 0;

  2. 如果value存在,对value执行ToNumeric(),定义 n = ToNumeric(value)的返回结果

    • value 是对象,ToNumeric 内 执行 ToPrimitive(value,NUMBER)

      • 执行对象的valueOf(),对象有该方法并且该方法返回一个原始值,将返回的原始值转换为数值类型并返回

      • 否则执行对象的toString()方法,如果该方法返回一个原始值,则将原始值转换为数值类型并返回

      • 否则:抛出TypeError错误

    • value 是原始值,ToNumeric 内 执行 ToNumber(value)

  3. 如果Number不是通过new调用的,返回上面步骤定义的变量n值;

  4. 如果Number是通过new调用的,对n进行对象封装,返回生成的对象;

原始值转Number

let n10 = Number(123.15); // 123.15
let n11 = Number(123); // 123
let n12 = Number(undefined); // NaN
let n13 = Number(null); // 0
let n14 = Number(true); // 1
let n15 = Number(false); // 0
let n16 = Number('123'); // 123
let n17 = Number('123abc'); // NaN
let n18 = Number('abc123'); // NaN
let n19 = Number('016'); // 16
let n20 = Number('0xAF'); // 175
let n21 = Number('AF'); // NaN

对象转Number

const no1 = Number({}); // NaN
const no2 = Number([]); // 0 => [].valueOf = []; [].toString() = ''; Number('') = 0;
const no3 = Number(new Date()); // 1705500007233
const no4 = Number({ a: 123 }); // NaN
const no5 = Number([1, 2, 3]); // NaN [1, 2, 3].toString = '1,2,3'; Number('1,2,3') = NaN
const no6 = Number({
  a: 123,
  valueOf() {
    return '456'
  },
  toString() {
    return '789'
  }
}); // 456 => 执行valueOf函数,返回456

const no7 = Number({
  a: 123,
  valueOf() {
    return 'Num456'
  },
  toString() {
    return '789'
  }
}); // NaN => 执行valueOf函数,返回'Num456',Number('Num456') = NaN

const no8 = Number({
  a: 123,
  valueOf() {
    return new Number()
  },
  toString() {
    return '789'
  }
}); // 789 => 执行valueOf函数,返回值为非原始值,接着执行toString(),返回789
parseInt(string,radix)

该函数根据指定的基数对字符串的内容进行转换,从而产生一个整数。

  • 前导空格会被忽略

  • 如果检测到的第一个字符不是数值、加号或者减号,直接返回NaN;!!与Number(‘’) = 0不同,parseInt(‘’)为NaN

  • 如果第一个是数值,检测到的第一个非数值字符及后面的字符就会被舍弃

let p1 = ' 123'; // 123
let p2 = '123blue'; // 123
let p3 = '0xAF'; //175
let p4 = ''; //NaN => 空字符串检测到的第一个字符为非数值字符,所以返回也是NaN
let p5 = 'blue123'; //NaN
let p6 = '123.45'; //123
let p7 = '123b123'; //123
parseFloat(string)

该函数通过将字符串参数的内容解释为十进制文字来生成一个Number值。

parseFloat的转换方式与parseInt的执行过程大致相等,但是不支持第二个参数,只能进行十进制数值转换,对于传入的16进制一直都是只会返回0(因为第二个值就是非数值了);

let p1 = ' 123'; // 123
let p2 = '123blue'; // 123
let p3 = '0xAF'; //0
let p4 = ''; //NaN => 空字符串检测到的第一个字符为非数值字符,所以返回也是NaN
let p5 = 'blue123'; //NaN
let p6 = '123.45'; //123.45
let p8 = '123.45.45'; //123.45 => 检测到的第一个小数点为有效小数点,后面的第二个小数点及后面的数据被舍弃
let p7 = '123b123'; //123

图解执行流程

【图解面试】深入解析数据类型转换_第2张图片

转字符串类型

字符串类型具有不可变性,一个字符串一旦创建后就不可修改,如果想要修改某个字符串中的字符串值,必须先销毁原始的字符串后再保存到该变量中。

转换方式

toString()

几乎所有值都有的toString()方法,一般情况下toString()方法不支持参数,但是如果是数值类型调用toString方法,支持传递进制数表明转换成指定进制数的字符串;‍♂️ 特别注意:null 和 undefined没有toString()

let ss1 = Symbol('abc'); // Symbol(abc)
let ss2 = Symbol(undefined); // Symbol()
let ss3 = undefined; // TypeError: Cannot read property '[object Object]' of undefined 【注意:null和undefined没有toString方法】
let ss4 = null; // TypeError: Cannot read property '[object Object]' of undefined
let ss5 = 123; // '123
let ss6 = 0; // '0'
let ss7 = Infinity; // 'Infinity'
let ss8 = true; // 'true'
let ss9 = false; // 'false'
let ss10 = [1, 2, 3]; // '1,2,3'
let ss11 = new Date(); // 'Tue Jan 30 2024 14: 46: 49 GMT +0800(中国标准时间)'
let ss12 = {
  a: 'abcd',
  valueOf() {
    return 'valueof'
  },
  toString() {
    return 'toString'
  }
}; // toString
let ss13 = {
  name: 'foo' // [object Object]
};
let ss14 = {
  a: 'abcd',
  valueOf() {
    return 'valueof'
  },
  toString() {
    return {}
  }
}; // {} 与String()不同,执行完toString,即使返回的不是原始值也会返回
[ss1, ss2, ss5, ss6, ss7, ss8, ss9, ss10, ss11, ss12, ss13, ss14].forEach(item => {
  console.log(item.toString())
})
String(value)

支持null和undefined的转换

  • 先判断value是否存在,如果不存在直接返回一个空字符串

  • 如果value存在且value是Symbol类型,返回Symbol(dec);

  • 如果value不是Symbol类型,返回对应的字符串格式

let s1 = Symbol('abc'); // Symbol(abc)
let s2 = Symbol(undefined); // Symbol()
let s3 = undefined; // 'undefined'
let s4 = null; // 'null'
let s5 = 123; // '123
let s6 = 0; // '0'
let s7 = Infinity; // 'Infinity'
let s8 = true; // 'true'
let s9 = false; // 'false'
let s10 = [1, 2, 3]; // '1,2,3'
let s11 = new Date(); // 'Tue Jan 30 2024 14: 46: 49 GMT +0800(中国标准时间)'
let s12 = {
  a: 'abcd',
  valueOf() {
    return 'valueof'
  },
  toString() {
    return 'toString'
  }
}; // toString
let s13 = {
  name: 'foo' // [object Object]
};
let s14 = {
  a: 'abcd',
  valueOf() {
    return 'valueof'
  },
  toString() {
    return {}
  }
}; // valueof
[s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14].forEach(item => {
  console.log(String(item))
})

图解执行流程

【图解面试】深入解析数据类型转换_第3张图片

操作符转换

以上都是我们显示的对数据类型进行转换,但是当我们使用操作符进行一些计算时,会对值进行一些隐式转换,以下为操作符转换的一些规则

一元操作符

递增、递减、一元加/减

**转换原则:**作用于非数值类型,则会发生隐形转换,变量类型从作用值类型转换为 数值类型,转换规则同【转数值类型】一致

let d1 = '12'; // 13
let d2 = 'b12'; // NaN
let d3 = true; // 2
let d4 = false; // 1
let d5 = 123.12; // 124.12
let d7 = undefined; // NaN
let d8 = null; // 1

[d1, d2, d3, d4, d5, d7, d8].forEach(item => {
  console.log(++item)
})

布尔操作符

  • 将操作数转换为布尔类型进行操作,转换规则同【转布尔类型】一致

乘性操作符

乘法、除法、取余

  • 将操作值隐形转换为数值类型,转换规则同【转数值类型】一致

加性操作符

  • 加法:如果两个操作值都是数值,则正常计算,如果有一方是字符串,则另一个也转换为字符串,然后进行字符串拼接

  • 减法:如果两个操作值都是数值,则正常计算,如果有一方不是数值,则转换为数值后进行计算

关系操作符

  • 如果都是数值,则进行数值比较

  • 如果都是字符串,则逐个比较字符对应的编码

  • 如有任意操作符是数值,则转为数值进行比较

  • 如果是对象,则先调用valueOf,在调用toString

  • 如果任意一个是布尔值,则转为数值进行比较

相等操作符

两个操作数中有至少一个为原始值

  • 如果是布尔值,转换为数值比较

  • 如果一个字符串,一个数值,则转换为数值比较

  • 如果一个是对象,则调用valueof比较

  • null 和 Undefined 相等

  • null 和 Undefined 不能转换为其他类型比较

如果两个都是对象,则看两个对象是否指向同一个空间,如果是的话则相等,否则不等

数据类型扩展

typeof

在 JavaScript 中,typeof 被设计为一种安全的方式来检查变量的类型,即使那个变量从未被声明。这样设计主要是为了便于错误处理和检查变量是否存在。如果 typeof 不能用于未声明的变量,那么在尝试确定一个可能未声明的变量的类型时,将捕获引用错误(ReferenceError)typeof 的这种特殊行为允许开发者在不引发错误的情况下检查变量的存在和类型。一般可以用于为某个缺失的功能写 polyfill。

Undefined 类型

Undefined 类型只有一个值,那就是undefined。它表示一个变量声明了但是没有初始化

let message;
// 等同于下面表达式
let message = undefined;

虽然上面两种表示的含义相同,但是在实际开发中,一般 不要 显示的给一个变量赋值为 undefined;

undefined 与 undeclared
  • undefined 表示一个变量在作用域中已经声明,但是未初始化

  • undeclared 表示一个变量在作用域中尚未声明

这两者之间存在本质的差别。

变量message在执行时,因为已经声明但是没有初始化所以会输出undefined,但是对于没有声明过的info变量来说,执行console.log就会提示引用错误信息。

对于undeclared变量唯一能执行的是typeof操作,typeof具有错误处理机制,即使变量没有被声明过返回的也是undefined

let message;

console.log(message); // undefined
console.log(info); // ReferenceError: info is not defined

console.log(typeof message); // undefined
console.log(typeof info); // undefined
如何安全的获取undefined

表达式void ___没有返回值,所以结果是undefined。如果我们确实需要一个undefined,可以使用表达式 void 0 来获取

console.log(void 0) // undefined

Null类型

Null类型也是只有一个特殊值null,表示一个空对象指针。

与undefined不同的是,我们一般不显式给一个变量赋值为undefined,但是如果我们确定一个变量之后是一个对象,我们需要显式赋值一个null,表示当前变量是一个对象类型。 需要注意:使用 typeof 检测数据类型时,返回的为object;

let empty = null;
console.log(typeof empty); // object
console.log(empty == message) // true

关于为什么 typeof null === ‘object’

早期 JavaScript 实现:

  • JavaScript 的第一个版本中,值是通过一种称为“标签值”(tagged value)的方式表示的。每个值由一个低位的类型标签和实际的数据组成。

  • 在这种表示法中,对象的类型标签是 0。由于 null 表示为空,它被表示为全零的指针。因此,null 的类型标签也被误解为 0,导致 typeof null 返回 'object'

为什么没有修复:

尽管这被认为是 JavaScript 的一个设计错误,但修复它会导致向后兼容性问题。很多现有的网页和 JavaScript 代码依赖于 typeof null === 'object' 这一行为。改变这一行为可能会破坏大量现有的网页和应用程序,因此这个问题一直未被修复。

Number

最大值和最小值
console.log(Number.MAX_VALUE); // 1.7976931348623157e+308
console.log(Number.MIN_VALUE); // 5e-324
console.log(Number.MAX_VALUE + 1000); // 1.7976931348623157e+308 => 准确度丢失
console.log(Number.MIN_VALUE - 1000); // -1000 => 准确度丢失
console.log(Number.MAX_VALUE + Number.MAX_VALUE); // Infinity
console.log(-(Number.MAX_VALUE + Number.MAX_VALUE)); // -Infinity
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991 => 能精准计算的最大值
console.log(Number.MIN_SAFE_INTEGER); // -9007199254740991 => 能精准计算的最小值
console.log(Number.MAX_SAFE_INTEGER + 10); // 9007199254741000 => 准确度丢失
console.log(Number.MIN_SAFE_INTEGER - 10); // -9007199254741000 => 准确度丢失
NaN
  • 当一个变量值想要返回数值的操作,但是失败了时,就是返回NaN;所有NaN的后续计算都会返回NaN
console.log(+0 / 0); // NaN
console.log(-0 / 0); // NaN
console.log(3 / 0); // Infinity
console.log(-3 / 0); // -Infinity
  • NaN 依旧还是数值类型,只是它是特殊的数值。没有任何变量值等于NaN,NaN本身也不与NaN相等
console.log(typeof NaN); // number
console.log(NaN === NaN) // false
  • window.isNaN 与 Number.isNaN
console.log(Number.isNaN(NaN)) // true
console.log(Number.isNaN('abc')) // false
console.log(isNaN('abc')) // true

window.isNaN 会对传入的参数进行数据类型转换,会先将参数转换为数值类型,然后再判断是否是NaN,所以执行window.isNaN时,字符串’abc’转换为数值类型时为 NaN,在进行isNaN判断为true;

参考资料

  1. JavaScript高级程序设计(第四版)第三章:3.3、3.4、3.5
  2. 你不知道的JavaScript(中卷)第四章
  3. ECMA262标准:https://tc39.es/ecma262/#sec-tonumber
  4. ECMA262标准:https://tc39.es/ecma262/#sec-toprimitive
  5. ECMA262标准:https://tc39.es/ecma262/#sec-stringtonumber

你可能感兴趣的:(图解面试,面试,前端)