JS 类型的世界

Hello,我是冬青,一个具有产品 sense 的前端。

今天由我来带大家一起探索 JS 类型的世界。

Hello, types!

相信大家都有所了解,JS 是一种 动态 / 弱类型 语言。那么什么是动态语言,什么是弱类型语言呢?

动态语言

根据维基百科的描述,动态语言是 一类在运行时可以改变其结构的语言。也就是说,我们在写 JS 代码的时候,无需提前声明变量的类型,它的类型是在运行时被自动确定的,并且我们也可以使用同一个变量保存不同类型的值。

下面我用一段代码来演示一下:

let notSure = 'hello';
console.log(typeof notSure); // "string"

notSure = 123;
console.log(typeof notSure); // "number"

notSure = true;
console.log(typeof notSure); // "boolean"
复制代码

我们首先声明了一个变量 notSure,并初始化为 "hello",注意,这里并没有规定变量 notSure 的类型。

然后我们使用 typeof 操作符查询变量 notSure 的数据类型,打印的结果为 "string"。也就是说,变量 notSure 在代码运行过程中自动拥有了一个 String 类型,并且通过修改变量的值,变量的类型也会随着改变(我们可以称之为 运行时类型)。

弱类型语言

关于弱类型语言,维基百科也没有给出准确的定义,这里我给出自己的理解,弱类型语言,一般具有 隐式类型转换 的特性。

话不多说,上代码:

const str = 'hello';
const num = 11;

console.log(str + num); // "hello11"
复制代码

是的,你没有看错,String 类型的变量 strNumber 类型的变量 num 居然可以相加(准确来说是字符串拼接运算,而不是算数运算),打印的结果为 String 类型的 "hello11"

这是因为变量 num 发生了隐式类型转换,类型由原来的 Number 转为了 String,然后和 String 类型的变量 str 发生了字符串拼接。

实际上,隐式类型转换在 JS 中是十分常见的,比如 ==&&||!+? :if...else 等等,有人喜欢它的 灵活包容,有人讨厌它的 未知不可控

Types in JS

简单了解了动态语言和弱类型语言的基本概念之后,我们继续探索一下,JS 中有哪些数据类型。

原始类型

原始类型,又称 基本类型。JS 中有 7 种原始类型:

  • Undefined
  • Null
  • Boolean
  • Number
  • String
  • BigInt
  • Symbol

那么什么是原始类型呢?—— 所有原始类型的值都是 不可改变的

需要特别注意的是,重新赋值不等同于改变了之前的原始值

我们可以使用 typeof 运算符检查原始值的数据类型(Null 除外):

console.log(typeof undefined); // "undefined"

console.log(typeof true); // "boolean"

console.log(typeof 11); // "number"

console.log(typeof 'hello'); // "string"

console.log(typeof 9007199254740992n); // "bigint"

console.log(typeof Symbol()); // "symbol"
复制代码

为什么 Null 除外呢?接着往下看:

console.log(typeof null); // "object"
复制代码

什么,typeof null === "object"

是的,在 JS 设计之初,值是由一个 类型标签实际数据值 表示的。

对象的类型标签是 0,而 Null 代表的是空指针(大多数平台下值为 0x00),它的类型标签也是 0,因此 typeof null === "object",这是一个历史遗留的问题。

不过我们依然可以使用 typeof 去判断一个值是否为 Null 类型,在写代码之前,先插入一个很重要的概念 —— falsy

我们知道,falseBoolean 类型的两个值之一,表示假的。而 falsy 作为它的名词形式出现,表示转换为 Boolean 类型之后为 false 的值。

比较常见的 falsy 有:

  • 0
  • NaN
  • ""(注意这里是空字符串,不是空格字符串)
  • null
  • undefined
console.log(Boolean(0)); // false

console.log(Boolean(NaN)); // false

console.log(Boolean("")); // false

console.log(Boolean(null)); // false

console.log(Boolean(undefined)); // false
复制代码

那么重点来了 ——

null 是 JS 世界中唯一一个 typeof 运算符返回 "object" 的同时,自身为 falsy 的值。

所以我们可以封装一个用于判断值是否为 Null 类型的通用函数:

const isNull = value => typeof value === 'object' && !value ? true : false;
复制代码

Object 类型

Object 类型,也称 引用类型,几乎所有通过 new 创建的东西,都为 Object 类型:

const obj = { name: '冬青', age: 24 };
console.log(typeof obj); // "object"

const arr = [1, 2, 3];
console.log(typeof arr); // "object"

const date = new Date();
console.log(typeof date); // "object"

const reg = /abc/;
console.log(typeof reg); // "object"

const boolObj = new Boolean(true);
console.log(typeof boolObj); // "object"

