JS 的数据类型知多少?

441 阅读5分钟

前端的小伙伴都知道js作为基本功,其中必不可少的就是js的数据类型,这也是我们进阶中高级前端烂熟于心的内功修炼。。。

我将从数据类型的概念、检测方法、转换方法几个方面,帮你梳理和深入学习 JavaScript 的数据类型的知识点。

数据类型概念

JavaScript 的数据类型有下图所示的 8 种:

其中,前 7 种类型为基础类型,最后 1 种(Object)为引用类型,也是你需要重点关注的,因为它在日常工作中是使用得最频繁,也是需要关注最多技术细节的数据类型

js基础数据类型和引用数据类型最大的不同在于存储数据结构的位置不同:

1、基础类型存储在栈内存,被引用或拷贝时,会创建一个完全相等的变量; 

2、 引用类型存储在堆内存,存储的是地址,多个引用指向同一个地址,这里会涉及一个“共享”的概念。

let a = {
  name: 'lee',
  age: 18
}
let b = a;
console.log(a.name);  //lee
b.name = 'son';
console.log(a.name);  //son
console.log(b.name);  //son


let a = {
  name: 'Julia',
  age: 20
}

function change(o) {
  o.age = 24;
  o = {
    name: 'Kath',
    age: 30
  }
  return o;
}
let b = change(a);  
console.log(b.age);    // 30
console.log(a.age);    // 24

数据类型检测

第一种判断方法:typeof

typeof 1 // 'number'

typeof '1' // 'string'

typeof undefined // 'undefined'

typeof true // 'boolean'

typeof Symbol() // 'symbol'

typeof null // 'object'

typeof [] // 'object'

typeof {} // 'object'

typeof console // 'object'

typeof console.log // 'function'

你可以看到,前 6 个都是基础数据类型,而为什么第 6 个 null 的 typeof 是 'object' 呢?这里要和你强调一下,虽然 typeof null 会输出 object,但这只是 JS 存在的一个悠久 Bug,不代表 null 就是引用数据类型,并且 null 本身也不是对象。因此,null 在 typeof 之后返回的是有问题的结果,不能作为判断 null 的方法。如果你需要在 if 语句中判断是否为 null,直接通过 ‘===null’来判断就好。

此外还要注意,引用数据类型 Object,用 typeof 来判断的话,除了 function 会判断为 OK 以外,其余都是 'object',是无法判断出来的。

第二种判断方法:instanceof

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

instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上。

let Car = function() {}

let benz = new Car()

benz instanceof Car // true

let car = new String('Mercedes Benz')

car instanceof String // true

let str = 'Covid-19'

str instanceof String // false

上面就是用 instanceof 方法判断数据类型的大致流程,那么如果让你自己实现一个 instanceof 的底层实现,应该怎么写呢?请看下面的代码。

//Object.getPrototypeOf() 方法返回指定对象的原型(内部[[Prototype]]属性的值)。
function myInstanceof(left, right) {
  // 这里先用typeof来判断基础数据类型,如果是,直接返回false
  if(typeof left !== 'object' || left === null) return false;
    // getProtypeOf是Object对象自带的API,能够拿到参数的原型对象
   let proto = Object.getPrototypeOf(left);
   while(true) {//循环往下寻找,直到找到相同的原型对象
    if(proto === null) return false;
    if(proto === right.prototype) return true;//找到相同原型对象,返回true
  }
}
console.log(myInstanceof(new Number(123), Number));    // true
console.log(myInstanceof(123, Number));                // falsetty

typeof和instanceof都可以判断数据类型,但是其中都有优缺点:

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

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

总之,不管单独用 typeof 还是 instanceof,都不能满足所有场景的需求,而只能通过二者混写的方式来判断。但是这种方式判断出来的其实也只是大多数情况,并且写起来也比较难受,你也可以试着写一下。

其实我个人还是比较推荐下面的第三种方法,相比上述两个而言,能更好地解决数据类型检测问题

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

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

Object.prototype.toString({})       // "[object Object]"

Object.prototype.toString.call({})  // 同上结果,加上call也ok

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.call() 可以很好地判断引用类型,甚至可以把 document 和 window 都区分开来。

但是在写判断条件的时候一定要注意,使用这个方法最后返回统一字符串格式为 "[object Xxx]" ,而这里字符串里面的 "Xxx" ,第一个首字母要大写(注意:使用 typeof 返回的是小写),这里需要多加留意。

那么下面来实现一个全局通用的数据类型判断方法,来加深你的理解,代码如下。

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

/* 代码验证,需要注意大小写,哪些是typeof判断,哪些是toString判断?思考下 */

getType([])     // "Array" typeof []是object,因此toString返回

getType('123')  // "string" typeof 直接返回

getType(window) // "Window" toString返回

getType(null)   // "Null"首字母大写,typeof null是object,需toString来判断

getType(undefined)   // "undefined" typeof 直接返回

getType()            // "undefined" typeof 直接返回

getType(function(){}) // "function" typeof能判断,因此首字母小写

getType(/123/g)      //"RegExp" toString返回z总结

总结

从以上三中方法来看,前两种方法都有其弊端,不能适应现实场景的开发需求,第三种方法

Object.prototype.toString.call通过调用原型链上的tostring方法来返回其数据类型,可以适用前端开发的任何场景需求。

注:参考若离老师的JavaScript 核心原理精讲系列