[译]JavaScript:如何判断值的类型
2017-07-18 11:50
381 查看
[译]JavaScript:如何判断值的类型
原文:http://www.adobe.com/devnet/html5/articles/categorizing-values-in-javascript.html转载出自:http://www.cnblogs.com/ziyunfei/archive/2012/10/11/2717057.html
本文中,我将会解释JavaScript一共有几种类型的值,以及如何判断一个值的类型.这将有助于你更好的理解JavaScript是如何工作的.也能帮你实现更高级的编程任务,比如在需要处理各种不同类型的传入值的地方,写一个完善的库进行处理.有了本文中的知识,你就能够避免因两个不同值之间存在细微的差别而引起的bug.
我将会给你展示四种判断值的类型的方法:通过隐藏属性
[[Class]],通过
typeof运算符,通过
instanceof运算符,以及通过函数
Array.isArray().我还会解释在进行类型判断时,为什么内置构造函数的原型对象会有一些不可思议的分类结果.
目录
1.回顾基础知识原始值和对象值
包装对象类型
内部属性
术语:原型和原型对象的区别
constructor属性
2.判断值的类型
[[Class]]
typeof
instanceof
Array.isArray()
3.内置原型对象
Object.prototype
Function.prototype
Array.prototype
RegExp.prototype
Date.prototype
Number.prototype
String.prototype
Boolean.prototype
4.建议
原型对象看作是其类型的原始成员
使用哪种分类机制
5.接下来该学习什么
回顾基础知识
在开始文章的正式部分之前,你需要复习一些必要的知识.原始值和对象值
JavaScript中的所有值不是原始值就是对象值.原始值.下面的值都是原始值:
undefined
null
布尔值
数字
字符串
原始值是不可变的;你不能给它们添加属性:
> var str = "abc"; > str.foo = 123; // 尝试添加属性"foo" 123 > str.foo // 并没有添加上 undefined
另外,原始值是通过值来比较的,也就是,如果它们有相同的内容,就被认为是相等的:
> "abc" === "abc" true
对象值.所有的非原始值都是对象值.对象值是可变的:
> var obj = {}; > obj.foo = 123; // 尝试添加属性"foo" 123 > obj.foo // 属性"foo"被成功添加 123
对象值是通过引用来比较的.每个对象都有自己唯一的标识符,因此,两个对象只有在是同一个对象的情况下才视为相等:
> {} === {} false > var obj = {}; > obj === obj true
包装对象类型
原始值类型boolean,
number以及
string都有自己对应的包装对象类型Boolean,Number和String.后面这几个类型的实例都是对象值,且和它们各自包装的原始值类型有很多不同点:
> typeof new String("abc") 'object' > typeof "abc" 'string' > new String("abc") === "abc" false
包装对象类型很少被直接使用,但它们的原型对象定义了许多其对应的原始值也可以调用的方法.例如,
String.prototype是包装类型
String的原型对象.它的所有方法都可以使用在字符串原始值上.包装类型的方法S
tring.prototype.indexOf.字符串原始值上也有,并不是两个拥有相同名称的方法,而的的确确就是同一个方法:
> String.prototype.indexOf === "".indexOf true
内部属性
内部属性是一些无法用JavaScript代码直接访问的属性,但它们会隐式的影响代码运行结果.内部属性的名称以大写字母开头,且两边用双下划线包围起来.例如,[[Extensible]]就是一个内部属性,存储着一个布尔值,表明能否在该对象身上添加属性.它的值只能被引擎内部管理.
Object.isExtensible()可以读取这个内部属性的值,
Object.preventExtensions()可以将它的值设置为
false.它的值一旦成为
false,就不可能再修改回
true.
术语:原型和原型对象的区别
在JavaScript中,术语"原型(prototype)"很不幸的可能会有点歧义:一方面,对象之间有"谁是谁的原型"这个关系.每个对象都有一个隐藏属性
[[Prototype]],指向了自己的原型(可以是对象值或者原始值
null).一个对象的原型是该对象的延续.如果在该对象上访问一个属性,却没有找到,则会继续去它的原型上找.多个对象可能有相同的原型.
另一方面,如果一个对象obj是由一个构造函数
Foo实例化的,那么构造函数Foo会有一个属性F
oo.prototype,
对象obj
的原型就指向这个属性.
说的再清晰点,开发者们通常把(1)中对象的"[[prototypes]]"内部属性称之为原型,把(2)中函数的"prototype"属性称之为原型对象.有三个方法可以用来操作对象的原型:
Object.getPrototypeOf(obj)返回对象obj的原型:
> Object.getPrototypeOf({}) === Object.prototype true
Object.create(proto)创建一个原型是
proto的空对象:
> Object.create(Object.prototype) {}
Object.create()还可以做更多的其他事,但已经超过本文的介绍范围了.
proto.isPrototypeOf(obj)返回
true,如果proto是
obj的原型(或者原型的原型,依次类推):
> Object.prototype.isPrototypeOf({}) true
"constructor"属性
给定一个构造函数Foo,它的原型对象
Foo.prototype会有一个属性
Foo.prototype.constructor,该属性的值又指向回
Foo.对于大部分用户定义的函数来说,其prototype属性以及prototype属性的constructor属性都会自动生成.(译者注:通过
Function.prototype.bind创建的绑定函数没有原型对象,typeof
function(){}.bind({}).prototype; // "undefined")
> function Foo() { } > Foo.prototype.constructor === Foo true > RegExp.prototype.constructor === RegExp true
构造函数的所有实例都会从其原型对象上继承到constructor属性.因此,你可以用它来判断一个实例是由哪个构造函数创建的:
> new Foo().constructor [Function: Foo] > /abc/.constructor [Function: RegExp]
判断值的类型
这里有四种方式可以用来进行值的分类:[[Class]]是一个内部属性,值为一个类型字符串,可以用来判断对象值的类型.
typeof是一个运算符,用来判断原始值的类型,还可以用来区分原始值和对象值.
instanceof是一个可以判断对象值类型的运算符.
Array.isArray()是一个函数,用来判断某个值是否是数组.
[[Class]]
[[Class]]是一个内部属性,它的值可能是下面字符串中的一个:
"Arguments", "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", "RegExp", "String"
在JavaScript代码中,唯一可以访问该属性的方法就是通过默认的
toString()方法,通常是这样调用的:
Object.prototype.toString.call(value)
可能返回的值有:
"[object Undefined]":如果值是undefined,
"[object Null]":如果值是null,
"[object " + value.[[Class]] + "]":如果值是一个对象.
"[object " + value.[[Class]] + "]":如果值是一个原始值(它会被转换成对应的包装类型,从而返回和上面相同的结果).
例子:
> Object.prototype.toString.call(undefined) '[object Undefined]' > Object.prototype.toString.call(Math) '[object Math]' > Object.prototype.toString.call({}) '[object Object]'
因此,下面的函数可以用来获取到任意值x的
[[Class]]
属性:
function getClass(x) { var str = Object.prototype.toString.call(x); return /^\[object (.*)\]$/.exec(str)[1]; }
运行上面的函数:
> getClass(null) 'Null' > getClass({}) 'Object' > getClass([]) 'Array' > getClass(JSON) 'JSON' > (function () { return getClass(arguments) }()) //译者注:ES3环境下会返回"Object",ES5下才是"Arguments" 'Arguments' > function Foo() {} > getClass(new Foo()) 'Object'
typeof
typeof可以用来判断原始值的类型,以及区分对象值和原始值.语法如下:
typeof value
它会返回下面表1中的某个字符串:
表1.
typeof返回的值
操作数类型 | 返回值 |
undefined | "undefined" |
null | "object" |
布尔值 | "boolean" |
数字 | "number" |
字符串 | "string" |
函数 | "function" |
其他 | "object" |
typeof在操作null时会返回"object",这是JavaScript语言本身的bug.不幸的是,永远不可能被修复了,因为太多已有的代码已经依赖了这样的表现.还需要注意的是,函数也是object类型,但
typeof区分了它们.可数组却没有区分,也被视为object.所有的这些怪异表现让判断一个值的类型是否是object变的复杂:
function isObject(x) { return x !== null && (typeof x === 'object' || typeof x === 'function'); }
instanceof
instanceof可以检测一个值是否是某个构造函数的实例:
value instanceof Type
该运算符会检查
Type.prototype是否处于值value的原型链上.也就是说,如果你自己实现一个
instanceof函数,可以像下面这样(没有进行参数的类型检测,比如参数Type为
null的情况下会出错):
function myInstanceof(value, Type) { return Type.prototype.isPrototypeOf(value); }
如果是原始值,instanceof总会返回
false:
> "" instanceof String false > "" instanceof Object false
Array.isArray()
Array.isArray()之所以存在,是因为在浏览器中:每个框架都有自己的全局运行环境.例如:框架A和框架B(无论谁包含谁),框架A中的代码可以向框架B中的代码传值.但框架B中的代码不能使用
instanceof Array来检测框架A中传来的这个值是否是数组.因为
B中的构造函数Array和A中的Array是不同的:
<html> <head> <script> // test()是从iframe中调用的 function test(arr) { var iframeWin = frames[0]; console.log(arr instanceof Array); // false console.log(arr instanceof iframeWin.Array); // true console.log(Array.isArray(arr)); // true } </script> </head> <body> <iframe></iframe> <script> // iframe中执行父窗口中的函数 var iframeWin = frames[0]; iframeWin.document.write('<script>window.parent.test([])</' + 'script>'); </script> </body> </html>
因此, ECMAScript 5才引入了
Array.isArray(),它使用了内部属性
[[Class]]来判断一个值是否是数组.可是,上面讲的问题不只针对与数组,其他的类型同样也有这个问题
,但我们没有Function.isFunction()等等.
内置原型对象
内置类型的原型对象是很特殊的值: 它们的行为很像是该类型的实例,但如果用instanceof检测的话,得出的结果是它们不是该类型的实例.其他的一些用来判断类型的方法也同样有问题.如果搞清楚了这是为什么,你就能更加深入的理解相关知识.
Object.prototype
Object.prototype是一个空对象:它没有任何可迭代的自身属性:
> Object.prototype {} > Object.keys(Object.prototype) []
意想不到的.
Object.prototype是一个对象,但它却不是Object的实例.一方面,
typeof和
[[Class]]都将它判断为一个对象:
> getClass(Object.prototype) 'Object' > typeof Object.prototype 'object'
另一方面,
instanceof不认为它是Object的实例:
> Object.prototype instanceof Object false
这是因为,如果上面的表达式结果为true,则意味着
Object.prototype会存在于自身的原型链中,这样会导致一个原型链的死循环.原型链不再是线性的了,这种数据结构是无法遍历的.因此,
Object.prototype没有原型(译者注:也可以说,有原型,但值是null).它也是唯一一个内置的没有原型的对象(译者注:Object.creat(null)也可以创建没有原型的对象).
> Object.getPrototypeOf(Object.prototype) null
类似的悖论也适用于所有其他的内置原型对象中:它们在大部分判断
手段下都会被判断为是自身类型的实例,但唯独
instanceof不是.
能预料到的.
[[Class]],
typeof和
instanceof在其他大部分对象上的判断结果都比较一致:
> getClass({}) 'Object' > typeof {} 'object' > {} instanceof Object true
Function.prototype
Function.prototype
也是一个函数.它可以接受任何参数,但总是返回
undefined:
> Function.prototype("a", "b", 1, 2) undefined
意想不到的.
Function.prototype是一个函数,但却不是F
unction的实例:一方面,
typeof(通过检测一个对象是否有
[[Call]]内部属性来判断它是否是函数)说
Function.prototype是一个函数:
> typeof Function.prototype 'function'
[[Class]]内部属性也一样:
> getClass(Function.prototype) 'Function'
但另一方面,
instanceof说
Function.prototype不是
Function的实例.
> Function.prototype instanceof Function false
这是因为
Function.prototype的原型链中没有它自身.它的上一级原型是
Object.prototype(译者注:和Object一样,必须这么规定,否则原型链会有死循环):
> Object.getPrototypeOf(Function.prototype) === Object.prototype true
能预料到的.
其他所有的函数,都没有特殊的表现:
> typeof function () {} 'function' > getClass(function () {}) 'Function' > function () {} instanceof Function true
构造函数Function也被认为是函数:
> typeof Function 'function' > getClass(Function) 'Function' > Function instanceof Function true
Array.prototype
Array.prototype是一个空数组:
> Array.prototype [] > Array.prototype.length 0
[[Class]]内部属性也判断它为数组:
> getClass(Array.prototype) 'Array'
Array.isArray()也同样,因为它本来就是基于
[[Class]]实现的:
> Array.isArray(Array.prototype) true
但是,
instanceof不行:
> Array.prototype instanceof Array false
为了减少重复语句,在文章剩余下部分中,我不会再提到"原型对象不是自身类型的实例"了.
RegExp.prototype
RegExp.prototype是一个可以匹配任意字符串的正则:
> RegExp.prototype.test("abc") true > RegExp.prototype.test("") true
[[Class]]的值为"RegExp"
> getClass(/abc/) 'RegExp' > getClass(RegExp.prototype) 'RegExp'
附加知识:空正则.
RegExp.prototype是一个"空正则".还有两种方式可以创建这样的正则:
new RegExp("") // 构造函数 /(?:)/ // 字面量
如果你需要动态的创建一个正则表达式(译者注:利用变量中存储的字符串),则你必须使用RegExp构造函数.如果想通过字面量创建一个空正则//是不可行的,因为//会被解析成为注释的开始.一个空的非捕获分组
(?:)可以作为一个空正则:它能匹配任意的字符串且不会在匹配过程中创建捕获分组:
> new RegExp("").exec("abc") [ '', index: 0, input: 'abc' ] > /(?:)/.exec("abc") [ '', index: 0, input: 'abc' ]
一个空的捕获分组的匹配结果会在索引为0的位置包含完整的匹配字符串,也会在索引为1的位置包含第一个捕获分组的值:
> /()/.exec("abc") [ '', // index 0 '', // index 1 index: 0, input: 'abc' ]
有趣的是,通过构造函数
创建的空正则和
RegExp.prototype其实都是一个空的非捕获分组:
> new RegExp("") /(?:)/ > RegExp.prototype /(?:)/
Date.prototype
Date.prototype也是一个Date对象:
> getClass(new Date()) 'Date' > getClass(Date.prototype) 'Date'
Date对象包装的是一个数字.引用自ECMAScript
5.1规范:
一个Date对象包含了一个数字,该数字表示了一个特定时间点的时间,单位为毫秒.这个数字称之为时间值.一个时间值也以是NaN,表明这个Date对象不表示一个特定时间点的时间.
ECMAScript中的时间是用从协调世界时1970年1月1日开始的毫秒数来表示的.
两种常用的获取时间值的方法是通过调用其
valueOf方法或者将该日期对象强制转换为数字:
> var d = new Date(); // 现在的时间 > d.valueOf() 1347035199049 > Number(d) 1347035199049
Date.prototype中的时间值是
NaN:
> Date.prototype.valueOf() NaN > Number(Date.prototype) NaN
Date.prototype是一个非法日期,相当于是向构造函数传入
NaN时创建的日期对象:
> Date.prototype Invalid Date > new Date(NaN) Invalid Date
Number.prototype
Number.prototype大致上与
new Number(0)相同:
> Number.prototype.valueOf() 0
将它转换为数字会返回其包装的原始值:
> +Number.prototype 0
和下面的比较一下:
> +new Number(0) 0
String.prototype
类似的,String.prototype大致上于new
String("")相同:
> String.prototype.valueOf() ''
将它转换为字符串会返回其包装的原始值:
> "" + String.prototype ''
和下面的比较一下:
> "" + new String("") ''
Boolean.prototype
Boolean.prototype大致上于new
Boolean(false)相同:
> Boolean.prototype.valueOf() false
Boolean对象可以被强制转换成一个原始布尔值,但转换的结果始终为
true,因为所有的对象值转换成布尔值都是
true.
> !!Boolean.prototype true > !!new Boolean(false) true > !!new Boolean(true) true
这和对象转换为数字和字符串的表现不同.如果一个对象包装了数字或字符串类型的原始值,转换的结果就是那些被包装的原始值.
建议
下面是针对在JavaScript中如何判断一个值的类型的建议.将原型对象看作是其类型的原始成员
原型对象总是其类型的原始成员吗?不是的,只有内置类型是这样的.更有用的想法是把它们看成是模拟类的东西:它们的属性会被所有的实例继承(通常是方法).使用哪种分类机制
在考虑使用哪种分类机制的时候,你要先想想需要处理的值是否可能来自其他框架.如果没这个可能的话,则使用typeof和instanceof,没必要使用
[[Class]]和
Array.isArray().但你必须熟悉
typeof的怪异表现:
null被认为是一个"object"以及存在两个非原始值的类型"object"和"function".例如,下面是一个用来判断是否是对象值的函数.
function isObject(v) { return (typeof v === "object" && v !== null) || typeof v === "function"; }
尝试使用这个方法:
> isObject({}) true > isObject([]) true > isObject("") false > isObject(undefined) false
如果你写的代码可能接受来自其他框架的值,那么
instanceof并不适合.你必须考虑使用
[[Class]]或者A
rray.isArray().另一个替代办法是使用一个对象的constructor属性的值,但这并不是一个靠得住的解决方案,因为并不是所有的对象都记录着自己的构造函数,也并不是所有的构造函数都有名字,另外还有命名冲突的风险.下面的函数演示了如何获取一个对象的构造函数的名字:
function getConstructorName(obj) { if (obj.constructor && obj.constructor.name) { return obj.constructor.name; } else { return ""; } }
另一个需要指出的是:函数的name属性(比如
obj.constructor.name)并不是标准的一部分,例如IE就不支持.尝试运行一下:
> getConstructorName({}) 'Object' > getConstructorName([]) 'Array' > getConstructorName(/abc/) 'RegExp' > function Foo() {} > getConstructorName(new Foo()) 'Foo'
如果将一个原始值传给
getConstructorName(),你会得到该原始值对应的包装类型:
> getConstructorName("") 'String'
这是因为原始值从临时生成的包装对象上获取到了constructor属性:
> "".constructor === String.prototype.constructor true
接下来该学习什么
通过本文,你已经学习了如何在JavaScript中判断一个值的类型.但不幸的是,你必须非常了解这部分知识的细节才能很好的完成这项任务,因为有两个相关的运算符是有缺陷的:typeof的奇怪表现(比如在操作null时返回
"object")以及
instanceof不能识别其他框架中的对象.本文也讲了如何处理这种缺陷.
接下来,你可以开始学习更多关于JavaScript继承的知识.下面的四篇文章可以助你启程:
Prototypes
as classes – an introduction to JavaScript inheritance
JavaScript
inheritance by example
Exemplar
patterns in JavaScript
Private
data for objects in JavaScript
相关文章推荐
- 如何判断javascript中参数类型,对象类型。
- javascript中如何判断变量类型
- javaScript如何简单而准确地判断复杂数据类型
- Javascript如何判断一个变量是数字类型?
- typeof + instanceof+toString+constructor是如何判断javascript数据类型的
- 数据类型:如何判断类型——JavaScript知识小结04
- JavaScript中如何判断变量是数组、函数或是对象类型
- javascript如何判断参数为一个数组类型
- javascript中如何做对象的类型判断
- [译]JavaScript:如何判断值的类型
- javascript 如何判断一个对象的类型
- JavaScript中如何判断一个值的类型
- 举例讲解如何判断JavaScript中对象的类型
- 举例讲解如何判断JavaScript中对象的类型
- 如何判断JavaScript数据具体类型
- javascript如何判断变量类型
- javascript:如何判断浏览器类型
- [译]JavaScript:如何判断值的类型
- 如何在客户端判断浏览器的类型(Detecting IE7+ in JavaScript)
- Javascript如何判断数据类型和数组类型