详解JavaScript中四种检测对象的类的方法

作者:admin     字体:[增加 减小]    类型:原创
JavaScript定义了少量的数据类型:null、undefined、布尔值、数字、字符串、函数和对象。typeof运算符可以得出值的类型。然而,我们往往更希望将类作为类型来对待,这样就可以根据对象所属的类来区分它们。

在JavaScript中检测任意对象的类,大体有下面四种方法:

使用instanceof运算符

左操作数是待检测其类的对象,右操作数是定义类的构造函数。如果o继承自c.prototype,则表达式

o instanceof c; //值为true

如果o所继承的对象继承自另一个对象,后一个对象继承自c.prototype,这个表达式的结算结果也是true。

function f(){}; //函数f继承自Function.prototype,而Function.prototype又继承自Object.prototype
console.log(f instanceof Object);   //结果仍然是true

构造函数是类的公共标识,原型是唯一标识。尽管intanceof运算符的右操作数是构造函数,但计算过程实际上是检测了对象的继承关系,而不是检测创建对象的构造函数。

使用isPrototypeOf()方法

function f(){}; //空函数的原型是Function.protoypte与Object.prototype
console.log(Object.prototype.isPrototypeOf(f)); //结果为true,正好跟instance相逆,但结果一样

在使用isPrototypeOf()方法时,可以不使用构造函数作为中介,达到检测对象的原型链上是否存在某个特定的原型对象。

instanceof运算符与isPrototypeOf()方法的缺点是:我们无法通过对象来获得类名,只能检测对象是否属于指定的类名。另外,在客户端中还有一个比较严重的不足,就是在多窗口和多框架子页面的Web应用中兼容性不佳。每个窗口和框架子页面都具有单独的执行上下文,每个上下文都包含独有的全局变量和一组构造函数。在两个不同框架页面中创建两个数组继承自两个相同但相互独立的原型对象,其中一个框架页面中的数组不是另一个框架页面的Array()构造函数的实例。

constructor属性

构造函数是类的公共标识,所以最直接的方法就是使用constructor属性,比如:

function typeAndValue(x){
    if (x==null) return ""; //Null 和 undefined 没有构造函数
    switch(x.constructor){
        case Number: return "Number: " + x; //处理原始类型
        case String: return "String: " + x;
        case Date: return "Date: " + x;     //处理内置类型
        case RegExp: return "RegExp: " + x;
        case Complex: return "Complex: " + x;   //处理自定义类型
    }
}

但是在JavaScript中并非所有的对象都包含constructor属性。而且使用constructor属性检测对象的不足之处跟instanceof一样,在多个执行上下文的场景中它是无法正常工作的。

使用构造函数的名称

似乎我们可以使用构造函数的名字而不是构造函数本身来作为类的标识符,这样就可以避免在多个执行上下文中存在构造函数的多个副本时检测结果出错的情况。一个窗口里的Array构造函数与另一个窗口的Array构造函数虽然不相等,但是它们的名字是一样一样的。

在一些JavaScript的实现中为函数对象提供了一个非标准的属性name,用来表示函数的名称。比如作者在用chrome浏览时,下面的代码会正常工作。

function f(){};
console.log(f.name);    //f

对于那些没有name属性的JavaScript实现来说,可以将函数转换为字符串,然后从中提取函数名

//返回函数的名字,如果它有(非标签的)name属性,则直接使用name属性
//否则,将函数转换为字符串然后从中提取名字
//如果是没有名字的函数,则返回一个空字符串
Function.prototype.getName = function() {
    return this.name || this.toString().match(/function\s*([^()]*)\(/)[1];
}

function f(){};
//因为我这边浏览器有name的属性,所有我使用toString方法获取
console.log(this.toString().match(/function\s*([^()]*)\(/)[1]); //f

下面给出一个函数type(),它以字符串的形式返回对象的类型,它用typeof运算符来处理原始值和函数。对于对象来说,它要么返回class属性的值要么返回构造函数的名字。并且用到了两个函数classof()Function.getName()

function classof(o){
    return Object.prototype.toString.call(o).slice(8,-1);
}

Function.prototype.getName = function() {
    return this.name || this.toString().match(/function\s*([^()]*)\(/)[1];
}

//判断值的类型的type函数
//以字符串形式返回o的类型:
/*
    如果o是null,返回"null",如果o是NaN,返回"nan"
    如果typeof所返回的值不是"object",则返回这个值
    (注意,有一些JavaScript的实现将正则表达式识别为函数)
    如果o的类不是"Object"且不是"Function",则返回这个值
    如果o包含构造函数并且这个构造函数具有名称,则返回这个名称
    否则,一律返回"Object"
*/
function type(o){
    var t,c,n;  //type,class,name

    //处理null值的特殊情形
    if ( o===null ) return "null";

    //另外一种特殊情况是,NaN和它自身不相等
    if ( o!==o ) return "nan";

    //如果typeof的值不是"object",则使用这个值
    //这可以识别出原始值的类型和函数
    if ( (t = typeof o) !== "object" ) return t;

    //返回对象的类名,除非值为"Object"
    //这种方式可以识别出大多数的内置对象
    if ( (c = classof(o)) !== "Object" && c !== "Function") return c;

    //如果对象构造函数名字存在的话,则返回它
    if ( o.constructor && typeof o.constructor === "function" &&
        (n = o.constructor.getName())) return n;

    //其他的类型都无法差别,一律返回"Object"
    return "Object";
}

function F(){};
var f = new F;
console.log(type(null));    //null
console.log(type(NaN));     //nan
console.log(type(123.456)); //number
console.log(type("string"));//string
console.log(type(true));    //boolean
console.log(type(function(){}));    //function
console.log(type(f));   //F,构造函数名称
console.log(type(new Object()));    //Object
console.log(type(document.getElementsByTagName("body")[0]));    //HTMLBodyElement

这种方法并不是完美的,因为并不是所有的对象都具有constructor属性,而且并非所有的函数都有名字。