【译】Javascript中的数据类型
这篇文章通过四种方式获取Javascript中的数据类型:通过隐藏的内置[[Class]]属性;通过typeof运算符;通过instanceof运算符;通过函数Array.isArray().我们也会看看原型对象的构造函数,可能会有意想不到的数据类型结果。
[这篇文章是我在adobe发布的文章,我发布在这里只是为了存档。]
- 知识储备
在开始我们的话题之前,我们不得不复习一些所需的知识点
1.1 原始值和对象
Javascript中的数据,要么是原始值,要么是对象。
原始值。下面是原始值:
- undefined
- null
- Booleans
- Numbers
- Strings
原始值是不变的,你不能给它们添加属性:
> var str = "abc";
> str.foo = 123; // try to add property "foo”
123
> str.foo // no change
undefined
原始值是通过数值进行比较的,如果它们有相同的内容就认为相等。
> ‘abc’ === ‘abc’
true
对象。所有非原始值都是对象。对象是可变的:
> var obj = {};
> obj.foo = 123; // try to add property “foo”
123
> obj.foo // property "foo" has been added
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的原型对象。它所有的方法对string也可用,原始值同样拥有String.prototype.indexOf,并不是不同的方法使用相同的名称,相同的方法是:
> String.prototype.indexOf === "".indexOf
true
1.2 内部属性
Javascript中内部属性不能直接访问,但是它影响程序的运行。内部属性的名称以大写字母开始,并且写在双层方括号中。如:[[Extensible]]是一个布尔型的标志,决定对象是否可以扩展属性。它的值可以通过Object.isEntensible()间接的获取它的值,Object.preventExtensions()可以设置它的值为false。一旦变成false之后,就无法再变成true了。
1.3 术语:原型和原型对象
在Javascript中,原型拥有多重含义:
1. 一方面,是原型对象之间的关系。每一个对象都有一个隐藏属性[[Porototype]],指向它的原型对象或者是null。对象的原型是一个延续,如果一个属性无法在对象中找到,可以追溯到它的原型上查找。多个对象可以有相同的对象。
2. 另一方面,如果一个类型是由构造函数Foo实现的,那么这个构造函数有一个原型对象Foo.prototype,保存类型的原型对象。
为了很好的区分,我们写了关于原型(1)和原型对象(2)的例子。三种方法帮助我们处理原型:
- Object.getPrototypeOf(obj),返回obj的原型:
> Object.getPrototypeOf({}) === Object.prototype
true
- Object.create(proto),新建一个原型是proto的空对象。
> Object.create(Object.prototype)
{}
Object.create()可以做的更多,但是超出了这篇文章的范围。
- proto.isPrototypeOf(obj),如果proto是obj的原型(或者是obj原型的原型),返回true
> Object.prototype.isPrototypeOf({})
true
1.4 constructor属性
实现一个构造函数Foo,它的原型Foo.prototype拥有一个属性Foo.prototype.constructor指向构造函数Foo。每个函数都有自动设置这个属性。
> function Foo() { }
> Foo.prototype.constructor === Foo
true
> RegExp.prototype.constructor === RegExp
true
所有构造函数的实例,继承原型对象上的所有属性,我们可以确定一个实例的构造函数。
> new Foo().constructor
[Function: Foo]
> /abc/.constructor
[Function: RegExp]
2. 数据的类型
我们可以通过四种方式获取数据类型:
- [[Class]]是一个内部属性字符串,用来给对象分类
- typeof是一个运算符,用来区分对象和原始值
- instance of是一个运算符,用来分类对象
- Array.isArray()是一个区分数值和数组的函数
2.1 [[Class]]
[[Class]]是一个内部属性,它的值有:
"Arguments", "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", RegExp", “String"
Javascript只能通过toString()方法(Object.prototype.toString())获取。这个方法是通用的,返回:
- "[object Undefined]" 如果值是undefined,
- "[object Null]" 如果值是null,
- "[object " + obj.[[Class]] + "]" 如果是一个对象obj.
- 原始值转化为对象可以按照上面的规则处理
如:
> Object.prototype.toString.call(undefined)
'[object Undefined]'
> Object.prototype.toString.call(Math)
'[object Math]'
> Object.prototype.toString.call({})
'[object Object]’
因此,下面的函数返回[[Class]]的值是x
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) }())
'Arguments' > function Foo() {}
> getClass(new Foo())
‘Object'
2.2 typeof
typeof对原始值进行分类,可以帮助我们区分原始值和对象。
typeof value
下面是一些值和结果的对照关系:
值 |
结果 |
undefined |
“undeifined" |
null |
“object" |
Boolean |
“boolean" |
Number |
“number" |
String |
“string" |
Function |
“function" |
其他值 |
“object" |
typeof null返回object是一个bug,但是不能修复,因为它会破坏现在存在的代码。注意,function也是一个对象,但是typeof做了区分,Array也是一个对象。
2.3 instanceof
instanceof检测值是不是一个构造函数的实例:
value instanceof Type
这个操作符看起来像Type.prototype,检测原型链是否有value。如果我们自己实现的话,就像下面这个样子(会有一些错误,比如说null):
function myInstanceof(value, Type) {
return Type.prototype.isPrototypeOf(value);
}
原始值使用instanceof都会返回false
> "" instanceof String
false
> "" instanceof Object
false
2.4 Array.isArray()
Array.isArray()这个方法存在是因为浏览器的一个特殊的问题:每一个frame都有一个自己的运行环境。如:现在存在frame A和frame B(每一都有自己的document)。通过frame A 传递参数给frame B,frame B中不能通过instanceof判断,传递的参数是不是array。因为frame A中的Array和frame B中Array(array是它的一个实例)并不是同一个。
<html>
<head>
<script>
// test() is called from the 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>
// Fill the iframe
var iframeWin = frames[0];
iframeWin.document.write(
'<script>window.parent.test([])</'+'script>');
</script>
</body>
</html>
因此,ES5中Array.isArray()使用[[Class]]来检查一个值是不是数组。它的意图是使JSON.stringify()更安全,instanceof的问题存在于各个类型中
3. 内置的原型对象
原型对象的内置类型是奇怪的值:他们都是原始的值,而不是实例。这就导致分类很诡异。为了摸清这个诡异的现象,我们需要深入理解分类。
3.1 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没有prototype属性了,它是唯一一个内置对象。
> Object.getPrototypeOf(Object.prototype)
null
这是所有内置对象的一个悖论:它算是实例类型但不是instanceof。
[[Class]], typeof and instanceof 在其他对象上是适用的:
> getClass({})
'Object'
> typeof {}
'object'
> {} instanceof Object
true
3.2 Function.prototype
Function.prototype是它的函数本身,接受任何的参数都返回undefined:
> Function.prototype("a", "b", 1, 2)
undefined
Function.prototype是一个函数,但是不是Function的实例: 一方面, typeof 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.getPrototypeOf(Function.prototype) === Object.prototype
true
其他函数没有什么特别的:
> typeof function () {}
'function'
> getClass(function () {})
'Function'
> function () {} instanceof Function
true
任何场景下,Function还是Function
> typeof Function
'function'
> getClass(Function)
'Function'
> Function instanceof Function
true
3.3 Array.prototype
Array.prototype是一个空数组:它的长度是0.
> Array.prototype
[]
> Array.prototype.length
0
[[Class]]也认为它是array:
> getClass(Array.prototype)
‘Array'
Array.isArray()也是这样的,因为它是基于[[Class]]实现的:
> Array.isArray(Array.prototype)
true
自然而然,instanceof不是这样的:
> Array.prototype instanceof Array
false
在这个章节我们不会提醒y原型对象不是他们构造函数的实例。
3.4 RegExp.prototype
RegExp.prototype是一个匹配任何东西的正则表达式:
> RegExp.prototype.test("abc")
true
> RegExp.prototype.test("")
true
RegExp.prototype也可以使用String.prototype.match通过[[Class]],检测参数是不是一个正则表达式. 检测结果如下:
> getClass(/abc/)
'RegExp'
> getClass(RegExp.prototype)
'RegExp'
空的正则表达式. RegExp.prototype和“空正则表达式”相等。 可以通过一下两种方式实现:
new RegExp("") // constructor 构造函数
/(?:)/ // literal 字面量
如果你想动态的生成一个正则表达式,只能通过构造函数才能创建。通过字面量的方式创建一个空的正则表达式,如: // 是不能直接使用的。应该是用(?:)空的非捕捉分组来实现空的正则表达式。
> new RegExp("").exec("abc")
[ '', index: 0, input: 'abc' ]
> /(?:)/.exec("abc")
[ '', index: 0, input: 'abc' ]
比较发现,空的分组不仅可以完成匹配,并且可以捕捉分组一中:
> /()/.exec("abc")
[ '', // index 0
'', // index 1
index: 0,
input: 'abc’ ]
有意思的是,空正则表达式不管是构造函数形式的还是RegExp.prototype形式的,它们最终的展现结果都是字面量:
> new RegExp("")
/(?:)/
> RegExp.prototype
/(?:)/
3.5 Date.prototype
Date.prototype也是date类型:
> getClass(new Date())
'Date'
> getClass(Date.prototype)
'Date'
日期是数字. 在ES5.1中的描述是这样的:
A Date object contains a Number indicating a particular instant in time to within a millisecond. Such a Number is called a time value. A time value may also be NaN, indicating that the Date object does not represent a specific instant of time. Time is measured in ECMAScript in milliseconds since 01 January, 1970 UTC.
两种方式可以获取日期的时间戳,一种是通过调用valueof()方法,一种是调用Number函数:
> var d = new Date(); // now > 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
3.6 Number.prototype
Number.prototype的值和new Number(0)是一样的:
> Number.prototype.valueOf()
0 转换成数字的话,返回基本数据类型: > +Number.prototype
0 比较: > +new Number(0) 0
3.7 String.prototype
String.prototype和new String("")的值是一样的:
> String.prototype.valueOf()
'' 转化为字符串的话,返回基本数据类型: > "" + String.prototype
'' 比较: > "" + new String("")
''
3.8 Boolean.prototype
Boolean.prototype和new Boolean(false)的值是一样的:
> Boolean.prototype.valueOf()
false
布尔对象可以转化为布尔值,但是所有的结果都是true,因为对象转换成布尔值都是true。
> !!Boolean.prototype
true
> !!new Boolean(false)
true
> !!new Boolean(true)
true
这个对象转化成数字或者字符串不同:如果对象封装了原始值,那么转换结果就是封装的原始值。
译者注:比如我使用Object实例化一个数字,我会这么操作:
> new Object(1);
Number {[[PrimitiveValue]]: 1}
//这就是上面所有的,被封装过的原始值
4. 推荐
本节给出了很多建议,怎么能最好的区分Javascript中的数据类型。
4.1 把原型对象作为原始类型的成员
一个原型对象总是一个原始类型的成员吗?不,这仅仅适用于内置的类型。一般而言,原型对象的行为很神奇,最好是把它们作为模拟类:它们包含所有实例共享的属性(通常方法)。
4.2 使用哪个分类机制
当决定如何最好的使用Javascript中分类机制,必须区分正常的代码和不同frame的代码。
普通代码:在普通代码中,使用typeof或者instanceof,而不是[[Class]]和Array.isArray()。你必须清楚的知道typeof的特殊结果:null的结果是object,有两个非原始值的分类:function和object。如判断一个函数是不是一个对象可以通过下面的方式:
function isObject(v) {
return (typeof v === "object" && v !== null) || typeof v === "function"; }
尝试:
> isObject({})
true
> isObject([])
true
> isObject("")
false
> isObject(undefined)
false
代码跨frame传递:如果接收其他frame传递的值,那么使用instanceof就不再可用了,必须考虑使用[[Class]]或者是Array.isArray()。另外一个选择就是获得构造函数名,但是这个也不是很靠谱:不是所有的对象都有构造函数,也不是所有的构造函数都有名称。下面是如何获得构造函数的名称:
function getConstructorName(obj) {
if (obj.constructor && obj.constructor.name) {
return obj.constructor.name;
} else {
return "";
}
}
另外需要指出的是,函数的name属性(obj.constructor)不是一个标准,如:IE浏览器就不支持。
尝试:
> getConstructorName({})
'Object'
> getConstructorName([])
'Array'
> getConstructorName(/abc/)
'RegExp'
> function Foo() {}
> getConstructorName(new Foo())
'Foo'
如果对原始值使用getConstructorName方法的话,它的值是该类型对应的构造函数:
> getConstructorName("")
'String'
那是因为原始值获取了原型对象上的constructor属性:
> "".constructor === String.prototype.constructor
true
5. 下一步读些什么
通过这篇文章知道Javascript中,怎么对数据进行分类。不幸的是,为了能够正确的执行,需要了解一些详细的知识。作为两个主要的分类是有缺陷的:typeof null是object,instanceof不能跨frame。文章也介绍了解决缺陷的建议。
下一步,需要进一步了解Javascript的继承,下面的四篇博客可以作为入门:
- Prototypes as classes – an introduction to JavaScript inheritance
- JavaScript inheritance by example
- Exemplar patterns in JavaScript
- Private data for objects in JavaScript
原文地址:http://www.2ality.com/2013/01/categorizing-values.html
【译】Javascript中的数据类型的更多相关文章
- 面试说:聊聊JavaScript中的数据类型
前言 请讲下 JavaScript 中的数据类型? 前端面试中,估计大家都被这么问过. 答:Javascript 中的数据类型包括原始类型和引用类型.其中原始类型包括 null.undefined.b ...
- JavaScript 中的数据类型
Javascript中的数据类型有以下几种情况: 基本类型:string,number,boolean 特殊类型:undefined,null 引用类型:Object,Function,Date,Ar ...
- javaScript中的数据类型
一.综述 javaScript中的数据类型分为两类: 简单类型:Boolean,Number,String 引用类型:Object 其他:undefined代表变量没有初始化,null代表引用类型为空 ...
- Javascript中的数据类型之旅
虽然Javascript是弱类型语言,但是,它也有自己的几种数据类型,分别是:Number.String.Boolean.Object.Udefined.Null.其中,Object属于复杂数据类型, ...
- javascript 中检测数据类型的方法
typeof 检测数据类型 javascript 中检测数据类型有好几种,其中最简单的一种是 typeof 方式.typeof 方法返回的结果是一个字符串.typeof 的用法如下: typeof v ...
- 鉴定JavaScript中的数据类型
众所周知,JavaScript是一门弱类型的语言,但是这并不代表JavaScript中没有数据类型.JavaScript中常见的数据类型有string.number.object等等,通常我们使用ty ...
- JavaScript中基本数据类型之间的转换
在JavaScript中共有六种数据类型,其中有五种是基本数据类型,还有一种则是引用数据类型.五种基本数据类型分别是:Number 数值类型.String 字符串类型.Boolean 布尔类型, nu ...
- JavaScript中基本数据类型和引用数据类型的区别(栈——堆)
JavaScript中基本数据类型和引用数据类型的区别 1.基本数据类型和引用数据类型 ECMAScript包括两个不同类型的值:基本数据类型和引用数据类型. 基本数据类型指的是简单的数据段,引用数据 ...
- 简单回忆一下JavaScript中的数据类型
说到JavaScript,大家都应该知道,它是一门脚本语言,也是一门弱类型语言,也是一门解析型的语言,同时也是一门动态类型的语言. 很好,至于JavaScript中数据类型.其分为基本数据类型和复杂数 ...
随机推荐
- 【IIS小技巧】将IIS Express改成可以通过ip地址访问
通过浏览器访问的是localhost,如果通过手机访问则需要用ip地址,所以要修改IIS Express的配置,允许通过ip地址访问. IIS Express的配置文件默认在C:\Users\User ...
- Verilog之i2c合约
说明:i2c乔布斯.有这么多的事情在网上参考. 时刻:2014年5一个月6周二星期 1.问题叙述性说明: 正如图.已知的时钟clk为100k,rst为复位信号.上升沿有效,基于Verilog HDL或 ...
- android常用软件下载资源链接
最新内容请看:http://www.androiddevtools.cn/ https://github.com/inferjay/AndroidDevTools 官方adt下载地址:http://d ...
- 自定义控件(视图)2期笔记01:自定义控件之自定义View的步骤
1. 根据Android Developers官网的介绍,自定义控件你需要以下的步骤: (1)创建View (2)处理View的布局 (3)绘制View (4)与用户进行交互 (5)优化已定义的Vie ...
- http://msh.baidu.com/UTWpR6wY4722
超人的计算机专业应届研究生个人简历,但企业不需要 前几天和一位做人力资源的朋友在饭店里面喝酒,聊起来大学生找工作不好找的话题.我的这个朋友对这个还真比较感兴趣,说着说着从公文包里拿出来一份简历递给我看 ...
- OD: Writing Small Shellcode
第 5.6 节讲述如何精简 shellcode,并实现一个用于端口绑定的 shellcode.原书中本节内容来自于 NGS 公司的安全专家 Dafydd Stuttard 的文章 “Writing S ...
- 一步步启动linux
可以一步一步启动linux. 在Ubantu刚一启动时,按c健即进入Grub>提示符状态,在此状态下输入(我用的是Ubuntu 13) grub>linux /vmlinuz grub&g ...
- Linux CentOS PhpMyAdmin安装
安装好PHP,Apache和MySQL程序后,为了管理MySQL数据库,我们需要安装phpMyAdmin程序.下面是关于如何在centos安装phpMyAdmin程序的方法.1.管理员root身份登录 ...
- LLVM对注释的新增支持 @ WWDC 2013
很久之前我就在想:“我应该按照什么格式写注释,才能像Apple官方API那样按住Option键并点击函数名可以跳出文档说明”,如下图: 我理所当然地认为这个功能应该是根据现有注释的格式来进行排版的,于 ...
- hdu 1711 Number Sequence(KMP模板题)
我的第一道KMP. 把两个数列分别当成KMP算法中的模式串和目标串,这道题就变成了一个KMP算法模板题. #include<stdio.h> #include<string.h> ...