javascript类型检测
原文在我的博客上: http://www.poised-flw.com/javascript/2013/05/10/avoid-null-comparisons/
JS中的检测
在Javascript中, 测试一个变量是否为null
很普遍, 但是却有问题. 如:
var Controller = { process: function(items) { if (items !== null) { // Bad items.sort(); items.forEach(function(item) { // do something }); } } };
上面的例子中, 目的很明确. 那就是只有items是数组的时候才在process
方法中对它进行 排序和遍历. 但是当items是Number
(不为0)或者非空的string
的时候不为null
的条件 照样满足, 因此在执行sort()
方法的时候就会报错!
检测原始值
在javascript中有5中原始类型: string, number, boolean, null, undefined. 可以用 typeof
来检测一个变量的类型. 如:
var str ="poised-flw";
typeof str;// "string"
由于不同的对象具有不同的方法, 因此在调用这些方法前检测以下变量的类型能避免一些错 误的产生:
// detect a string if (typeof name === "string") { anotherName = name.substring(3); } // detect a number if (typeof count === "number") { updateCount(count); } // detect a boolean if (typeof found === "boolean" && found) { message("Found!"); } // detect undefined if (typeof MyApp === "undefined") { MyApp = { // code }; }
当用typeof
检测一个未声明的变量时, 都返回"undefined".
typeof nondeclared_var;// "undefined"
对于null
这个类型, 简单的比较一个变量是否为null
并不能给你关于这个变量的足够信 息. 当使用!==
和===
的时候能判断一个变量是否真的为null
.
// If you must test for null, this is the way to do it var element = document.getElementById("my-div"); if (element !== null) { element.className = "found"; }
由于:
typeofnull;// "object"
因此若想真正比较一个变量是否为null的时候应该使用!==
或者===
.
检测引用值
在javascript中, 引用是比较有名的. 如两个对象的赋值只是引用并非真正的赋值, 他两指 向同一片内存区域. 因此一个对象的修改将会导致另一个对象的改变.
var obj = { name: 'poised-flw' } var _obj = obj; _obj.name = 'javascript'; obj.name; // 'javascript'
因此在javascript中, 任何值的定义都是一个指向. 对于js一些内建的对象Object, Array, Date, Error等. 虽然名字不一样但是执行typeof后的结果都是一样的.
console.log(typeof{});// "object" console.log(typeof[]);// "object" console.log(typeofnewDate());// "object" console.log(typeofnewRegExp());// "object" console.log(typeofnull);// "object"
当然, 这个时候我们就需要换一种方法了.
instanceof
是检测引用类型的有效途径. 例如:
// detect a Date if (value instanceof Date) { console.log(value.getFullYear()); } // detect a RegExp if (value instanceof RegExp) { if (value.test(anotherValue)) { console.log("Matches"); } } // detect an Error if (value instanceof Error) { throw value; }
instanceof
不但用来检测构造这个实例的constructor
, 而且还能用与检测原型链. 原型链包括通过继承得到的属性. 对于继承, 每个对象都继承自Object
. 所以对于每个对 象, 执行value instanceof Object
都会返回true
.
var now = new Date(); console.log( now instanceof object ); // true console.log( now instanceof Date ); // true
instanceof
的另一使用场景, 对于自定义的一些构造函数和实例之间:
function Person(name) { this.name = name; } var me = new Person("Nicholas"); console.log(me instanceof Object); // true console.log(me instanceof Person); // true
因为me是Person的一个实例, 并且上面提到, 任何对象都是Object的实例. 故两个都返回 true.
检测函数
函数是javascript中典型的类型, 有一个Function
构造函数, 每个函数都是它的实例.
function test(){} // Bad console.log( test instanceofFunction);// true
然而, 上面的这个方法对于跨越frames
间的检测是没用的, 因为每个frame
都有它自己 的Function
构造函数, 但是我们能通过typeof来判断.
console.log(typeof test ==="function");// true
使用typeof唯一的限制是, 在IE8和更早的版本中, 属于DOM的函数都会返回"object"而不是 "function".
// Internet Explorer 8 and earlier console.log(typeof document.getElementById);// "object" console.log(typeof document.createElement);// "object" console.log(typeof document.getElementsByTagName);// "object"
因此, 在处理浏览器兼容性的时候. 对于一些非共有的方法在使用的时候要确定当前浏览器 是否支持它.
// detect DOM method if ("querySelectorAll" in document) { images = document.querySelectorAll("img"); }
在调用querySelectorAll
前, 检测当前的浏览器中是否支持querySelectorAll
方法.
检测数组
由于在frames中, 和函数一样, 每个frame都有自己的Array对象, 因此在一个frame中声明 的数组在另一个frame中并不能通过instanceof检测出来. 因此有了如下的几种方法:
- 大牛Douglas Crockford提出的:
// Duck typing arrays function isArray( value ) { return typeof value.sort === "function"; }
因为只有数组才具有sort
方法, 因此不管变量的声明地方都能被正确的检测到, 但是这个 方法使用的限制是检测的非数组对象中没有sort
这个方法.
- Kangax提出的, 目前很多流行的库采用这种方法:
function isArray( value ) { return Object.prototype.toString.call( value ) === "[object Array]"; }
对于一个给定的值, 当调用它的原生方法toString()的时候, 在所有浏览器中都会返回一个 标准的字符串. 如对于JSON对象会返回"[object JSON]".
基于以上的原因, 在ECMAScript5中引入了Array.isArray()方法, 思想都是基于上面的.
function isArray(value) { if (typeof Array.isArray === "function") { return Array.isArray(value); } else { return Object.prototype.toString.call(value) === "[object Array]"; } }
检测属性
当检查一个对象是否含有某个属性的时候通常有以下几种方法:
// Bad: Checking falsyness if (object[propertyName]) { // do something } // Bad: Compare against null if (object[propertyName] != null) { // do something } // Bad: Compare against undefined if (object[propertyName] != undefined) { // do something }
上面的方法都存在缺点, 当这个属性本身存在, 但是值为0,"", false, null, undefined的时候.
所有要准确的判断一个对象是否有某个属性, 应该使用in
操作符. 它只是简单检查这个对 象是否有这个属性, 而不去读这个属性的值, 从而避免了如上的不足.
var object = { count: 0, related: null }; // Good if ("count" in object) { // this executes } // Bad: Checking falsy values if (object["count"]) { // this doesn't execute } // Good if ("related" in object) { // this executes } // Bad: Checking against null if (object["related"] != null) { // doesn't execute }
如果你只想检测对象直接继承的属性而不管原型链上的属性, 使用hasOwnProperty
方法.
// Good for all non-DOM objects if (object.hasOwnProperty("related")) { //this executes } // Good when you are not sure if ("hasOwnProperty" in object && object.hasOwnProperty("related")) { //this executes }
由于IE8的存在... 在使用之前应该先检查一下.