很难吗?十分钟over数据类型、判断和转换

236 阅读9分钟

基础不扎实,搭建的房子怎么会又高又稳,所以这次我们来深入探讨一下数据类型、类型判断和类型转换吧

数据类型

首先我们要知道JavaScript是一种弱类型(或称为动态类型)的语言,这意味着我们不需要声明变量的类型,类型是在运行时自动推断的。JavaScript的主要数据类型可以分为以下几类:

  • 原始类型

    原始类型是不可改变的数据类型,意味着它们的值不可更改,每个值都是独立的。主要包括以下几个常见的数据类型:

    • number:包含整数或浮点数的任何数值
    • string:由零个或多个字符组成的序列(字符串)。
    • boolean:只能是truefalse
    • undefined:表示变量已被声明,但尚未赋值或未定义。
    • null:表示一个空值或不存在,或者说没有声明,找不到这个值。
    • Symbol:ES6 新增的类型,用于创建唯一的键标识符,通常用于对象的键名。
    • BigInt:ES10 引入的类型,用于表示任意大小的整数,超过 Number.MAX_SAFE_INTEGER 的整数。

注意:一定要注意nullundefined的区别。举个例子吧,就好像你的口袋一样,你用手伸进去摸,里面有个钱包但是没有钱,这就是undefined;而如果伸进去摸什么都没有,那就null

  • 复杂类型

    复杂类型,又称为对象类型或者引用类型,是可变的,它们可以包含多个值。常见的引用类型主要包括以下几种:

    • 对象 object:最通用的类型,可以包含属性和方法也就是键值对。数组和函数在JavaScript 中也是对象。
    • 数组(Array):一种特殊的对象,用于存储有序的值列表。
    • 函数(Function):可以被调用的对象,用于执行特定任务。

当然还有日期(Date)、正则表达式(RegExp)、错误(Error)其他ES6及后续版本引入的复杂类型,如MapSetWeakMapWeakSet等。

数据存储

  1. 原始类型的值直接存在调用栈中
  2. 复杂类型的值存在堆结构中,并将堆中的引用地址存在调用栈当中
  • 调用栈空间比较小,引用类型的数据当量又可能非常大,所以引用类型如果放在调用栈中很容易爆栈
  • 较长的字符串和其他较大的原始类型也会被提升到堆中存储

类型判断

对数据类型有一定的了解之后,肯定会想到JS不是个弱类型语言嘛,都不需要声明变量的类型,那么我们该怎么判断这些变量属于哪些类型呢,这就要让我们来熟悉一下,js中的类型判断了。有很多种方法,来一起探究一下吧

typeof

  1. 原始类型typeof能够正确地识别numberstringbooleanundefinedsymbolbigint类型的值。

    console.log(typeof 123);    // "number"
    console.log(typeof "");     // "string"
    console.log(typeof true);   // "boolean"
    console.log(typeof undefined); // "undefined"
    console.log(typeof Symbol()); // "symbol"
    console.log(typeof BigInt(123)); // "bigint"
    
  2. null类型:尽管null是一个原始类型,typeof null却返回"object"。这是JavaScript的一个历史遗留问题,源自于早期的实现,当时null被设计为一个特殊的对象引用。

    console.log(typeof null);    // "object"
    
  3. 引用类型 :对于所有引用类型,包括objectarraydateregexp等,typeof都返回"object"。这是因为typeof对于引用类型的检测是基于类型标记的("object"是所有引用类型的基类),而不是基于具体的类型。

    console.log(typeof {});      // "object"
    console.log(typeof []);      // "object"
    console.log(typeof new Date()); // "object"
    
  4. 函数类型:但typeof能够识别function类型的值。

    console.log(typeof function(){}); // "function"
    

总得来说,typeof()可以判断除null之外的原始类型,但无法判断除function之外的引用类型。

instanceof

这是一个关键字,它特别适合于判断引用类型,用法是这样的arr instanceof Array,通过判断左边的是不是隶属于右边的,这只是粗略的说法。事实上instanceof关键字是用于检查一个对象是否是特定构造函数的实例,或者是否继承自特定构造函数的原型,是通过原型链的查找来判断。

  1. 原始类型instanceof不能判断numberstringboolean的类型,会返回false。原始数据类型在JavaScript中是值类型,它们不具有原型链,也不是构造函数的实例。因此instanceof无法应用于这些类型。除非使用new Number(), new String(), 或者 new Boolean()来创建。

    console.log('hello' instanceof String);  // false
    console.log(123 instanceof Number);  // false
    console.log(false instanceof Boolean);  // false
    
    let num = new Number(123);
    let str = new String("Hello");
    let bool = new Boolean(true);
    console.log(num instanceof Number); // true 
    console.log(str instanceof String); // true 
    console.log(bool instanceof Boolean); // true
    
  2. nullundefined类型

    这俩哥们既不是任何构造函数的实例,也没有原型链,因此instanceof不能用于判断这两个值的类型。当你使用instanceof来判断的时候,就会报错。

  3. 引用类型instanceof可以判断引用类型,因为左边的的原型链上都继承自右边这个构造函数。

    console.log({} instanceof Object);  // true
    console.log([] instanceof Array);  // true
    console.log(function () {} instanceof Function);  // true
    let date = new Date()
    console.log(date instanceof Date);  // true
    

    但是我们知道通过原型链查找,这些引用类型都会通过原型链找到后面的"object"。所以在判断数组是不是object时,也会返回true。

不过数组中有一个方法来判断是不是数组,这种方法是Array.isArray

不如我们先来手写一下instanceof吧,这样也能实现关键字instanceof的作用

