JavaScript进阶内容笔记1:各种对象类型判断

该文章主要用来介绍JavaScript中常用的一些对象检测判断方法,整理资源来自书本和网络,如有错误或说明不详之处,望评论提出,本菜定提名感谢……(本文章知识比较基础,大牛请提些意见再绕道,三克油^_^)

1.检测原始值(typeof)
    结论:
        JavaScript五种原始类型:字符串、数字、布尔值、undefined、null。其中前四种的类型检测请使用 typeof ,最后一种 null 的类型检测请使用恒等(===)或者非恒等(!==)运算符。
    原因:
        typeof 运算符返回一个表示值的类型的字符串,
        如:
        typeof(字符串)         => "string"
        typeof(数字)         => "number"
        typeof(布尔值)         => "boolean"
        typeof(undefined)     => "undefined"

        typeof 的基本语法是:
            typeof 变量 //推荐语法
            也可以使用:
            typeof(变量) //虽然这是合法的,但这让 typeof 看起来更像是函数,而非运算符。

        typeof 检测四种原始类型做法:

        //检测字符串
        if(typeof mytype === "string"){
            ……
        }
        // 检测数字
        if(typeof mytype === "number"){
            ……
        }
        // 检测布尔值
        if(typeof mytype === "boolean" && mytype){
            ……
        }
        // 检测 undefined
        if(typeof mytype ==="undefined"){
            ……
        }

        最后一种是对 null 的检测,因为简单地把一个对象和 null 进行比较,不足以判断其值的类型是否合法,比如:
        if(items !== null){//不好的做法
            items.sort();//如果items不是数组,该执行将报错
            items.foreach(function(item){
                //执行一些逻辑
            })
        }
        其实,items 是一个数组,但是仅仅通过和 null 比较,不足以判断其是合法的数组类型,因为tiems 也可以是1、字符串、对象等,都和 null 不相等。
        因此,只有在你明确预见某个值真的是 null ,则可以直接和 null 比较,比如:
        var element = document.getElementById("my-div");
        if(element !== null){//因为 getElementById 要么返回节点,要么返回 null
            ……
        }
        特别提示:typeof(null) 返回 "object",这被认为是标准规范的严重"bug",所以一定要杜绝使用 typeof 检测 null类型。
2.检测引用值(instanceof)
    结论:
        JavaScript引用类型有:Object、Array、Date、Error、RegExp 等等。检测引用类型的最好方法是使用 instanceof 运算符(Array 无法跨帧,需另讲)。
    原因:
        不能使用 typeof 检测引用类型,因为所有对象都会返回"object":
        typeof {} => "object"
        typeof [] => "object"
        typeof new Date() => "object"
        typeof new RegExp() => "object"
        所以不能用 typeof 来检测。
        instanceof 意思是"实例",也就是说只要一个变量是某个对象(引用类型)的实例,则返回 true,如:
        // 检测日期
        if(value instanceof Date){
            ……
        }
        // 检测正则表达式
        if(value instanceof RegExp){
            ……
        }
        // 检测 Error
        if(value instanceof Error){
            ……
        }

        另外,instanceof 还可以检测原型链,比如,所有对象都继承自 Object,所以 value instanceof Object 都会返回 true。
        如:
        var now = new Date();
        now instanceof Object => true
        now instanceof Date => true
3.检测函数(typeof)
    结论:
        检测函数使用 typeof;而IE8及其更早浏览器中检测DOM的方法时(document.getElementById/document.createElement/document.getElementsByTageName……)时使用 in 运算符。
    原因:
        function 也是引用类型,虽然
        function myFun(){}
        console.log(myFun instanceof Function)// true
        返回true,但是每个函数中都有自己的 Function 构造方法,如果跨帧(frame)使用,在另一个帧中就不会识别另一个帧中的实例对象。
        建议使用 typeof,返回"function".
        function myFun(){}
        console.log(typeof myFun === "function")// true(推荐使用)

        另外,在IE8和更早版本中,使用typeof 检测document.getElementById/document.createElement/document.getElementsByTageName……时,返回 "object"
        推荐使用:
        if("querySelectorAll" in document){
            images=document.querySelectorAll('img');
        }
        虽然不是最理想的,如果想在IE8及其更早版本中使用,这无疑是最安全的。其他情况则使用 typeof 检测是否为函数。
