Javascript中值的分类
本文为自己读http://www.adobe.com/devnet/html5/articles/categorizing-values-in-javascript.html?utm_source=javascriptweekly&utm_medium=email 的笔记和部分翻译,不对之处,敬请指正!
在 Javascript 中值 (value) 可以通过以下几种方式进行分类:
(1)通过隐藏的属性[[Class]]。
(2)通过 typeof 操作符。
(3)通过 instanceof 操作符。
(4)通过 Array.isArray 函数。
基础知识复习:
基础类型(primitives) VS 对象(objects)
在 Javascript 中,一个值要么是基础类型要么就是对象。
基础类型,下面的值就是属于基层类型的:
- undefined
- null
- Booleans
- Numbers
- Strings
基础类型是不可改变的;你不能给它们添加属性:
var str = "abc"; str.foo = 123; //给其添加属性“foo” console.log(str.foo); //undefined
并且,基础类型是通过值来进行比较的,意思是说如果它们拥有相同的内容,那么它们就认为是相等的:
“abc” === “abc” //true
对象,所有不是基础类型的值都是对象。对象是可以改变的:
var obj = {}; obj.foo = 123; console.log(obj.foo);//"foo"属性被添加上了 123
对象是通过引用来进行比较的。每一个对象都有自己独有的标识。因此要两个对象相等,只能是它们是同一个对象。
console.log({} === {}) //false var obj = {}; console.log(obj === obj); //true
包装对象类型 (Wrapper object types)
基础类型的 boolean, number 和 string 都有相对应的包装对象类型Boolean, Number 和 String.包装对象类型的实例是对象,它们和基础类型值是不相同的:
console.log(typeof new String("abc")) //object console.log(typeof "abc") //string new String("abc") === "abc" //false
包装对象类型很少被直接运用,但是他们的原型对象(prototype objects)定义了基础类型的方法。例如,String.prototype 是包装对象类型 String 的原型对象,它所有的方法对于 strings 都是可以使用的。如包装的方法 String.prototype.indexOf. 基础类型 strings 也有相同的方法,两个方法并不是不同的方法拥有相同的名称,而就是相同的方法:
String.prototype.indexOf === "".indexOf //true
内部属性 (internal properties)
内部属性是影响着 javascript 怎样工作但是又不能被直接访问的属性。内部属性的名称是首字母大小并被两个双方括号所包裹着的。例如[[Extensible]]就是一个内部属性,它拥有一个boolean标志,这个标志决定了属性是否可以被添加到一个对象上。它的值只能通过 Object.isExtensible() 进行读取,Object.preventExtensions()将其设置为false。一旦将其值设置成false,将无法通过其他方法改为true。
术语:原型 (prototypes)VS 原型对象 (prototype objects)
在 Javascript 中,原型术语被不幸地进行了重载。
- 一方面,在对象之间存在着 prototype-of 关系。每一个对象都有一个隐藏的属性 (hidden properties) [[Prototype]],它要么指向对象的原型 (prototype),要么指向null。原型是一个延续的对象。如果一个对象的原型是一个可以被访问的对象,并且它不能被继续下去(它的对象的原型指向的是 null),那么就只会延续的上一个对象。多个对象可以拥有相同的原型。
- 另一方面,如果一个类型被一个构造函数Foo实现,那么那个构造函数拥有一个Foo.prototype属性作为类型的原型对象。
为了使两种得到明确的区分,开发者称第一种为“原型 (prototypes)”,第二种为“原型对象(prototype objects)”.下面的方法可以帮助我们处理原型:
- Object.getPrototypeOf(obj)返回的是 obj的原型:
Object.getPrototypeof({}) === Object.prototype //true
- Object.create(proto)是创造了一个空的对象,它的原型是 proto:
Object.create(Object.prototype) //{}
- proto.isPrototypeOf(obj)返回的是 true,如果 proto 是 Obj 的对象(或者一个原型的原型,等等)。
Object.prototype.isPrototypeOf({})
"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 //[Fucntion: 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 Mach] Object.prototype.toString.call({}); // [object Object]
因此,下面的函数就可以检测一个值的[[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 Foo(){} getClass(new Foo()) //Object (function (){ return getClass(arguments)}()); //Arguments
typeof
对于操作的值,会根据下表进行返回:
Operand | Resulet |
undefined | "undefined" |
null | "object" |
Boolean value | "boolean" |
Number value | "value" |
String value | "string" |
Function | "function" |
其他值 | "object" |
typeof操作符对 null 的返回值是 object,是一个无法被修复的 bug, 因为要兼容已有的代码。 需要注意的是尽管函数(function)是一个object,但是 typeof 进行了区分。另一方面,Array也被认为是objects.
这样就使得对对象的判断变得复杂了一些:
function isObject(x) { return x !== null && (typeof x === 'object' || typeof x === 'function'); }
instanceof
instanceof 是一个检查一个值是否是一个类型的实例:
value instanceof Type
这个操作符通过寻找 Type.prototype 来检验它是否在值得原型链中。这样来说,如果你要自己实现 instanceof,那么大体的代码会像下面一样:
function myInstanceof(value, Type) { return Type.prototype.isPrototypeOf(value); }
对于基础类型, instanceof 常常会返回false
"" instanceof String //false "" instanceof Object //false
Array.isArray()
Array.isArray()的存在是因为浏览器中的一个特有的问题:每一个 frame 都拥有它自己的全局环境。 比如, 给定一个 frame A 和一个 frame B(任何一个都可以是 document),在 frame A 中向 frame B中传递一个值。 在 frame B中的代码不能通过使用 instanceof Array 来判断一个值是否是 array,因为它的 B Array 和 Array 是不同的 ,例如:
<!DOCTYPE HTML> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> <script type="text/javascript" charset="utf-8"> //在 iframe 中调用 test() 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 type="text/javascript" charset="utf-8"> //填充iframe var iframeWin = frames[0]; iframeWin.document.write( '<script>window.parent.test([])</'+'script>'); </script> </body> <
因此,ES5引进了Array.isArray(),通过使用 [[Class]]来判断一个值是否是数组。尽管如此,上面描述的关于 frames 的问题再其他的类型,当使用 instanceof 时依然存在。
内置原型对象(Built-in prototype objects)
内置类型的原型对象非常的奇怪:它们的表现就像是这个类型的实例,但是当通过 instanceof 来检验的时候,会发现它们并不是实例。一些对原型对象的其他分类结果页并不如预期。
Object.prototype
Object.prototype 是一个空的对象:它打印出来就只是一个,并没有其他可例举的属性:
Object.prototype //{} Object.keys(Object.prototype) //[]
没有想到的是, Object.prototype是一个对象,但是它不是 Object 的一个实例。一方面, typeof 和 [[Class]]都将其认为是 object:
getClass(Object.prototype) //object typeof object.prototype //object
另一方面, instanceof并不认为它是 Objec t的实例:
Object.prototype instanceof Object //flase
为了让上面的结果正确,那么 Object.prototype 必须拥有它自己的原型链,这样就会造成从 Object.prototype 开始到 Object.prototype 结束的回环。原型链不再是线性的,这就不是你所想要的数据结构并且不能被遍历。因此,Object.prototype 并没有prototype。 它是为一个没有 prototype 的对象。
Object.getPrototypeOf(Object.prototype) //null
这种悖论对所有的内置原型对象都是正确的:它们被所有的机制认为是所对应类型的实例,除了 instanceof.
对于其他的对象,[[Class]], typeof 和 instanceof 都保持一致:
getClass({}) //Object typeof {} //object {} instanceof Object //true
Function.prptotype
Function.prototype 自身本来是一个函数,它接受任何函数但是都返回 undefined.
Function.prototype("a", "b", 1, 2) //undefined
出乎意料的是 Function.prototype 是一个 function,但是它并不是 Function 的实例:
typeof Function.protype //function getClass(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 typeof Function //function getClass(Function) //function Function instanceof Function //true