const numObj = new Number(11);
console.log(typeof numObj); // "object"

const strObj = new String('hello');
console.log(typeof strObj); // "object"
复制代码

关于 boolObjnumObjstrObj,这里补充几个比较重要的概念 —— 装箱转换拆箱转换临时装箱

let str = 'hello';
console.log(typeof str); // "string"

str = new String(str); // 装箱转换
console.log(typeof str); // "object"
复制代码

装箱转换之前,变量 str 存储的只是一个 String 类型的 原始值;装箱转换之后,变量 str 变成了 Object 类型的、通过 new 一个构造函数 String 创建的 实例对象。变量 str 同时也拥有了构造函数 String 对应原型对象上的属性和方法。

当然,我们也可以通过拆箱转换,获取到一个实例对象的原始值:

let str = new String('hello');

str = str.valueOf();
console.log(str); // "hello"
复制代码

相信大家有疑问了,String 类型的原始值,也可以直接使用构造函数 String 对应原型对象上的属性和方法呀,这就涉及到了临时装箱(auto-boxing)的过程,JS 主动帮我们做了。

Function 类型

有很多人把 Function 归为 Object 类型,因为 Function 也是派生自 Object,它也有自己的属性:

const fn = (a, b) => {};

console.log(fn.name); // "fn"
console.log(fn.length); // 2
复制代码

但是由于它在 JS 世界中一等公民的地位,在设计时,typeof 运算符返回 "function"

console.log(typeof function () {}); // "function"
复制代码

What type?

在 JS 中,如何判断数据的类型呢?下面我来为大家列举最常见的 3 种方法。

typeof

返回一个字符串,表示未经计算的操作数的类型。

关于 typeof 运算符,我们已经见识过了,它可以准确地判断出除了 Null 以外的所有原始类型,以及 Function 类型,但是遇到 Object 类型的数据,它就无能为力了,只能返回 "object"

如果我们想获取更准确的类型,typeof 显然无法满足我们的需求。

instanceof

用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

这句话如何理解呢?上代码:

const arr = [1, 2, 3];
console.log(arr instanceof Array); // true
console.log(arr instanceof Object); // true
复制代码

因为构造函数 Arrayprototype 属性出现在 arr 的原型链上,而 Array 派生自 Object,所以构造函数 Objectprototype 属性也出现在 arr 的原型链上,两次打印结果都为 true

instanceof 同样适用于自定义的类或者构造函数:

class List extends Array {
  // ...
}
const list = new List();
console.log(list instanceof List); // true
console.log(list instanceof Array); // true
console.log(list instanceof Object); // true
复制代码

关于原型链,大家可以先简单了解一下,后续我会单独出一篇关于原型链的文章。

使用 instanceof 判断数据类型,最直观的好处是可以判断自定义的类或者构造函数,但是 instanceof 也有它的不足之处,比如它无法判断原始类型,且使用场景更适合对已有的猜测进行确认。

Object.prototype.toString.call()

Object.prototype.toString.call() 返回 "[object Type]",其中 Type 是对象的类型。

或许这是判断数据类型的终极方案了,上代码:

console.log(Object.prototype.toString.call(undefined)); // "[object Undefined]"

console.log(Object.prototype.toString.call(null)); // "[object Null]"

console.log(Object.prototype.toString.call(true)); // "[object Boolean]"

console.log(Object.prototype.toString.call(123)); // "[object Number]"

console.log(Object.prototype.toString.call('hello')); // "[object String]"

console.log(Object.prototype.toString.call(9007199254740992n)); // "[object BigInt]"

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

console.log(Object.prototype.toString.call({})); // "[object Object]"

console.log(Object.prototype.toString.call([])); // "[object Array]"

console.log(Object.prototype.toString.call(new Date())); // "[object Date]"

console.log(Object.prototype.toString.call(/abc/)); // "[object RegExp]"

console.log(Object.prototype.toString.call(() => {})); // "[object Function]"
复制代码

可见 Object.prototype.toString.call() 真的非常强大,它几乎可以帮我们判断所有的数据类型,但是返回结果为 "[object Type]" 的形式,感觉怪怪的,不过我们可以手动做一层封装,让它变得更好用。

封装

学习了上面 3 种判断数据类型的方法,我们可以试着自己封装一个好用的方法:

const typeOf = value => {
  if (typeof value !== 'object') {
    return typeof value;
  } else {
    if (!value) {
      return 'null';
    } else {
      const str = Object.prototype.toString.call(value);
      return str.slice(8, -1).toLowerCase();
    }
  }
};

const arr = [1, 2, 3];
console.log(typeOf(arr)); // "array"
复制代码

Bye, types!

关于 JS 的数据类型,就先聊到这吧,写困了,睡觉,晚安。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享