4.检测数组(Array.isArray + Object.prototype.toString.call(value) === '[object Array]' )
    结论:
        function isArray(value){
            if(typeof Array.isArray === 'function'){
                return Array.isArray(value);
            }else{
                return Object.prototype.toString.call(value) === '[object Array]';
            }
        }
    原因:
        最开始检测数组采用“鸭式辨型”:
        function isArray(value){
            return typeof value.sort === 'function';
        }
        因为目前只有数组才有排序 sort() 方法,但是如果传递的 value 包含自定义的 sort(),该方法就会失效。

        目前比较流行的方法是:
        function isArray(value){
            return Object.prototype.toString.call(value) === '[object Array]';
        }
        该方法是基于 toString()方法在所有浏览器中都会返回标准的字符串结果。对于数组则返回 '[object Array]'。
        由于ECMAScript5正式引用了 Array.isArray() 方法,IE9+、FireFox4+、Safari 5+、Opera 10.5+、Chrome都支持了该方法,所以
        推荐使用:
        function isArray(value){
            if(typeof Array.isArray === 'function'){
                return Array.isArray(value);
            }else{
                return Object.prototype.toString.call(value) === '[object Array]';
            }
        }
5.检测属性(in 或 hasOwnProperty)
    结论:
        判断属性是否存在最好的方法是使用 in 运算符。只有需要判断实例属性时才会用到 hasOwnProperty。
    原因:
        有时候检测属性会这样使用:
        //不好的写法:检测假值
        if(object[propertyName]){
            ……
        }
        //不好的写法:和 null 比较
        if(object[propertyName] != null){
            ……
        }
        //不好的写法:和 undefined 比较
        if(object[propertyName] != undefined){
            ……
        }
        以上的代码,是通过获取该属性值来判断是否包含该属性,但是如果该属性的值本身就是 false、0、""、null、undefined呢?是不是就不管用了!
        所以,最好的办法是使用 in 运算符。in 仅仅只会判断属性是否存在,而不会获取属性值。如果只需要判断实例属性可以用 hasOwnProperty。

        in vs hasOwnProperty:
        in:判断对象(包括原型链)是否具备指定的属性。(有中说法叫:判断实例对象属性、或者继承自对象的原型的属性)
        hasOwnProperty:判断对象自身(不包括原型链)是否具有指定名称的属性。 (有种说法叫:仅用于判断实例属性)

        上面说明很晦涩,继续看就明白了:
        先来对比几个关键词:
        实例属性 vs 原型的属性 vs 静态属性:
        先看代码:
        function Man(myname, age) { 
            //定义实例属性 
            this.myname = myname; 
            this.age = age; 
        }
        //定义静态属性
        Man.sex = '男'; 
        //定义原型属性 
        Man.prototype.phone = '123456'; 

        document.writeln(Man.sex);//
        document.writeln(Man.myname);//undefined
        document.writeln(Man.age);//undefined
        document.writeln(Man.phone);//undefined
        document.writeln(Man.prototype.phone);//123456

        var man = new Man("Tom", 24); 
        document.writeln(man.myname);//Tom
        document.writeln(Man.sex);//
        document.writeln(man.age);//24
        document.writeln(man.phone);//123456
        代码总结:
        实例属性:构造函数中定义的属性,类似C#/java中 只能通过 new 实例化一个对象后才能访问到的属性。
        原型属性:属于原型链中的属性,通过prototype扩展的属性。
        静态属性:在构造函数外,直接通过【对象名.新属性】追加的属性叫静态属性,类似C#/java中的 static 属性。
     另外,
     (1)构造函数中定义的属性和方法要比原型中定义的属性和方法的优先级高,如果定义了同名称的属性和方法,构造函数中的将会覆盖原型中的。
     (2)把方法写在原型中比写在构造函数中消耗的内存更小,因为在内存中一个类的原型只有一个,写在原型中的行为可以被所有实例共享,实例化的时候并不会在实例的内存中再复制一份。而写在类中的方法,实例化的时候会在每个实例中再复制一份,所以消耗的内存更高。所以没有特殊原因,我们一般把属性写到类中,而行为写到原型中。
