JS知识回顾——数据类型的判断方法

114 阅读6分钟

数据类型

  1. 基础数据类型五种:undefined,null,Number,Boolean,String
  2. 引用数据类型:Object,具体又分为:Array、Function、RegExp、Date、Error、Arguments等
  3. ES6新增数据类型:Symbol(表示一个唯一值,常用于对象的键,不需要new),BigInt(表示一个超出Number能表示的最大数字(2^53 - 1),在一个整数后面加n来表示BigInt)

对比null和undefined

  • 这两个类型的值都只有一个
  • null是一个关键字,undefined是window对象上的一个变量
  • 使用console.log(Object.getOwnPropertyDescriptor(window,null))获取null的描述信息返回的是undefined
  • 同样获取undefined的描述信息,返回如下,以前还能给undefined赋值,现在已经不行了,所以直接判断是否为undefined是安全的,也可以使用void number表示undefined,number可以是任意数字 image.png

检测数据类型的方法

typeof

它是一个操作符,返回一个字符串,使用方式是 typeof operand 或 typeof(operand),operand 是一个表达式

console.log(typeof undefined); // undefined
console.log(typeof null); // object
console.log(typeof true); // boolean
console.log(typeof 1); // number
console.log(typeof 'abc'); // string
console.log(typeof  (new Object)); // object
console.log(typeof ['1', '2']); // object
console.log(typeof function(){}); // function
console.log(typeof (new Date())); // object
console.log(typeof (/a/g)); // object
console.log(typeof (new Error)); // object
var arg;
(function getArg(){
    arg=arguments;
})();
console.log(typeof arg); // object
typeof的特点
  • 只能检测基础数据类型,并且对null不能准确检测;不能检测引用数据类型,但是能正确检测函数;对于null和除函数外的引用数据类型都返回object
  • 特殊的几个检测数据
typeof null // object
typeof NaN// number
typeof document.all // undefined

特别说明一下document.all,在浏览器打印时获取的是所有的html文档;或许是因为document.all不是标准(但是各浏览器都支持)所以检测它类型时返回的是undefined;在IE10以前typeof document.all返回的是object

image.png

  • 因为ES6出现暂时性死区,在暂时性死区中使用typeof会报错
  • typeof检测返回的值全部是小写
为什么typeof null返回的是object?

在js的最初版本中,使用32位系统,js使用低位来存储变量的类型信息,对象的低位标识和null的低位标识是一样的,所以返回的类型和对象是一样的

  • 对象:000
  • 整数:001
  • 浮点数:010
  • 字符串:100
  • 布尔:110
  • undefined:111
  • null:000

construct

它指向创建实例对象的构造函数

console.log((2).constructor === Number); // true
console.log(true.constructor === Boolean); // true
console.log("str".constructor === String); // true
console.log([].constructor === Array); // true
console.log(function () {}.constructor === Function); // true
console.log({}.constructor === Object); // true
const date = new Date();
console.log(date.constructor === Date);// true
construct的特点
  • 它能正确检测所有的数据类型;除null和undefined,因为它们身上没有构造函数,在它们身上使用会报错
  • 如果改变了实例对象的原型,那么constructor将判断不准确
function Fn() {}
Fn.prototype = new Array();
var f = new Fn();
console.log(f.constructor === Fn); // false
console.log(f.constructor === Array); // true
  • 如果直接改变了constructor的指向,那么判断也会出错
String.prototype.constructor = function a() {
  return {};
}
console.log('a'.constructor === String); // false

instanceof

可以正确判断对象的数据类型,运行机制是判断其原型链上能否找到该类型的原型;右操作数必须是函数或者class

console.log(2 instanceof Number); // false
console.log(true instanceof Boolean); // false
console.log("str" instanceof String); // false
console.log([] instanceof Array); // true
console.log(function () {} instanceof Function); // true
console.log({} instanceof Object); // true

传言instanceof性能比typeof慢很多,因为它要去递归原型链;对比百万数据来看instanceof的性能确实不如typeof但是也只差两三倍

const count = 1000000;
let startTime = new Date();
const fun = ()=>{}
for(let i=0;i<count;i++) {
  typeof fun === 'function'
}
console.log('typeof:',new Date().getTime() - startTime.getTime());
startTime = new Date();
for(let i=0;i<count;i++) {
  fun instanceof Function
}
console.log('instanceof:',new Date().getTime() - startTime.getTime());
手写instanceof
function myInstanceof(left, right) {
  // 获取对象的原型等同于   let proto = left.__proto__;
  let proto = Object.getPrototypeOf(left);
  // 获取构造函数的 prototype 对象
  let prototype = right.prototype;
  // 判断构造函数的 prototype 对象是否在对象的原型链上
  while (true) {
    if (!proto) return false;
    if (proto === prototype) return true;
    // 如果没有找到,就继续从其原型上找,Object.getPrototypeOf方法用来获取指定对象的原型等同于   proto = left.__proto__;
    proto = Object.getPrototypeOf(proto);
  }
}

