Front-end cornerstone: 9 big data types and data type conversions in JS

9 Big Data Types

Strange values ​​in numbers:

  • NaN: Not a valid number, but of type number. isNaN is to check whether the value is NaN. When checking, if the current value is not a number type, it will be implicitly converted to a number type (Number conversion), and then it will be judged whether it is a valid number. Object.is(NaN, NaN) can also detect if a value is NaN

data type conversion

In the type conversion of the front end, there are two types: manual conversion and implicit conversion

Convert other types to Number type

  • Manual conversion

    • Number([val])
    • parseInt/parseFloat([val])
  • Implicit conversion (the browser's internal default is to convert to Number first for calculation)

    • isNaN([val])
    • Numerical operations (+, -, *, /, %), but + operations are not numeric operations in the presence of strings, but string concatenation
    • When doing == comparison, some values ​​need to be converted to numbers before comparing

Number conversion

// 字符串转换为数字
Number('') => 0
Number('10') => 10
Number('10px') => NaN  // 只要出现非有效数字结构就是 NaN

// 布尔值转换为数字
Number(true) => 1
Number(false) => 0

// 其他类型转换为数组
Number(null) => 0
Number(undefined) => NaN
Number(Symbol(1)) => 报错
Number(BigInt(1)) => 1
对象转换为数字 => 先基于 valueOf 方法转换为原始类型,没有原始类型在基于 toString 转换为字符串,在将字符串转换为数字
复制代码

parseInt/parseFloat conversion

parseInt conversion mechanism (parseFloat is similar, only one more decimal point is recognized):

  1. Find significant digits starting from the first string to the left of the string.
  2. Stop searching when encountering a non-valid number character, regardless of whether there is a valid number after it or not.
  3. Convert the found valid numeric characters to numbers.
  4. If none of the valid numeric characters are found, the result is NaN.

Look at a few examples of interview questions from a large factory: What is the output?

parseInt('');
Number('');
isNaN('');

parseInt(null);
Number(null);
isNaN(null);

parseInt('1px');
Number('1px');
isNan('1px');

parseInt('1.1px') + parseFloat('1.1px') + typeof parseInt(null);
isNaN(Number(!!Number(parseInt('1.8'))));
typeof !parseInf(null) + !isNaN(null);

1 + false + undefined + [] + 'Tencent' + null + true + {};
复制代码

And there is a more classic interview question for parseInt():

let arr = [1,2,3,4];
arr = arr.map(parseInt);
console.log(arr);
复制代码

Here we need to figure out the rules of parseInt([value], [radix]) first:

  • [radix] This value is a hexadecimal. If you don't write 0 or write 0, it will be processed in decimal by default (special case: if the value starts with 0x, the default value is 10 or 16).
  • The hexadecimal has a range of values, between 2 and 36. If it is not within this range, the result of the entire program must be NaN.
  • [value] is the value in [radix], and needs to convert [value] to decimal.
arr.map(parseInt)
=> 
parseInt('1', 0);  => 1
parseInt('2', 1);  => NaN
parseInt('3', 2);  => NaN
parseInt('4', 3);  => NaN
=> [1, NaN, NaN, NaN]
复制代码

implicit conversion

See a simple example:

[] == false;
复制代码

对象和布尔的比较,都是转换为数字(隐式转换),对象转换为数字先基于 valueOf 获取原始值,如果没有原始值在去 toString 转换为字符串然后在转换为数字。

在上面的例子中:

  1. [] 基于 valueOf 获取原始值,[].valueOf() => [] ,发现 [] 没有原始值。
  2. [] 没有原始值就调用 toString 转换为字符串,[].toString() => ''。
  3. '' 转换为数字为 0 。
  4. false 转换为数字为 0。
  5. 所以 [] == false => true

在看一个扩展的例子:

![] == false;
复制代码
  1. 这里的比较先看符号优先级,! 的优先级高于 == ,所以是 ![] 的结果在和 false 进行比较。
  2. ![] 的潜在意思就是将 [] 转换为布尔在取反。
  3. 在 js 中 ''、0、null、undefined、NaN 这五个值变成布尔为 false ,其他的值都是 true ,所以 [] 转换为布尔结果 为 true。 ![] => false。
  4. ![] == false 结构就是 true。

对象转换的规则,会先调用内置的 [ToPrimitive] 函数,其规则逻辑如下:

  • 如果部署了 Symbol.toPrimitive 方法,优先调用再返回;
  • 调用 valueOf(),如果转换为基础类型,则返回;
  • 调用 toString(),如果转换为基础类型,则返回;
  • 如果都没有返回基础类型,会报错。

其他类型转换为 String 类型

  • 手动转换

    • String([val])
    • toString()
  • 隐式转换

    • +运算,如果一边出现字符串,则是字符串拼接。
    • 这里有一种特殊的情况,如果出现对象的 + (例如:1+[])也会变成字符串拼接,原因在于对象转换为数字的过程中会先将对象转换为字符串,+ 遇到字符串就变成字符串的拼接了。
    • 对于 +/++ 这种不一定是字符串拼接,是运算。
    • 还有一种特殊情况 {} + 0 这种情况,左边貌似一个对象,但是在处理的时候会当做一个代码块在处理。所以结果是数字 0 。
    • 把对象转换为数字,需要先 toString() 转换为字符串,再去转换为数字

其他类型转换为 Boolean 类型

  • 手动转换
    • ![val]
    • !![val]
    • Boolean([val])
  • 隐式转换
    • 在循环或者条件判断中,条件处理的结果就是布尔类型值

数据类型检测

第一种判断方法:typeof

typeof 1 => 'number'
typeof '1' => 'string'
typeof treu => 'boolean'
typeof undefined => undefined
typeof null => 'object'
typeof Symbol(1) => 'symbol'
typeof 1n => 'bigint'
typeof []  =>'object'
typeof {}  =>'object'
typeof console  => 'object'
typeof console.log  => 'function'
复制代码

typeof 的类型检测有两点需要注意:

  1. typeof null 结果是 'object',一般大家都说这是计算机的一个 Bug 导致的,但是到底是一个什么 Bug 导致了这个问题了?

“typeof null”错误是 JavaScript 第一版的遗留物。在这个版本中,值存储在 32 位单元中,由一个类型标签(1-3 位)和值的实际数据组成。类型标签存储在单元的低位中。其中有五个:

  • 000:对象
  • 1:整数
  • 010:浮点数
  • 100:字符串
  • 110:布尔值

也就是说,最低的位是一位,那么类型标记只有一位长。或者它是零,那么类型标签的长度是三位,为四种类型提供两个额外的位。有一个值比较特殊:

  • undefined 是整数 -2 ^30 (整数范围之外的数字)
  • null 是机器码 NULL 指针。一个对象类型标签加上一个为零的引用。 这就很明显为什么 typeof null 结果是 'object' 了。

并且在ECMA6中,曾经有提案为历史平凡,将 type null 的值纠正为 'null',但最后提案被拒了。理由是历史遗留代码太多。

  1. 引用数据类型 Object,用 typeof 来判断的话,除了 function ,其余都是 'object'。这也是为为什么在类型区分的时候,需要将 object 和 function 区分开的原因之一。

typeof 它虽然可以判断基础数据类型(null 除外),但是引用数据类型中,除了 function 类型以外,其他的也无法判断。

第二种判断方法:instanceof

[value] instanceof [constructor]

instanceof 运算符用来检测 [value] 在其原型链中是否存在一个 [constructor] 的 prototype 属性。

function myInstanceof(left, right) {
  // 先用 typeof 来判断基础数据类型,如果是,直接返回 false
  if(typeof left !== 'object' || left === null) return false;
  // 然后使用 getProtypeOf 获取参数的原型对象
  let proto = Object.getPrototypeOf(left);
  // 循环往下寻找,直到找到相同的原型对象
  while(true) {
    // 找到最顶层
    if(proto === null) return false; 
		// 找到相同原型对象,返回true
    if(proto === right.prototype) return true;
    // 继续向上寻找
    proto = Object.getPrototypeof(proto);
   }
}
复制代码

instanceof 可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型;

第三种判断方法:constructor

[value].constructor === 类,相对于 instanceof 来讲基本类型也可以处理,而且因为获取实例的 constructor 实际上获取的是所属的类,所以在检测准确性上比 instanceof 还好一点,但是 constructor 是可以随意被改动的。

第四种判断方法:Object.prototype.toString

toString() 是 Object 的原型方法,调用该方法,可以统一返回格式为 “[object Xxx]” 的字符串,其中 Xxx 就是对象的类型。对于 Object 对象,直接调用 toString() 就能返回 [object Object];而对于其他对象,则需要通过 call 来调用,才能返回正确的类型信息。

Object.prototype.toString({})       	=> "[object Object]"
Object.prototype.toString.call({})    => "[object Object]"
Object.prototype.toString.call(1)     => "[object Number]"
Object.prototype.toString.call('1')   => "[object String]"
Object.prototype.toString.call(true)  =>"[object Boolean]"
Object.prototype.toString.call(function(){})  => "[object Function]"
Object.prototype.toString.call(null)   => "[object Null]"
Object.prototype.toString.call(undefined) => "[object Undefined]"
Object.prototype.toString.call(/123/g)    => "[object RegExp]"
Object.prototype.toString.call(new Date()) => "[object Date]"
Object.prototype.toString.call([])       => "[object Array]"
Object.prototype.toString.call(document)  => "[object HTMLDocument]"
Object.prototype.toString.call(window)   => "[object Window]"
复制代码

通过 Object.prototype.toString 可以实现一个通过的类型判断方法:

function getType(obj){
  let type  = typeof obj;
  // 先进行typeof判断,如果是基础数据类型,直接返回
  if (type !== "object") {    
    return type;
  }
  // 对于typeof返回结果是object的,再进行如下的判断,正则返回结果
  return Object.prototype.toString.call(obj).replace(/^[object (\S+)]$/, '$1'); 
}
复制代码

在 ES3 中,在 toString 方法被调用时,会执行下面的操作步骤:

  1. 获取 this 对象的 [[Class]] 属性的值。
  2. 计算出三个字符串 "[object ",第一步的操作结果 Result(1),以及 "]" 连接后的新字符串。
  3. 返回第二步的操作结果 Result(2) .

在 ES5 中,在 toString 方法被调用时,会执行下面的操作步骤:

  1. 如果 this 的值为 undefined,则返回 "[object Undefined]"。
  2. 如果 this 的值为 null,则返回 "[object Null]"。
  3. 获取 ToObject(this) 的结果 Result(1)。
  4. 获取 Result(1) 的内部属性 [[Class]] 的值 Result(2)。
  5. 返回三个字符串"[object ", Result(2), 以及 "]"连接后的新字符串.

参考

Guess you like

Origin juejin.im/post/7080418682022821902