归正传,
        in 和 hasOwnProperty 如何使用呢,看代码:
        function Site(){
            //判断是否有这些实例属性方法:s.hasOwnProperty("name/url...") 或 "name/url..." in s
            this.name = "CodePlayer";
            this.url = "http://www.365mini.com/";

            this.sayHello = function(){
                document.writeln("欢迎来到" + this.name);
            };
        }

        Site.ower='Alec';//判断是否有静态属性方法:Site.hasOwnProperty('ower') 或 "ower" in Site

        var obj = {
            //判断是否有这些原型属性方法:"engine/sayHi.." in s (这些对象是s原型链中的对象,如果该obj又有更高级原型域,同样可以使用 '属性名' in s 判断)
            engine: "PHP",
            sayHi: function(){
                document.writeln("欢迎访问" + this.url);
            }
        };
        Site.prototype = obj;

        var s =  new Site();
        document.writeln( s.hasOwnProperty("name") ); // true
        document.writeln( s.hasOwnProperty("sayHello") ); // true
        // 以下属性继承自原型链,因此为false
        document.writeln( s.hasOwnProperty("engine") ); // false
        document.writeln( s.hasOwnProperty("sayHi") ); // false
        document.writeln( s.hasOwnProperty("toString") ); // false
        //静态属性只能通过原型对象判断,无法用实例对象判断
        document.writeln( s.hasOwnProperty("ower") ); // false
        document.writeln( Site.hasOwnProperty('ower') );//true

        // 想要查看对象(包括原型链)是否具备指定的属性,可以使用in操作符
        document.writeln( "url" in s ); // true
        document.writeln( "sayHello" in s ); // true
        document.writeln( "engine" in s ); // true
        document.writeln( "sayHi" in s ); // true
        document.writeln( "toString" in s ); // true

        document.writeln( "ower" in s ); // false
        document.writeln( "ower" in Site ); // true 静态属性需要使用原型对象名访问
    附图一张,进一步说明:
    JavaScript进阶内容笔记1:各种对象类型判断
看到这里,应该对 in 和 hasOwnProperty 的使用场景有所了解了吧? 最后,需要特别说明的一点是: IE8及其更早的IE版本中,DOM对象并非继承自Object,因此,无法使用hasOwnProperty()方法,换句话说,如果你在调用DOM对象的hasOwnProperty()方法之前需要先检测器是否存在(假如你已经知道对象不是DOM,就可以省略此步骤) //对于所有非DOM对象来说,这是好的写法 if(object.hasOwnProperty('related')){ …… } //如果你不确定是否为DOM对象,则这样写是好的 if('hasOwnProperty' in object && object.hasOwnProperty('related')){ …… } 因为IE浏览器存在此问题,在判断实例对象是否存在时,我更倾向于使用 in运算符,只有在需要判断实例属性时才会用到hasOwnProperty()。

看到这里,说明你是个善良的人,非常感谢!如有错误之处,请留言讨论,共同进步……tks!

参考资料:

《编写可维护的JavaScript》

http://www.365mini.com/page/javascript-hasownproperty.htm

http://www.jb51.net/article/20236.htm

http://blog.sina.com.cn/s/blog_54d1f65b0101dfv3.html