整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

JavaScript的数据类型及判断

JavaScript的数据类型及判断

文同步本人掘金平台的文章:https://juejin.cn/post/6844903858112577549

最近自己回归基础看了下javascript的相关知识点,想着看都看了,写出来记录下足迹也是一件好事,遂记录~

在javascript中有两种类型,一种是基本数据类型,一种是引用类型。

基本类型

基本数据类型,也称为简单数据类型,在ES5中有以下五种:Undefined、Null、Boolean、Number和String,在ES6中新增了一种简单的数据类型Symbol。

Undefined类型

Undefined类型只有一个值undefined。在进行相关变量定义的时候,未赋值的情况下,默认是赋值为undefined了。但是也是有些特殊的情况下会报错的。情况我大致罗列下:

# 情况1??:变量声明了,但是没有赋值
var message;
console.log(message); // undefined

# 情况2??:变量声明并赋值了,但是在console运行之后
console.log(message); // undefined
var message='find a frontend job in Canton!';

# 情况3??:变量没声明,报引用报错
// var message;
console.log(message); // Uncaught ReferenceError: message is not defined

# 情况4??:不通过var声明,直接写变量,报引用错误
message; // 不等价 var message;
console.log(message); // Uncaught ReferenceError: message is not defined

# 情况5??:不通过var声明,直接写变量赋值
message='find a frontend job in Canton!'; // 默认在message前添加了var
console.log(message); // find a frontend job in Canton!

# 情况6??:不通过var声明,直接写赋值,但是在console运行之后,报引用错误
console.log(message);
message='find a frontend job in Canton!'; // 相当于没message变量
复制代码


上面罗列的是ES5中通过var声明的情况。也许你会对情况2??产生疑惑:我都给message赋值了啊,但是打印出undefined,这就有点尴尬了?


因为在js中执行上下文分为两个阶段,第一个阶段是创建阶段,第二个阶段才是执行阶段。

上面情况2??的执行情况如下:

1. 创建阶段:

executionContextObj={
	scopeChain: { ... },
	variableObject: {
		message: undefined
	},
	this: { ... }
}
复制代码

2. 执行阶段:

executionContextObj={
	scopeChain: { ... },
	variableObject: {
		message: 'find a frontend job in Canton!'
	},
	this: { ... }
}
复制代码

详细的解析可以看下我之前翻译的一篇文章JS的执行上下文和环境栈是什么?。

上面讲到的是var,我们引入ES6的let 和 const来演示下:

# 情况7??:let声明变量赋值
let message;
console.log(message); // undefined

# 情况8??:let声明变量但是不赋值,在console运行之后
console.log(message); // Uncaught ReferenceError: Cannot access 'message' before initialization
let message='find a frontend job in Canton!';

# 情况9??:const声明变量但是不赋值,报语法错误
const message;
console.log(message); // Uncaught SyntaxError: Missing initializer in const declaration
复制代码

let和const改变了var命令会发生变量提升的现象,即变量可以在声明之前使用,值为undefined。它们改变了这种奇怪的现象,声明的变量一定要在声明之后使用,否则报错。

当然还有其他声明变量的方法,比如function命令等,这里不一一列举,只是探讨下undefined的值而已~

Null类型

Null类型的值是null。从逻辑角度来看,null值表示一个空对象指针。

如果定义的变量准备在将来用来保存对象,那么最好就是将变量初始化为null,而不是其他的数据值。这样,只要直接检查null值就可以知道相应的变量是否已经保存了一个对象的引用。如下面的例子:

if(car !=null) {
	// 对car对象执行某些操作
}
复制代码

undefined值是派生自null值的。虽然两者在==比较时候是相等的,如下:

console.log(null==undefined); // true
复制代码

当变量不确定类型的时候,可以不用为变量赋值,也就是默认赋值undefined了。但是如果你知道你的变量要保存对象但是还没有真正保存对象的时候就要赋值null了。

Boolean类型

Boolean类型在日常生活中使用频繁了,其值是true和false,对应我们口头的是和否。

将布尔值的true和false转换为数值的话,可以用非0和0数字表示。

console.log( 1==true); // true

console.log( 0==false); // true
复制代码

如果是恒等的比较方式===,那数字表示法是要凉凉的~

