Skip to content

Latest commit

 

History

History
250 lines (228 loc) · 12.5 KB

变量类型判断.md

File metadata and controls

250 lines (228 loc) · 12.5 KB

变量类型判断

本文我们将简单了解下JavaScript当前数据类型 与 如何判断某个数据 是什么类型!

变量类型

JavaScript现在有八种内置类型,一共分为两种:

基本数据类型:

  • 未定义Undefined

undefined 不是保留字,在低版本浏览器能被赋值
let undefined = 1// 这样判断就会出错
所以用下面的方式来判断
void 后面随便跟上一个组成表达式,返回就是 undefined
a === void 0

  • 布尔值Boolean
  • 空值Null
  • 数字Number
  • 字符串String
  • 大整数BigInt(ES6中新增)
  • 符号Symbol(ES2020 引入)

Symbol: 是ES6中引入的一种原始数据类型,表示独一无二的值。具体可查看:你知道JavaScript中的Symbol类型怎么用吗?

BigInt:是 ES2020 引入的一种新的数据类型,用来解决 JavaScript中数字只能到 53 个二进制位(JavaScript 所有数字都保存成 64 位浮点数,大于这个范围的整数,无法精确表示的问题。具体可查看:新数据类型 — BigInt

更多:JavaScript类型判断的四种方法

  • 对象Object
  • Object又有许多常见的具体类型:函数Function数组Array日期Date

typeof

首先明确一点:typeof 不是 函数,而是 操作符,故其右边为 表达式,返回值为这个表达式的数据类型 ;它基本用于判断简单数据类型,具体语法为:

typeof variable

判断代码:

console.log(typeof 1)// number
console.log(typeof '')// string
console.log(typeof true)// boolean
console.log(typeof undefined)// undefined
console.log(typeof 1000n)// bigint
console.log(typeof Symbol('符号描述'))// symbol
console.log(typeof null)// object
console.log(typeof [])// object
console.log(typeof function () {})// function
console.log(typeof new Date())// object

我们可以注意到:

  • typeof 可判断除了null以外的其他 基本数据类型
  • typeof 对于 引用数据类型 无能为力,除了function函数 以外的其他对象,typeof 无法判断,只能返回 object

nulltypeof 判断为 对象object,为什么会是这个结果?

JavaScript底层存储变量时,在机器码低位(1-3位)存储变量的类型信息,000开头代表是对象,然而null信息却为全0,所以将它错误的判断为对象。 虽然现在的内部类型判断代码已经改变了,但这个Bug却一直流传下来。

五种基本数据类型:

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

undefinednull 比较特殊:

  • null:所有机器码均为0
  • undefined:用 −2^30 整数来表示

instanceof

为了解决typeof不能判断引用数据类型的问题,我们可以使用instanceof,其专门用于判断一个对象是否为某个构造函数实例

object instanceof constructor

代码示例:

[] instanceof Array;    //true
{} instanceof Object;   //true
new Date() instanceof Date;    //true

instanceof判断的原理是什么?

短说:检测某个构造函数的原型对象在不在某个对象的原型链上。

长话:判断实例对象的 __proto__ 是否与构造函数的 prototype 指向同一个引用(也就是说通过构造函数原型对象是否是实例对象的原型对象来判断类型,需要注意的是,如不相等会一直从原型链往上找,直到相等或者原型对象为null)。

只要constructorobject的父类/祖宗类 ,都将返回true

instanceof代码实现:

// 注意,我们手写的判断函数名O是大写的
function instanceOf (object, constructor) {
  // 为了避免被传入非引用数据类型,我们使用typeof进行简单判断是否为对象类型
  if (typeof object === 'object' || typeof object === 'function') {
    // 一个用于获取对象原型的方法 proto当前为实例原型对象
    let proto = Object.getPrototypeOf(object)
    // proto为null时while结束
    while (proto) {
    // 指向函数对象的原型属性:判断当前proto原型对象是否与构造函数原型对象为同一个
      if (proto === constructor.prototype) {
        return true
      }
      // 如果不相等,将proto的原型赋值给proto
      proto = Object.getPrototypeOf(proto)
    }
  }
  // 原型链找完依旧不相等返回false
  return false
}

Object.prototype.toString.call()

Object.prototype.toString.call()顾名思义,调用的Object原型对象的方法。

 Object.prototype.toString.call(anyValue);

返回结果为'[Object ConstructorName]'格式字符串。这个ConstructorName是某个原生构造函数的名称,也就是类型值。

console.log(Object.prototype.toString.call(1))// [object Number]
console.log(Object.prototype.toString.call(''))// [object String]
console.log(Object.prototype.toString.call(true))// [object Boolean]
console.log(Object.prototype.toString.call(undefined))// [object Undefined]
console.log(Object.prototype.toString.call(1000n))// [object BigInt]
console.log(Object.prototype.toString.call(Symbol('符号描述')))// [object Symbol]
console.log(Object.prototype.toString.call(null))// [object Null]
console.log(Object.prototype.toString.call([]))// [object Array]
console.log(Object.prototype.toString.call({}))// [object Object]
console.log(Object.prototype.toString.call(new Date()))// [object Object]

注:为什么我们需要调用Object原型方法,是因为尽管对象原型链都能找到toString方法,但是他们都将其重写,导致结果不如我们所预料,通过call将方法的this改为当前对象。

[].toString()//''
[].__proto__.hasOwnProperty('toString')// true  也可这样写:Array.prototype.hasOwnProperty('toString')
delete [].__proto__.toString// true     也可这样写:delete Array.prototype.toString
[].toString()// "[object Array]"
// 自定义类型检测
function Foo () {
  this.name = 'trump'
}
const f1 = new Foo()
console.log(Object.prototype.toString.call(f1))// [object Object]

从上面代码我们可以推断:

  • Object.prototype.toString.call()只适合JavaScript内置对象,对于我们自定义的方法就无能为力
  • 原始值被当作构造函数创建的一个对象来使用(调用方法/属性)时, JS 会将其转换为一个对象,以便其可以使用对象的特性(如方法),而(使用完毕)后抛弃对象性质,并将它变回到原始值。
var str="测试";
String.prototype.returnThis=function(){
  debugger;
  console.dir(this);
  console.log(typeof(this));
};
str.returnThis();

更多:为什么 JavaScript 中基本数据类型拥有 toString 之类方法?

constructor

constructor属性表示原型对象与构造函数之间的关联。原型对象的constructor会指回构造函数。

obj.constructor.name// 其实对象自身是没有的,会通过原型链去原型对象寻找,我们通过name可获得构造函数的名称,也就是我们所要的对象

如果我们修改了原型对象,一般会同时修改constructor属性,防止引用的时候出错。

[].constructor.name//"Array"
''.constructor.name//"String"
null.constructor.name//Uncaught TypeError: Cannot read property 'constructor' of null
undefined.constructor.name//Uncaught TypeError: Cannot read property 'constructor' of null

注意:

  • nullundefined这种非对象无法调用,不然会报错
  • 基本类型还是转换成包装类调用的
  • 本质上和instanceof差不多
  • constructor 是可修改的,这个主要体现在自定义对象上,当开发者重写 prototype 后,原有的 constructor 引用会丢失,constructor 会默认为 Object。更多:JavaScript类型判断的四种方法

如果网页中包含多个框架,就可能存在多个不同的全局环境,不同的全局环境拥有不同的全局对象。由于ObjectArray等构造函数是window对象的属性,所以ObjectArray等构造函数也存在不同的版本。   这导致如果将一个对象从一个全局环境传到另一个全局环境中,instanceof / constructor的判断出错。

例如:

一个页面(父页面)有一个框架,框架中引用了一个页面(子页面),在子页面中声明了一个array,并将其赋值给父页面的一个变量,这时判断该变量,Array == array.constructor;会返回false

原因:

每个页面的Array原生对象所引用的地址是不一样的,在子页面声明的array,所对应的构造函数,是子页面的Array对象;父页面来进行判断,使用的Array并不等于子页面的Array

更多:JS 中对变量类型的五种判断方法

扩展

鸭子类型(duck-typing)

又称:特性判断法,意思是指一个动物,如果看起来像鸭子,走起路来像鸭子,叫起来也像鸭子,那我们就认为他是只鸭子。

function isArray(object) {
  return object !== null && 
    typeof object === 'object' && 
    'splice' in object && 
    'join' in object
}

isArray([]);  // true

这种判断当然是不准确的,不过在部分场景这种做法是可行的。

原生方法

为了解决类型检测的问题,JavaScript引入了一些原生方法来提供支持,比如isNaNArray.isArrayNumber.isInteger等。

isNaN(null / 0)   // true
Array.isArray([]);   // true
Array.isArray(123);  // false
Number.isInteger(1);     // true
Number.isInteger(-1);    // true
Number.isInteger(-1.1);  // false
Number.isInteger('aaa'); // false

自定义判断方法

为什么我们需要封装定义一个获取判断类型的方法?

  • Object.prototype.toString.call()无法判断基本数据类型与包装类的区别:
console.log(Object.prototype.toString.call(1), Object.prototype.toString.call(new Number(1)))// [object Number] [object Number]
  • 不同判断方案各有优缺点,我们需要综合优点,错开缺点
/**
 * @传入参数: Variable: 要判断的数据类型
 * @返回值: Type:传入数据的类型(基本数据类型为全小写,引用数据类型一般首字母大写,根据构造函数而定)
 */
function getTypeof (object) {
  let type
  // 简单判断是否为除Null以外的原始数据类型,我们使用typeof进行简单判断是否为对象类型
  if (typeof object === 'object' || typeof object === 'function') {
    // 获得类型值
    type = Object.prototype.toString.call(object).slice(8, -1)
    // 二次判断,以免是自定义类型
    if (type === 'Object') {
      // 避免构造属性被修改导致没有指向某个构造函数最后name值为空的问题
      type = object.constructor.name ? object.constructor.name : 'Object'
    }
    if (type === 'Null') type = 'null'// 基本类型的null改为小写
  } else {
    // 如是基本类型则使用typeof判断
    type = typeof object
  }
  // 原型链找完依旧不相等返回false
  return type
}

总结

方法 优点 缺点
typeof null的全部基础数据类型判断 能区分包装类对象与基础数据类型的区别
instanceof 判断所有实例是否属于某个构造函数(包括自定义类) 需要已知类型再检查是否为真;不能判断基本数据类型;避免环境不同导致不同版本导致Array判断为假
Object.prototype.toString.call() 能判断JavaScript所有原生引用对象类型 无法将原始类型与包装类区分开;无法判断自定义类型
constructor 判断除nullundefined外的所有对象实例类型 基本类型会误判为包装类;无法判断人为修改constructor的实例;避免环境不同导致不同版本导致Array判断为假
原生方法 简单速记 方法不多,不够全面和通用

更多: