JavaScript 是一种弱类型 或者说动态 语言。这意味着你不用提前声明变量的类型,在程序运行过程中,类型会被自动确定。这也意味着你可以使用同一个变量保存不同类型的数据:
复制 let a = 42
a = "bar"
a = true
a = {}
最新的 ECMAScript 标准定义了 8 种数据类型:
基本类型(7种)
基本类型也称为简单类型,存储于栈内存中,数据大小确定,内存空间大小可以分配,是按值存储的,可以直接访问 。
引用类型(1种)
Object
是唯一的复杂数据类型。 Object、Function 、Array、RegExp、Date
这些引用类型值最终都可以归结为 Object
复杂数据类型。
引用类型存储于堆内存中。而存储在变量处的值,是一个指针,指向存储对象的内存处,即按址访问 。
除 Object 以外的所有类型都是不可变的(值本身无法被改变)。例如,与 C 语言不同,JavaScript 中字符串是不可变的(如:JavaScript 中对字符串的操作一定返回了一个新字符串,原始字符串并没有被改变)。我们称这些类型的值为"原始值”。
鉴于ECMAScript 是弱类型的语言,因此需要有一种手段来检测给定变量的数据类型。对于这个问题,JavaScript 也提供了多种方法,但遗憾的是,没有一种是完美,每种方法都有自己的优缺点。
复制 let objT = {
str : '' ,
num : 1 ,
bool : true ,
undef : undefined ,
nul : null ,
symbol : Symbol () ,
bigInt : BigInt ( 10 ) ,
fun : new Function () ,
arr : [] ,
numN : new Number () ,
boolN : new Boolean () ,
strN : new String () ,
date : new Date () ,
reg : new RegExp () ,
}
typeof
typeof
操作符返回一个字符串,表示未经计算的操作数的类型。
返回该类型的字符串(全小写字母)形式表示,包括以下 8 种:string、number、boolean、symbol、bigint、undefined、function、object 等。
有些时候,typeof 操作符会返回一些令人迷惑但技术上却正确的值:
对于 null ,返回'object '
复制 // JavaScript 诞生以来便如此
typeof null === 'object' ;
在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null
代表的是空指针(大多数平台下值为 0x00),因此,null 的类型标签是 0,typeof null
也因此返回'object'
。
曾有一个 ECMAScript 的修复提案(通过选择性加入的方式),但被拒绝了 。该提案会导致 typeof null === 'null'
。
引用类型Function,返回'function'
复制 let func1 = new Function ()
let func2 = function () {}
typeof func1 //"function"
typeof func2 //"function"
除 Function 外的所有构造函数的类型都是'object'
复制 // 除 Function 外的所有构造函数的类型都是 'object'
let str = new String ( 'String' );
let num = new Number ( 100 );
let func = new Function ();
typeof str; // 返回 'object'
typeof num; // 返回 'object'
typeof func; // 返回 'function'
对正则表达式字面量的类型判断在某些浏览器中不符合标准:
复制 typeof / s / === 'function' ; // Chrome 1-12 , 不符合 ECMAScript 5.1
typeof / s / === 'object' ; // Firefox 5+ , 符合 ECMAScript 5.1
当前所有的浏览器都暴露了一个类型为 undefined
的非标准宿主对象 document.all
。
复制 typeof document .all === 'undefined' ;
尽管规范允许为非标准的外来对象自定义类型标签,但它要求这些类型标签与已有的不同。document.all
的类型标签为 'undefined'
的例子在 Web 领域中被归类为对原 ECMA JavaScript 标准的“故意侵犯”。
在 ECMAScript 2015 之前,typeof
总能保证对任何所给的操作数返回一个字符串。即便是没有声明的标识符,typeof
也能返回 'undefined'
。使用 typeof
永远不会抛出错误。
但在加入了块级作用域的 let 和 const 之后,在其被声明之前对块中的 let
和 const
变量使用 typeof
会抛出一个 ReferenceError 。块作用域变量在块的头部处于“暂存死区 ”,直至其被初始化,在这期间,访问变量将会引发错误。
复制 for ( let k in objT) {
console .log ( typeof objT[k])
}
let objT = {
str : '' , //string
num : 1 , //number
bool : true , //boolean
undef : undefined , //undefined
nul : null , //object
symbol : Symbol () , //symbol
bigInt : BigInt ( 10 ) , //bigint
fun : new Function () , //function
//以下都为object
arr : [] ,
numN : new Number () ,
boolN : new Boolean () ,
strN : new String () ,
date : new Date () ,
reg : new RegExp ()
}
instanceof
instanceof
运算符 用于检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上。即用来判断 object 是否为 constructor 的实例,如果 object 是 constructor 的实例,则返回 true,否则返回 false。表达式为:
复制 object instanceof constructor
不能用来判断7种基本类型值,因为基本数据类型没有原型链
复制 objT .str instanceof String //false
objT .num instanceof Number //false
objT .bool instanceof Boolean //false
objT .undef instanceof undefined //Uncaught TypeError: Right-hand side of 'instanceof' is not an object
objT .nul instanceof null //Uncaught TypeError: Right-hand side of 'instanceof' is not an object
objT .symbol instanceof Symbol //false
objT .bigInt instanceof BigInt //false
不能正确判断 Object 类型
instanceof 只能用来判断两个对象是否属于实例关系, 而不能判断一个对象实例具体属于哪种类型。
复制 [] instanceof Array // true
[] instanceof Object ; // true
function Person (){}
new Person () instanceof Person //true
new Person () instanceof Object ; // true
prototype 属性的值是可变的
复制 function A ( ){}
let a = new A ()
a instanceof A //true
//修改构造函数的prototype
A . prototype = null
a instanceof A //false
//修改对象的原型链
b . __proto__ = null
b instanceof A //false
构造函数的 prototype
属性的值有可能会改变,改变之后的值很有可能不存在于 object
的原型链上。
对象 obj
的原型链的也是可以修改的。虽然在目前的ES规范中,我们只能读取对象的原型而不能改变它,但借助于非标准的 __proto__
伪属性,是可以实现的。
不能用于跨窗口判断
在浏览器中,我们的脚本可能需要在多个 frame 或 window 之间的交互。多个窗口意味着多个全局环境,不同的全局环境拥有不同的全局对象,从而拥有不同的内置类型构造函数。这可能会引发一些问题。比如:
复制 document . body .appendChild ( document .createElement ( 'iframe' ))
let xArray = window .frames[ 0 ].Array
let arr = new xArray ( 1 , 2 , 3 )
arr instanceof xArray //true
arr instanceof Array //false
针对数组的这个问题,ES5 提供了 Array.isArray() 方法 。该方法用以确认某个对象本身是否为 Array 类型,而不区分该对象在哪个环境中创建。
constructor
constructor 属性返回一个指向创建了该对象原型的函数引用。需要注意的是,该属性的值是那个函数本身。所有对象都会从它的原型上继承一个 constructor
属性。
复制 obj . constructor === Constructor
null 和 undefined 是无效的对象,因此是不会有 constructor 存在的,这两种类型的数据需要通过其他方式来判断。
复制 objT . undef . constructor //Cannot read property 'constructor' of undefined
objT . nul . constructor //Cannot read property 'constructor' of null
其他基本数据类型访问时,JavaScript会自动调用其构造函数来生成一个对象
复制 ( 0 ). constructor === Number // true
true . constructor === Boolean // true
'a' . constructor === String // true
// 相当于
( new Number ( 0 )). constructor === Number
( new Boolean ( true )). constructor === Boolean
( new String ( 'a' )). constructor === String
对象的constructor是可变的
复制 function A ( ){}
let a = new A ()
a . constructor === A //true
//修改对象的constructor
a . constructor = null
a . constructor === A //false
不能用于跨窗口判断
与instanceof类似,constructor在不同的窗口拥有不同的内置构造函数。
复制 document . body .appendChild ( document .createElement ( 'iframe' ))
let xArray = window .frames[ 0 ].Array
let arr = new xArray ( 1 , 2 , 3 )
arr . constructor === Array //false
arr . constructor === xArray //true
toString
toString
方法是最为可靠的类型检测手段,它会将当前对象转换为字符串并输出。 toString
属性定义在Object.prototype
上,因而所有对象都拥有toString
方法。 但Array
, Date
等对象会重写从Object.prototype
继承来的toString
, 所以最好用Object.prototype.toString
来检测类型。
复制 //目前是最可靠的了,它总能返回正确的值
Object . prototype . toString .call (variable)
无法检测用户自定义类型
toString
也不是完美的,它无法检测用户自定义类型。 因为Object.prototype
是不知道用户会创造什么类型的, 它只能检测ECMA标准中的那些内置类型。
复制 function A ( ){}
let a = new A ()
Object . prototype . toString .call (a) //[object Object]
避免了跨窗口问题
因为返回值是字符串,也避免了跨窗口问题。当然IE弹窗中还是有Bug,不必管它了。
常见问题
内部属性 [[Class]] 是什么?
每个对象在创建时都会带有一个 [[class]] 的内部属性,这个属性无法直接访问,可通过 Object.prototype.toString()
来查看。
复制 Object . prototype . toString .call ([]) // '[object Array]'
Object . prototype . toString .call ( /a/ i ) // '[object RegExp]'
一般来说,对象的内部 [[class]] 属性和创建该对象的构造函数 是相对应的,但是也有例外,如,null|undefined
。总的来说, [[class]] 属性代表的是数据的类型值。
对象的 [[class]] 属性:
复制 Object . prototype . toString .call ({}) // '[object Object]'
Object . prototype . toString .call ( new Date ()) // '[object Date]'
基本类型值的 [[class]] 属性:
复制 Object . prototype . toString .call ( "abc" ) // '[object String]'
Object . prototype . toString .call ( 1234 ) // '[object Number]'
Object . prototype . toString .call ( true ) // '[object Boolean]'
Object . prototype . toString .call ( null ) // '[object Null]'
Object . prototype . toString .call ( undefined ) // '[object Undefined]'
总结
typeof
只适用于基本数据类型,对于 null
还有 Bug;
instanceof
适用于构造函数创建的对象,它是基于原型链运作的;
constructor
适用于 ECMA 内置 JavaScript 类型和构造函数创建的对象,无法判断 null、undefined;
toString
适用于 ECMA 内置 JavaScript类型(包括基本数据类型和内置对象)的类型判断;
instanceof
和constructor
是不安全的,且都有跨窗口问题。
总之,如果你要判断的是基本数据类型或 JavaScript 内置对象,使用 toString; 如果要判断的是自定义类型,请使用 instanceof 或 constructor 。
参数链接
判断JS数据类型的四种方法
如何检查 JavaScript 变量类型?