数据类型的判断方法以及手写实现

247 阅读3分钟

在上一篇文章中,我们详细了解了原型与原型链的概念,以及通过一个经典的问题详细阐述了原型链之间存在的“玄学”关系,这一篇文章我们来了解一下有哪些数据类型判断方法

typeof

typeof可以检测的数据类型有哪些

  • "undefined"
  • "boolean"
  • "string"
  • "number"
  • "object"
  • "function"

为什么 typeof null 会返回 "object"?

因为null会被认为是对空对象的引用,空对象的数据类型是object

原理

说到这里,我们应该考虑一下,JavaScript是怎么存储数据的呢,又或者说,对于一个变量,它的数据类型权衡的标准是什么呢?

查阅了相关的资料,其实这个是一个历史遗留的bug,在 javascript 的最初版本中,使用的 32 位系统,为了性能考虑使用低位存储了变量的类型信息:

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

但是对于 undefinednull 来说,这两个值的信息存储是有点特殊的。

null:对应机器码的 NULL 指针,一般是全零。

undefined:用 −2^30 整数来表示!

所以,typeof 在判断 null 的时候就出现问题了,由于 null 的所有机器码均为0,因此直接被当做了对象来看待。

instanceof

原理

  • instanceof 是用来判断 A 是否为 B 的实例,表达式为:A instanceof B。
  • instanceof 只能用来判断两个对象是否属于实例关系, 而不能判断一个对象实例具体属于哪种类型
  • instanceof 对于引用类型的支持很好,但他是无法对原始类型进行判断,所以一般都是在 typeof 判断为 object 时才使用 instanceof。
console.log([] instanceof Array);       //true
console.log({} instanceof Object);       //true
console.log(new Date() instanceof Date);       //true
console.log([] instanceof Object);       //true
console.log(new Date() instanceof Object);       //true
console.log("123" instanceof String);       //false
// 由上我们可以看到由于原项链的关系,所有的对象实例都是可以间接的指向 Object 这个类的。

手写实现

function my_instance_of(leftVaule, rightVaule) {
if((typeof leftValue !== 'object') || leftValue === null) return false;
let proto = leftValue.__proto__;
while(true){
if(proto === null) return false;
if(proto === rightValue.prototype) return true;
proto = proto.__proto__;
}
}

constructor

constructor 可以打印出实例所属的类,表达式为:实例.constructor 。那么判断各种类型的方法就是:

console.log([].constructor == Array);       //true
console.log({}.constructor == Object);      //true
console.log("string".constructor == String);        //true
console.log((123).constructor == Number);       //true
console.log(true.constructor == Boolean);       //true
console.log([].constructor == Object)       // false    

Object.prototype.toString.call()

toString() 是 Object 的原型方法,调用该方法,默认返回当前对象的 [[Class]] 。这是一个内部属性,其格式为 [object Xxx] ,其中 Xxx 就是对象的类型。

对于 Object 对象,直接调用 toString() 就能返回 [object Object] 。而对于其他对象,则需要通过 call / apply 来调用才能返回正确的类型信息。

console.log(Object.prototype.toString.call('')) ;       // [object String]
console.log(Object.prototype.toString.call(1)) ;        // [object Number]
console.log(Object.prototype.toString.call(true)) ;     // [object Boolean]
console.log(Object.prototype.toString.call(undefined)) ;     // [object Undefined]
console.log(Object.prototype.toString.call(null)) ;     // [object Null]
console.log(Object.prototype.toString.call(new Function())) ;     // [object Function]
console.log(Object.prototype.toString.call(new Date())) ;     // [object Date]
console.log(Object.prototype.toString.call([])) ;     // [object Array]
console.log(Object.prototype.toString.call(new RegExp())) ;     // [object RegExp]
console.log(Object.prototype.toString.call(new Error())) ;     // [object Error]
console.log(Object.prototype.toString.call(document)) ;     // [object HTMLDocument]
console.log(Object.prototype.toString.call(window)) ;     //[object global] window 是全局对象 global 的引用

new关键字的原理及实现

  • new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。new 关键字会进行如下的操作:
1. 创建一个空的简单JavaScript对象(即{});
2. 链接该对象(即设置该对象的构造函数)到另一个对象 ;
3. 将步骤1新创建的对象作为this的上下文 ;  
4. 如果该函数没有返回对象,则返回this
  • 看下面事例:
function Person() {
this.name = 'xy'
}


根据上面描述的,new Person()做了:
1. 创建一个空对象:var obj = {}
2. 将空对象分配给 this 值:this = obj
3. 将空对象的__proto__指向构造函数的prototype:this.__proto__ = Person().prototype
4. 返回this:return this

实现代码一

function newOper(){
  let obj = {}
  let constructor = Array.prototype.shift.apply(arguments)
  obj.__proto__ = constructor.prototype
  var ret = constructor.apply(obj, arguments)
  return typeof ret === 'object' ? ret : obj
}

实现代码二

function newOper(cons, ...args){
  if(typeof cons !== 'function'){
    throw 'the first param must be a function'
  }
  let obj = Object.create(cons.prototype)
  let  res = cons.apply(obj, args)


  let isObject = typeof res === 'Object' && res !== null
  let isFunction = typeof res === 'function'


  return isObject || isFunction ? res : obj