`
jimode2013
  • 浏览: 39361 次
社区版块
存档分类
最新评论

Checking Types in Javascript

 
阅读更多

Have you ever wondered: what is the correct way to check if a Javascript variable is an Array?

 

Do a Google search and you will get a great variety of answers. And, unfortunately, there's is no correct answer. This is one of the sad things about Javascript, not only are there many varying implementations of the language, there are also many varying opinions about how things should be done.

Enough philosophizing and feeling sorry about the state of things. With this article, I will trying to give a comprehensive overview of the different techniques of checking-types in Javascript, the pros and cons of each and why they exist.

typeof operator

In the beginning, there was typeof. This handy operator gives you the "type" of a Javascript value:

typeof 3 // "number"
typeof "abc" // "string"
typeof {} // "object"
typeof true // "boolean"
typeof undefined // "undefined"
typeof function(){} // "function"

 All is fine 'n dandy until

typeof [] // "object"

Huh? An array's type is object? I guess it is, if you want to get technical about it, but still, what the...

typeof null // "object"

Okay, now that's just wrong!

 

Also, typeof will return "object" for Dates, RegExp, user defined objects, DOM elements, and pretty much anything else. So, typeof is pretty good at distinguishing between different kind of primitive values, and distinguish between them and objects, but is completely useless when it comes to distinguishing between different kinds of objects - which includes arrays and nulls(WTF?!).

instanceof operator

The instanceof operator tells you whether a object is an instance of a certain type. The so-called "type" is a constructor. For example

function Animal(){}
var a = new Animal()
a instanceof Animal // true

Alternatively, you could make this check using the constructor property of an object

a.constructor === Animal // true

However, the constructor check has two problems. First, it does not walk up the prototype chain

function Cat(){}
Cat.prototype = new Animal
Cat.prototype.constructor = Cat
var felix = new Cat
felix instanceof Cat // true
felix instanceof Animal // true
felix.constructor === Cat // true
felix.constructor === Animal // false

The second problem is that it will bomb out if the object in question is null or undefined.

felix = null
felix instanceof Animal // true
felix.constructor === Animal // throws TypeError
instanceof  works for all native types!
[1, 2, 3] instanceof Array // true
/abc/ instanceof RegExp // true
({}) instanceof Object // true
(function(){}) instanceof Function // true

or does it?

3 instanceof Number // false
true instanceof Boolean // false
'abc' instanceof String // false

Okay, what is going on here? It turns out that instanceof does not work with primitive values. The primitive types in Javascript are: stringsnumbersbooleansnull, and undefined - (Oh good! You can count them all on one hand!) Well actually, I should have said it does not work with primitives with the exception of null and undefined, because they are not an instance of anything, and so instanceof correctly returns false when either is used on the left hand side.

null instanceof Boolean // false
undefined instanceof Array // false

To top that off though, checking for the constructor property will work for the primitive types number, string and boolean.

(3).constructor === Number // true
true.constructor === Boolean // true
'abc'.constructor === String // true

This works because whenever you reference a property on a primitive value, Javascript will automatically wrap the value with an object wrapper, like so

var wrapper = new Number(3)

except you don't see this - it happens behind the scenes. The wrapper then will be an instance of - in this case Number - or a Boolean or a String depending on the type of the primitive value, at which point it can walk up the prototype-chain and get at the properties of the Number prototype, etc. So, for example, creating a wrapper manually will make the instanceof operator work

new Number(3) instanceof Number // true
new Boolean(true) instanceof Boolean // true
new String('abc') instanceof String // true

But doing that would be pointless because it requires you to already know the type of the value of which you are asking whether or not it is of the type that you already know it is.

Cross-window Issues of instanceof

It turns out that instanceof has another problem. It breaks down when you try to test an object coming from another window. You know? The ones that are created for each <iframe>, <frame> or popup window that you create. 

 

<html>
<body>
</body>
<script>
var iframe = document.createElement('iframe')
document.body.appendChild(iframe)
var iWindow = iframe.contentWindow // get a reference to the window object of the iframe
iWindow.document.write("<script>var arr = [1,2, 3]<\/script>") // create an array var in 

iframe's window
console.log(iWindow.arr) // [1, 2, 3]
console.log(iWindow.arr instanceof Array) // false
</script>
</html>
 

 

Above, we created a variable arr inside the context of the iframe and set it to the array [1, 2, 3]. However, we get false when we ask whether it is a instance of Array. What is happening?!! Behold.

Array === iWindow.Array // false

The Array in the iframe is not the same Array as our Array! This is true for all built-in objects: there are two versions of all of them! Basically, we have parallel universes! OMG!

What this means is that an array created within the iframe is only an instance of the Array constructor within the iframe

iWindow.arr instanceof iWindow.Array // true

The same phenomenon happens with windows created using the open() function. It is because of this cross-window problem that the use of instanceof is somewhat limited and discouraged within the Javascript community.

Duck-Typing

Because neither typeof or instanceof are satisfactory, many resort to duck-typing. This means checking the behavior: if it looks like a duck and quacks like a duck, then it is a duck as far as I am concerned. Pretty sure I misquoted that...oh well.

 

So, using duck-typing, an isArray check might look like

 

// source: http://forums.devshed.com/javascript-development-115/javascript-test-whether-a-variable-is-array-or-not-33051.html
function isArray(obj){
    return (typeof(obj.length)=="undefined") ?
        false:true;
}

 

 

 

or 

 

// source: http://www.hunlock.com/blogs/Ten_Javascript_Tools_Everyone_Should_Have
function isArray(testObject) {
    return testObject &&
	!(testObject.propertyIsEnumerable('length')) &&
	typeof testObject === 'object' &&
	typeof testObject.length === 'number';
}
 

 

 

 

in jQuery, the function to check whether an object is a window is

 

isWindow: function( obj ) {
    return obj && typeof obj === "object" && "setInterval" in obj;
}
 

 

 

 

You could easily trick this function into returning true

 

$.isWindow({setInterval: 'bah!'}) // true
 

 

 

Clearly, the problem with this approach is that

  1. it is inexact and can have false positives
  2. the set of properties of the object to test is arbitrary, and so it's unlikely for different people to agree on one way of doing it
For the record, I don't like this approach.

Object.prototype.toString method

It turns out that, you can get type information about an object by using the Object.prototype.toString method.

Object.prototype.toString.call(3) // "[object Number]"
Object.prototype.toString.call([1, 2, 3]) // "[object Array]"
Object.prototype.toString.call({}) // "[object Object]"

This native method is rarely encountered normally because it's usually shadowed by another toString method lower down in the prototype chain(Array.prototype.toString, Number.prototype.toString, etc.). This method reliably differentiates between native types, however, will return "[object Object]" for all user-defined types

Object.prototype.toString.call(new Animal) // "[object Object]"

It does, however, work across different window contexts

Object.prototype.toString.call(iWindow.arr) === "[object Array]" // true

with one exception: different windows(as in popup window) in IE.

var pWindow = open("")
pWindow.document.write('<script>var arr = [1, 2, 3]</script>')
Object.prototype.toString.call(pWindow.arr) // you get "[object Object]" in IE; "[object Array]" else where.

This is strange because for iframes it works just fine, bummer! This method has become somewhat of a preferred way to differentiate native types despite the IE bug.Ehh, nobody uses popup windows anymore anyway.

Function.prototype.toString method

Yet another way to test for type information is by using the Function.prototype.toString method.

Function.prototype.toString.call((3).constructor)
// "function Number() {
//    [native code]
// }"

The method gives you the complete source of a function. In the case of native functions, it just says "[native code]" in the body. One could easily parse out the name of the function to figure out type of the object using this helper function

function type(obj){
	var text = Function.prototype.toString.call(obj.constructor)
	return text.match(/function (.*)\(/)[1]
}
type("abc") // "String"

This one will work for user-defined types too

type(new Animal) // "Animal"

this code has a problem wrt popup windows in IE just like instanceof. It's because when Function.prototype.toString is called with a constructor from another parallel universe, it can only discern the constructor as an object("[object Object]"), and thus rejects the argument and throws a "Function expected" error. This problem can actually be worked around by referencing the toString method indirectly

function type(obj){
	var text = obj.constructor.toString()
	return text.match(/function (.*)\(/)[1]
}

Now, it works for popup windows in IE too! This fix makes it susceptible to shadowing

Array.toString = function(){ return "function NotArray(){ }" }
type([1,2,3]) // "NotArray"

but still, I'd say this is pretty cool.

Now, let's return to user-defined types for a minute. With this approach, there's no way to distinguish between two different user-defined types with the same name

var f = function Animal(){ "something" }

var g = function Animal(){ "something entirely different" }
type(new f) // "Animal"
type(new g) // "Animal"

For this reason, this method is inferior to instanceof for user-defined types. Another seemingly obvious problem with this approach is performance, but I'd have to benchmark it(jsperf!) to make a real claim.

DOM Elements and Host Objects

So far, I have not mentioned type checking for DOM elements and host objects. That's because it's hard. With the exception of duck-typing, none of the methods mentioned above will work for all browsers. If you drop IE7 and below, however, you can actually get some of the things to work. The output below were created using Tutti

> var div = document.createElement('div')
> typeof div
Safari 5.0 => object
Firefox 3.6 => object
IE 7.0 => object
IE 8.0 => object
Opera 11.01 => object
> div instanceof Element
Safari 5.0 => true
Firefox 3.6 => true
IE 7.0 => Error: 'Element' is undefined
IE 8.0 => true
Opera 11.01 => true
> div instanceof HTMLDivElement
Safari 5.0 => true
Firefox 3.6 => true
IE 8.0 => true
IE 7.0 => Error: 'HTMLDivElement' is undefined
Opera 11.01 => true

First up, typeof is useless, that was expected. Next, everyone but IE 7 recognizes that a div is an instance of Element as well as an HTMLDivElementIn IE7, those constructors aren't even present. Next,

> Object.prototype.toString.call(div)
Safari 5.0 => [object HTMLDivElement]
Firefox 3.6 => [object HTMLDivElement]
IE 7.0 => [object Object]
IE 8.0 => [object Object]
Opera 11.01 => [object HTMLDivElement]

The result of Object.prototype.toString in IE - even IE 8 - is particularly unhelpful. What about

> div.constructor.toString()
Safari 5.0 => [object HTMLDivElementConstructor]
Firefox 3.6 => [object HTMLDivElement]
IE 7.0 => Error: 'div.constructor' is null or not an object
IE 8.0 => [object HTMLDivElement]
Opera 11.01 => function HTMLDivElement() { [native code] }

Function.prototype.toString: it gives us something useful for IE8, but every browser has a slightly different output.

Fun! Try another one? Allllllllllllllllrrrighty then!

> typeof window
Safari 5.0 => object
Firefox 3.6 => object
IE 8.0 => object
IE 7.0 => object
Opera 11.01 => object
> window instanceof Window
Safari 5.0 => ReferenceError: Can't find variable: Window
Firefox 3.6 => true
IE 8.0 => true
IE 7.0 => Error: 'Window' is undefined
Opera 11.01 => ReferenceError: Undefined variable: Window
> Object.prototype.toString.call(window)
Safari 5.0 => [object DOMWindow]
Firefox 3.6 => [object Object]
IE 8.0 => [object Object]
IE 7.0 => [object Object]
Opera 11.01 => [object Window]
> window.constructor
Safari 5.0 => function Object() {
    [native code]
}
Firefox 3.6 => function Object() {
    [native code]
}
IE 8.0 => [object Window]
IE 7.0 => undefined
Opera 11.01 => function Object() { [native code] }

With window it is just all over the place, none of these methods worked for all browsers. You can try testing out some other host objects if you want, but in general it doesn't look doable. However, in my testing, the XMLHttpRequest object and DOM and Text elements look doable using instanceof, if you can drop support for IE7 and below.

What We've Learned

Checking types in Javascript is a big mess. Although it's really not that hard to check for any one particular type, it is definitely not consistent across types, and you probably have had to make a lot of choices along the way. So it helps to know about all the different options. In an upcoming post, I will try to make all of this easier with a small piece of code. Stay tuned.

Sources

分享到:
评论

相关推荐

    JavaScript in 10 Minutes

    #### Types in JavaScript JavaScript has nine distinct data types: 1. **Null** (`null`): Represents a null value. It throws an error if you try to access any property on `null`. It is never boxed. 2....

    JavaScript Applications with Node.js, React, React Native and MongoDB

    JavaScript Applications with Node.js, React, React Native and MongoDB: Design, code, test, deploy and manage in Amazon AWS By 作者: Eric Bush ISBN-10 书号: 0997196661 ISBN-13 书号: 9780997196665 出版...

    程序语言设计原理习题解答

    Interview: Larry Wall—Part 2: Scripting Languages in General and Perl in Particular 388 History Note 396 History Note 397 History Note 401 9.6 Parameters That Are Subprogram Names 408 History...

    智慧健康体检平台-后台管理系统源码+项目说明.zip

    In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types....

    django-master-class

    While Django provides many built-in field types, there may be cases where you need to store more complex data structures. Custom fields allow for greater flexibility in handling non-primitive data ...

    java7帮助文档

    Catching Multiple Exception Types and Rethrowing Exceptions with Improved Type Checking Java Virtual Machine Java Virtual Machine Support for Non-Java Languages: Java SE 7 introduces a new JVM ...

    python3.6.5参考手册 chm

    The json module: JavaScript Object Notation The plistlib module: A Property-List Parser ctypes Enhancements Improved SSL Support Deprecations and Removals Build and C API Changes Port-Specific ...

    You Don't Know JS(高清带目录中文版)4-6

    - **类型检测(Type Checking)**:`typeof`、`instanceof`、`Object.prototype.toString.call`等方法用于检测类型。 - **引用类型(Reference Types)**:对象是引用类型,通过引用传递,而非值传递。 - **null ...

    BobBuilder_app

    Splitting a page when full is simple and does not require a tree traversal for node overflow checking as in a b+tree. Main page list updates are infrequent and hence the locking of the main page list...

    04-数据类型

    5. 类型检测(Type Checking) - `typeof` 操作符:返回一个字符串,表示变量的数据类型,但注意它不能准确识别null和对象。 - `instanceof` 运算符:用于判断对象是否为某个构造函数的实例。 - `Object....

    action-required-review:[只读] GitHub Action,它将检查是否满足PR的所有审查要求。 该存储库是一个镜像,用于问题跟踪和开发,请访问:https:github.comautomatticjetpack

    例子name : Required review checkon : pull_request_review : pull_request : types : [ opened, reopened, synchronize ]jobs : check : name : Checking required reviews runs-on : ubuntu-latest # GitHub ...

    .htaccess

    This will, of course, mean that if someone types the directory name into their browser, a full listing of all the files in that directory will be shown. This could be a security risk for your site....

    Eclipse 常用设置

    此外,通过`Window -&gt; Preferences -&gt; General -&gt; Content Types`,可以统一设定不同类型的文件编码,比如将JavaScript文件设置为UTF-8。 在开发JSP页面时,需要确保JSP文件的默认编码也是UTF-8。这可以通过`Window...

    myeclipse 优化

    实时验证功能会检查XML、JSP、JSF、JavaScript等文件的语法错误,虽然有助于保持代码质量,但在大型项目中可能成为性能瓶颈。为了加速MyEclipse的响应速度,可以考虑禁用实时验证,只在需要时手动执行验证操作。具体...

    Eclipse IDE开发平台配置及参数深度优化说明

    - **位置**:`General -&gt; Editors -&gt; Text Editors -&gt; Spelling`,取消勾选`Spell checking`。 **4. 暂停验证器** - **位置**:`General -&gt; Validation`,勾选`Suspend all validators`。 **5. 定制视图** - ...

    mps打字稿

    3. **类型检查与转换(Type Checking & Transformation)**:MPS 可以支持对 TypeScript 代码的静态类型检查,并将模型代码转换成可执行的JavaScript代码。 4. **代码生成(Code Generation)**:MPS 可以生成符合 ...

    is-what:JS类型检查(受TypeScript支持)功能,如`isPlainObject()isArray()`等。简单而小型的集成

    是什么? :hear-no-evil_monkey: 非常简单的小型JS类型检查功能。 它完全支持TypeScript! npm i is-what或可通过以下网址获取"deno.land/x/is_what" : "deno.land/x/is_what"动机我之所以建立它,是因为现有的解决...

Global site tag (gtag.js) - Google Analytics