Number类型

Number类型有二进制表示法,八进制表示法,十六进制表示法和十进制表示法。这里只讨论十进制表示法,因为在平常的开发中,用到十进制的情况居多

这个类型用来表示整数值和浮点数值(即带小数点的值)。

整数值的基本操作很是简单,而且没啥bug好说,除非不在Number.MIN_VALUE和Number.MAX_VALUE范围内。带小数点的还是要留意下的,比如:

let a=13.04;
let b=2.5;
console.log(a + b); // 15.54
console.log(a * b); // 32.599999999999994
console.log(a - b); // 10.54
复制代码

咦咦,真是让人尴尬,怎么上面代码中两个浮点数相乘会出现那么多位的数字啊,不是等于32.6吗?

所以在进行浮点数的运算的时候还是得慎重点,先转换成整数计算,之后再切换回去浮点数,比如上面的a * b可以考虑写成(a * 100 * (b * 10))/1000。

当你要判断一个值是否是数值,可以使用isNaN来表示,其返回一个布尔值,如下:

console.log(isNaN(NaN)); // true
console.log(isNaN(10)); // false
console.log(isNaN('10'); // false , '10'会被转化为10
console.log('blue'); // true , 不能转化为数值
console.log(true); // false, 可被转化为数值1
复制代码

还有将非数值转化为数值的三个方法:Number()、parseInt()和parseFloat()。见名思义:

**Number()是将传入的内容转换为数字(整数)或NaN。但是在转换字符串的时候比较复杂,一般用parseInt()**居多。**parseFloat()**就是转化成浮点数的方法啦。

String类型

String类型也就是字符串类型啦。

字符串类型包含一些特殊的字符字面量,也叫转义序列,用来表示非打印字符串。比如换行符\n啦。

在实际的开发中,我们需要将数字类型或对象类型转换成字符串类型,那么我们可以直接使用toString()方法进行操作啦。好吧,这api的东西大家都会用,就不说了

Symbol类型

Symbol类型是ES6引入的新类型,为了防止对象中属性名冲突的问题。

Symbol值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的Symbol类型。凡是属性名属于Symbol类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。

具体的看下阮一峰的es6入门中Symbol部分。

上面说到的是6种基本的数据类型,还有一种是引用类型。

引用类型

引用类型:当复制保存对象的某个变量时,操作的是对象的引用,但是在为对象添加属性时,操作的是实际的对象。引用类型值指那些可能有多个值构成的对象。

引用类型有这几种:Object、Array、RegExp、Date、Function、特殊的基本包装类型(String、Number、Boolean)以及单体内置对象(Global、Math)。

基本包装类型这个有点好玩,咦?上面的基本数据类型都有String、Number和Boolean啦,怎么这里还有这些。是的,上面的基本类型是通过基本包装类型来创建的。如下:

var s1='find a frontend job in Canton';
var s2=s1.substring(2);
复制代码

上面的代码实际进行了下面的步骤:

(1)创建String类型的一个实例;

(2)在实例中调用指定的方法;

(3)销毁这个实例。

上面的三个步骤转化为代码如下:

var s1=new String('find a frontend job in Canton');
var s2=s1.substring(2);
s1=null;
复制代码

(正规)的引用类型和基本包装类型的主要区别就是对象的生存期。使用new操作符创建的引用类型的实例,在执行流离开当前作用域之前都一直保存在内存中。而自动创建的基本包装类型的对象,则只存在于下一行代码的执行瞬间,然后立即被销毁。这意味着我们不能在运行时为基本类型值添加属性和方法。来看下下面的例子:

var s1='find a frontend job in Canton';
s1.name='jia ming';
console.log(s1.name); // undefined
复制代码

只能通过基本包装类的原型来添加了,比如改写toString方法:

var s1='find a frontend job in Canton';
String.prototype.toString=function() {
    console.log('my name is jia ming');
}
console.log(s1.toString()); // my name is jia ming
复制代码

嗯~苦口婆心介绍了javascript的数据类型,那么下面才是重头戏。我们在实际的开发中,如何识别不同的数据类型呢?

数据类型判断

数据类型有上面的7种类型,其中基本类型是Undefined、Null、Boolean、Number、String和Symbol,还有一种引用类型。引用类型又包含比较多种的对象,比如Object、Array等。

我们首先想到的是通过typeof来判断,直接上代码来试下吧:

let symbol=Symbol('jia ming');
let str='find a frontend job in Canton!';
let flag=true;
let height=99;
let job;
let obj=null;
console.log(typeof symbol); // symbol
console.log(typeof str); // string
console.log(typeof flag); // boolean
console.log(typeof height); // number
console.log(typeof job); // undefined
console.log(typeof obj); // object
复制代码

嗯~很ok啦,对基本的数据类型都能判断到啦,这个null得到的结果是object,你可以当成特殊情况来处理啦 -- 无中生有,一生万物嘛。

我们再来看下引用类型打印出来的是什么东东

let person={
    name: 'jia ming',
    info: 'find a frontend job in Canton!',
};
let arr=['jia ming', 'find a frontend job in Canton!'];
let reg=new RegExp('jia ming', 'g');
let date=new Date();
let fn=()=> {
    return 'find a frontend job in Canton!';
}
let math=Math.min(2, 4, 8);
console.log(typeof person); // object
console.log(typeof arr); // object
console.log(typeof reg); // object
console.log(typeof date); // object
console.log(typeof fn); // function
console.log(typeof math); // number
复制代码

咦咦~着实让人尴尬啊,这个为啥那么多object啊,我的小心脏。我们只是简单通过typeof校验比较尴尬啊,我们换个思路,我们来结合call改变下上下文对象,改写一个方法进行判断,如下:

let person={
    name: 'jia ming',
    info: 'find a frontend job in Canton!',
};
let arr=['jia ming', 'find a frontend job in Canton!'];
let reg=new RegExp('jia ming', 'g');
let date=new Date();
function handleType(obj, type) {
    if(typeof obj==='object') {
        return Object.prototype.toString.call(obj)===`[object ${type}]`;
    }
    return false;
}
console.log(handleType(person, 'Object')); // true
console.log(handleType(arr, 'Array')); // true
console.log(handleType(reg, 'RegExp')); // true
console.log(handleType(date, 'Date')); // true
复制代码

美滋滋,可以实现区别判断的哈。可是上面的基本类型中null也是object啊,然后是Math类型的typeof也是number啊,这个你可以自己做下处理啦。这里就不考虑了~

参考

  • 《JavaScript高级程序设计》
  • 阮一峰 ECMAScript 6 入门
npm install type-yes

目地址:github.com/liutaigang/…

About

首先通过一个例子来认识下 Ty:

一个方法的参数类型判断的例子,如:

function func(value) {
    if( value 为 string 或 number 或 为空时 ) {
        ... do something
    }
}


判断方式:

// 方式一:常规版
typeof value==='string' || typeof value==='number' || value==null

// 方式二:Lodash 版
_.isString(value) || _.isNumber(value) || _.isNil(value)

// 方式三:Ty 版
Ty(value).str.num.nil.or


Ty 版的判断是最简洁的!!!,但是也会让人有些疑惑——上述表达式:Ty(value).str.num.nil.or,它如何实现判断的?下面分析下:

  • 判断参数:需要判断的量,可以是任意类型
  • 类型标识符:类型的“符号”。str—— string,num —— number, nil —— null or undefined
  • 逻辑运算符:最终逻辑运算方式。or —— 或运算

上述表达式可以简单理解为:

// 当 value=123

[[value, 'str'], [value, 'num'], [value, 'nil']]==(判断类型)==> [false, true, false]==(或运算)==> true


到了这里,你大概已经了解 Ty 的逻辑符 or 的使用,除了 or , Ty 还有 is,not,and,nor,nand

Usage

is

逻辑”是“判断

// 常规
typeof value==='number'
// Ty
Ty(value).num.is

// Ty error, 当进行 is 判断时,如果判断参数(或判断标识符)输入多个值时,会报错
Ty(value01, value02).num.is // error
Ty(value).num.str.is // error


not

逻辑”否“判断, is 的取反

// 常规
typeof value !='number'
// Ty
Ty(value).num.not

// Ty error, 当进行 not 判断时,如果判断参数(或判断标识符)输入多个值时,会报错。与 is 判断相同


or

逻辑”或“判断

// 常规
typeof value==='string' || typeof value==='number'
// Ty
Ty(value).str.num.or

// 等价于:
Ty(value, value).str.num.or // 参数会自动补全,所以这样写就“没必要”了


nor

逻辑”或非“判断, or 的取反

// 常规
!(typeof value==='string' || typeof value==='number')
// Ty
Ty(value).str.num.nor


and

逻辑“与”判断

示例一:

// 常规
typeof value01==='string' && typeof value02==='number'
// Ty
Ty(value01, value02).str.num.and


示例二:

// 常规
typeof value01==='string' && typeof value02==='string'
// Ty
Ty(value01, value02).str.and

// 等价于:
Ty(value01, value02).str.str.and // 标识符也会自动补全,所以这样写就“没必要”了


nand

逻辑“与非”判断,and 的取反

// 常规
!(typeof value01==='string' && typeof value02==='number')
// Ty
Ty(value01, value02).arr.num.nand


上述的判断中,除了所有的逻辑操作符的使用方法,我还认识了 num、str 、nil 等类型标识符。在 Ty 中,类型标识符共有 60+,其中包括:简写类型标识符特殊类型标识符常规类型标识符,下面我们将一一介绍:

简写类型标识符

简写标识符

对应的常规标识类

实际类型

obj

object

Object (这里的 object, 不包含 array 和 null )

arr

array

Array

str

string

String

num

number

Number

bool

boolean

Boolean

undef

undefined

undefined

func

function

Function

特殊类型标识符

标识符

实际类型

nil

null 或 undefined

empty

[] 或 {}

emptyobject

{} —— 没有任何属性的空对象

emptyarray

[] —— 没有任何元素的空数组

NaN

NaN

infinity

Infinity 无穷大

primitive

原始类型: null, undefined, boolean, number, bigint, string, symbol

示例:

const isPrimitive=Ty(value).primitive.is // value=Symbol()
const isEmpty=Ty(value).empty.is // value=[]


常规类型标识符

标识符

实际类型

null

null (不包含 undefined)

undefined

undefined

boolean

Boolean

number

Number

string

String

bigint

BigInt

symbol

Symbol

object

Object (这里的 object, 不包含 array 和 null )

array

Array

function

Function

promise

Promise

date

Date

regexp

RegExp

map

Map

set

Set

......更多的请看附录


示例:

const isIterator=Ty(value).array.map.set.or
cosnt isPrimitive=Ty(value).null.undefined.boolean.number.string.bigint.symbol.or


More

扩展的 Ty 的类型标识符

如果已有的类型标识符不满足时, Ty 支持扩展,只要提供一个 TypeMatcher , 即类型匹配器:

type TypeMatcher<T extends string>=(parameter: any, typeFlag: T)=> boolean;


示例(ts):

import { Ty, TypeMatcher, TypeFlag, buildinTypeMatcher } from 'type-yes';

type MyType='element' | 'finite' | TypeFlag; // TypeFlag 是 Ty 的所有的类型标识符的一个联合类型
const typeMather: TypeMatcher<MyType>=(parameter, typeFlag)=> {  // parameter —— 判断参数, typeFlag —— 类型标识符
  switch (typeFlag) {
    case 'element':
      return parameter instanceof Element;
    case 'finite':
      return Number.isFinite(parameter);
    default:
      return buildinTypeMatcher(parameter, typeFlag); // buildinTypeMatcher —— Ty 内置的类型匹配器
  }
};

const tty=new Ty(typeMather);


使用效果(element 和 finite 会出现在拼写提示中):

Proxy 如何判断

Proxy 类型是难以判断的——Proxy 代理的对象是什么类型,proxy 实例就判定为相应的类型,如:

const arr=['a', 'b', 'c'];
const arrProxy=new Proxy(arr, {});
typeof arrProxy; // array
Object.prototype.toString.call(arrProxy); // [object Array]


Ty 中,继承 Proxy 实现了一个子类:IdentifiableProxy,这个子类的类型是可以判断的,如:

const arr=['a', 'b', 'c'];
const arrProxy=new IdentifiableProxy(arr, {});
Object.prototype.toString.call(arrProxy); // [object Proxy-Array]

// 使用 Ty 判断
Ty(arrProxy).proxy.is; // true —— 做 proxy 判断时,arrProxy 判定为 proxy
Ty(arrProxy).array.is; // true —— 做 array 判断时,arrProxy 判定为 array
Ty(arrProxy).array.proxy.and; // true


类型标识符的“否运算“

如何使用 Ty 实现下面这样一个类型判断:

typeof value01==='object' && typeof value02 !='number'


在 Ty 中,可以对单个类型标识符进行否运算:! + 类型标识符,如:

Ty(value01, value02).obj['!num'].and


Appendix

常规类型标识符附录

标识符

对应类型

error

Error

reflect

Reflect

json

JSON

math

Math

int8array

Int8Array

uint8array

Uint8Array

uint8clampedarray

Uint8ClampedArray

int16array

Int16Array

uint16array

Uint16Array

int32array

Int32Array

uint32array

Uint32Array

bigint64array

BigInt64Array

biguint64array

BigUint64Array (en-US)

float32array

Float32Array

float64array

Float64Array

weakmap

WeakMap

weakset

WeakSet

arraybuffer

ArrayBuffer

atomics

Atomics

dataview

DataView

weakref

WeakRef

finalizationregistry

FinalizationRegistry (en-US)

iterator

Iterator

proxy

Proxy

intl

Intl

intl.collator

Intl.Collator

intl.datetimeformat

Intl.DateTimeFormat

intl.displaynames

Intl.DisplayNames

intl.listformat

Intl.ListFormat

intl.locale

Intl.Locale

intl.numberformat

Intl.NumberFormat

intl.pluralrules

Intl.PluralRules

intl.relativetimeformat

Intl.RelativeTimeFormat

intl.segmenter

Intl.Segmenter

global

node 环境下的 globalThis

window

window 环境下的 globalThis 或 window


作者:_code_bear_
链接:https://juejin.cn/post/7351321160809725990


JavaScript中,有多种方法可以判断一个变量的数据类型。以下是一些常见的方法:

  1. typeof 操作符

typeof 是JavaScript中的一元操作符,返回一个表示未计算变量类型或已计算对象类型的字符串。但是,需要注意的是 typeof 对于 null 和 array 的处理可能不是你所期望的:


console.log(typeof undefined);    // "undefined"  
console.log(typeof 123);          // "number"  
console.log(typeof 'hello');      // "string"  
console.log(typeof true);         // "boolean"  
console.log(typeof {});           // "object"  
console.log(typeof []);           // "object" 而不是 "array"  
console.log(typeof null);         // "object" 而不是 "null"  
console.log(typeof function(){}); // "function"
  1. instanceof 操作符

instanceof 操作符用于检测构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。这主要用于检测对象是否属于某个类。

console.log([] instanceof Array);  // true  
console.log(null instanceof Object); // false,因为 null 不是一个对象
  1. Array.isArray() 方法

这是检测一个值是否为数组的最佳方法。

console.log(Array.isArray([]));  // true  
console.log(Array.isArray({}));  // false
  1. Object.prototype.toString.call() 方法

这个方法返回表示该对象的字符串。对于检测原始值类型,特别是当 typeof 给出不直观的结果时(如 null 和 array),这是一个很有用的方法。

function getType(obj) {  
    return Object.prototype.toString.call(obj).slice(8, -1);  
}  
  
console.log(getType(null));       // "Null"  
console.log(getType([]));         // "Array"  
console.log(getType({}));         // "Object"  
console.log(getType(123));        // "Number"  
console.log(getType('hello'));    // "String"  
console.log(getType(true));       // "Boolean"  
console.log(getType(undefined));  // "Undefined"
  1. constructor 属性

每个JavaScript对象都有一个 constructor 属性,它指向创建该对象的构造函数。但请注意,如果 constructor 被手动修改,则可能不准确。

console.log(([]).constructor===Array);  // true  
console.log(({}).constructor===Object); // true
  1. ES6 中的 Symbol.toStringTag

某些内置对象(如 Array、Date、RegExp 等)的 @@toStringTag 属性值是一个字符串,该字符串用于定制 Object.prototype.toString.call(obj) 的默认行为。但通常你不需要直接使用这个属性,除非你在实现自定义对象并希望改变 Object.prototype.toString.call(obj) 的默认行为。