```js
function myInstanceof(L, R) {
    while (L !== null) {
      if (L.__proto__ == R.prototype) {
        return true;
      }
      L = L.__proto__
    }
    return false;
  }
```

现在是有一个typeof来判断原始类型,instanceof来判断引用类型,那么我们肯定想要一个方法来判断这两种类型。当然肯定有的,来接着往下看吧。

Object.prototype.toString

Object.prototype.toString 方法在JavaScript中提供了一种更全面的方式来判断一个值的类型。这个方法会返回一个表示指定对象的类型字符串。例如:"[object Number]"

当你调用Object.prototype.toString 方法时,会先调用 ToObject(x) 将x转为对象,此时得到的对象内部一定拥有一个属性 [[class]], 而该属性[[class]]的值就是 x 的类型,设class 是[[class]]的值,会返回由 “[object” 和 class 和 “]” 拼接得到的字符串。

可是,原始类型中它们没有toString属性,更不用说Object.prototype.toString了。所以我们通常会用.call().apply()方法来间接调用它。

console.log(Object.prototype.toString.call(undefined)); // "[object Undefined]" console.log(Object.prototype.toString.call(null)); // "[object Null]" console.log(Object.prototype.toString.call(123)); // "[object Number]" console.log(Object.prototype.toString.call("hello")); // "[object String]" console.log(Object.prototype.toString.call(true)); // "[object Boolean]" console.log(Object.prototype.toString.call({})); // "[object Object]" console.log(Object.prototype.toString.call([])); // "[object Array]" console.log(Object.prototype.toString.call(function(){})); // "[object Function]"

虽然输出这个我们也可以看出他们的类型,不过,我们肯定是想直接输出类型,而不是和"[object Null]"一样,所以我们来改造一下吧

function type(x) {
    let res = Object.prototype.toString.call(x)
    return res.slice(8, -1) // '[object String]'
  }
  
  console.log(type('hello')); // String
  console.log(type(123)); // Number
  console.log(type([])); // Array

这样我们就可以用这个方法来判断类型了。

类型转换

在JavaScript中,类型转换分为显式类型转换和隐式类型转换两种。本文将深入探讨这两种类型转换的机制及其在实际编码中的应用。

显式类型转换

显式类型转换是指程序员通过使用特定的函数或语法结构来改变一个值的数据类型。在JavaScript中,最常用的显式类型转换包括:

  1. 转布尔类型 - 使用 Boolean(x) 将任何值转换成布尔值。
  2. 转数字类型 - 使用 Number(x) 将任何值转换成数字。
  3. 转字符串类型 - 使用 String(x) 将任何值转换成字符串。

例如:

let x = "123";
let num = Number(x); // num is now 123 (a number)

下面这张表基本上就包含了所有的显式类型转换:

image.png

隐式类型转换

隐式类型转换发生在不需要明确指定转换的情况下,通常是由于某些操作符要求操作数具有相同类型时自动进行的。在JavaScript中,隐式类型转换尤其普遍,不过有时会导致意料之外的结果。

对象转原始值

当需要一个对象参与原始值操作时,JavaScript会尝试将其转换为原始类型,通常遵循以下步骤:

  1. 转布尔:所有对象在转为布尔值时都会变成 true

  2. 转数字:首先调用 ToNumber() 函数,它会进一步调用 ToPrimitive() 方法。ToPrimitive() 方法按照以下顺序尝试:

    • 如果值已经是原始类型,直接返回。
    • 否则,尝试调用对象的 valueOf() 方法。
    • 如果 valueOf() 返回一个原始值,则返回这个值。
    • 如果 valueOf() 返回一个对象,或者没有 valueOf() 方法,尝试调用 toString() 方法。
    • 如果 toString() 返回一个原始值,则返回这个值。
    • 如果上述步骤都没有返回一个原始值,抛出一个错误。
  3. 转字符串:类似转数字的过程,但优先级顺序相反,即首先尝试 toString(),然后才是 valueOf()

例如:

let obj = { value: 42 };
let num = +obj; // num =42

toString() 和 valueOf()

  • Object.prototype.toString() 方法返回一个表示对象的字符串,通常形式为"[object Type]"
  • Array.prototype.toString() 方法返回一个由数组元素组成的字符串,元素之间以逗号分隔。
  • 其他的xxx.prototype.toString() 方法返回一个字符串字面量,或者其他内置类型或自定义对象可能重写了toString()方法来提供定制的行为。

运算符与类型转换

  • 一元运算符 + 当用于非数字前缀时,会触发从其他类型到数字的隐式转换。
  • 二元运算符 + 当两个操作数中有一个是非字符串时,另一个会被转换为字符串。

== 与 === 比较

  • ==(相等比较)会触发隐式类型转换,以使两边的操作数具有相同的类型,然后再进行比较。
  • ===(严格相等比较)不会进行类型转换,只在两边操作数类型和值都相同时才返回true

常见的面试题[]== ![]

可能很多人知道这个题目,但是我们还是来分析一下:

  • [] 是一个空数组,它的类型是 object

  • ![] 是一个逻辑非操作符应用于一个空数组。由于空数组在布尔类型中被视为 true,因此 ![] 的结果是 false

  • 所以,我们就得到:左边是 [],右边是 false

  • [] 是一个对象,所以它需要被转换为一个原始值。所以JavaScript会尝试调用 valueOf() 方法,但由于 Array 没有重写 valueOf() 方法,它会继续调用 toString() 方法。Array.prototype.toString() 方法返回一个空字符串

  • 所以我们比较的是 空字符串和 false

  • 进行比较的时候,两边都会转变为数字类型,[]->'' ->0,![] -> false->0

  • 所以最后的结果是true。