isPrototypeOf

用于检测一个对象实例是否存在于另一个对象的原型链中,能正常返回值的情况下和instanceof很相似;区别是对操作数没有限制

console.log(Number.prototype.isPrototypeOf('1')); // false
console.log(Boolean.prototype.isPrototypeOf(true)); // false
console.log(String.prototype.isPrototypeOf('str')); // false
console.log(Array.prototype.isPrototypeOf([])); // true
console.log(Function.prototype.isPrototypeOf(function () {})); // true
console.log(Object.prototype.isPrototypeOf({})); // true

鸭子数据类型检测

根据某个属性或者行为作为判断依据,就像嘎嘎叫的就是鸭子;比如对promise的检测,typeof value.then === "function"就是promise

Object.prototype.toString

会返回形式如 [object,class] 的字符串,class 指代的是其检测出的数据类型

var toString=Object.prototype.toString;
console.log(toString.call(undefined)); // [object Undefined]
console.log(toString.call(null)); // [object Null]
console.log(toString.call(true)); // [object Boolean]
console.log(toString.call(1)); // [object Number]
console.log(toString.call('abc')); // [object String]
console.log(toString.call(new Object)); // [object Object]
console.log(toString.call(['1','2'])); // [object Array]
console.log(toString.call(function(){})); // [object Function]
console.log(toString.call(new Date())); // [object Date]
console.log(toString.call(/a/g)); // [object RegExp]
console.log(toString.call(new Error())); // [object Error]
var arg;
(function getArg(){
    arg=arguments;
})();
console.log(toString.call(arg)); // [object Arguments]
Object.prototype.toString特点
  • 能检测所有类型数据
  • 返回的类型首字母都是大写,区别于typeof
  • 特殊点:对于Boolean.prototype返回的是[Object Boolean];
    • Boolean.prototype对象上有个属性叫BooleanData; image.png
    • 而Object.prototype.toString判断是否是Boolean时也是根据是否存在BooleanData属性判断的 image.png
  • Object.prototype.toString()内部会访问Symbol.toStringTag属性,因此我们可以改变Object.prototype.toString()返回的值,这样也会导致判断类型出错
class ValidatorClass {
  get [Symbol.toStringTag]() {
    return 'Validator';
  }
}
console.log(Object.prototype.toString.call(new ValidatorClass()));
// [object Validator]

综合数据类型检测方法

/**
 * @desc 数据类型检测
 * @param obj 待检测的数据
 * @return {String} 类型字符串
 */
function type(obj) {
  return typeof obj !== "object" ? typeof obj : Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
}

特殊的NaN和Number.NaN

他们两个基本是相同的,都是表示一个非数字;使用typeof检测都是返回number类型,切不等于自己,不可以删除

1686791539502.jpg

判断是否是NaN

isNaN
  • 如果参数不是Number类型,会先尝试将参数转为数值,然后再对转换后的结果进行判断
    • 对于能被强制转换为数值的参数来说就会返回false
    console.log(isNaN('')) // false
    console.log(isNaN(true)) //false
    
    • "NaN"不会转换成NaN
  • 可以利用上面的特性来检测函数的参数是否可运算
  • 对于BigInt和Symbol类型会抛出错误,因为在对其进行转换的时候就会抛出异常
  • isNaN相当于以下的检测,其内部可能不是用的Number()函数,因为Number()函数对于BigInt类型会忽略n而不会抛出异常,可能是+运算符
function myIsNaN(val) {
  return Object.is(Number(val),NaN)
}
Number.isNaN
  • 直接判断参数是否是数字,并且值等于NaN,不会对参数进行转换
function myNumIsNaN(val) {
  if(typeof val !== 'number'){
    return false
  }
  return Object.is(val,NaN)
 }
综合严格判断是否是NaN的方法
function mysNaN(val) {
  return Object.is(val,NaN)
}
function mysNaN(val) {
  return val !== val;
}
function mysNaN(val) {
  return typeof val === Number && isNaN(val);
}
// 综合垫片,然后使用Number.isNaN判断是否是NaN
if(!('isNaN' in Number)){
  Number.isNaN = function (val) {
    return typeof val === Number && isNaN(val);
  }
}

检测数组中是否有NaN

const arr = [NaN,+0]
indexof
console.log(arr.indexOf(NaN)) // 返回-1

原因:indexof内部调用的是Number::equal

image.png

includes
console.log(arr.includes(NaN)) // true
console.log(arr.includes(-0)) // true

原因:includes内部调用的是Number::sameValueZero

image.png