Quantcast
Channel: w3cplus
Viewing all articles
Browse latest Browse all 1557

《献给你,我深爱的ECMAScript》之Object篇

$
0
0

本文主要想说说ECMAScript中的Object相关的东西,主要内容会定位在5和6。

题外话:

其实我自己博客一直维护着一个分类叫《js-object》,里面收录了一些框架关于Object扩展的业务api和ECMAScript已有以及新增相关. 本文中部分内容也来自这个分类,有兴趣的可以看看。

我们先看一段代码:

        /* prototype 1.6.1
         * 这个版本很简单:
         * 没有hasOwnProperty的过滤,采用push
         * 但是prototype的代码命名很正统,语义化意识明显
         */
        function keys(object){
            var results = [];
            for(var property in object){
                results.push(property);
            }
            return results;
        }

        /* prototype 1.7
         * 辅助函数 -- 其实如果自己抽离的话,就不需要这样设计
         * 对参数进行了类型验证
         * 加入了hasOwnProperty过滤
         */
        function Type(o){
            switch(o){
                case null : return NULL_TYPE;
                case (void 0) : return UNDEFINED_TYPE;
            }

            var type = typeof o;
            switch(type){
                case 'boolean' : return BOOLEAN_TYPE;
                case 'number' : return NUMBER_TYPE;
                case 'string' : return STRING_TYPE;
            }
            return OBJECT_TYPE;
        }


        function keys(object){
            //加了一层类型判定
            if(Type(object) !== OBJECT_TYPE){
                throw new TypeError();
            } 
            var results = [];
            for(var property in object){
                //改进了hasOwnProperty
                if(object.hasOwnProperty(property)){
                    results.push(property);
                }
            }
            return results;
        }


        /* underscore 1.5,1
         * 加入了ES5的兼容分支,同时也有参数类型验证
         * 也辅助函数_.has的过滤
         */
        _.has = function(obj,key){
            Object.prototype.hasOwnProperty.call(obj,key);
        };

        var nativeKeys = Object.keys;

        _.keys = nativeKeys || function(obj){
            if(obj !== Object(obj)){
                throw new TypeError('Invalid object');
            }
            var keys = [];
            for(var key in obj){
                if(_.has(obj,key)){
                    keys.push(key);
                }
            }

            return keys;
        };


        /* kissy 1.3
         * 这个和MDN上差不多,加了一层hasEnumBug的判定
         */
        var hasEnumBug = !({toString:1}.propertyIsEnumerable('toString')),
            enumProperties = [
                "constructor",
                "hasOwnProperty",
                "isPrototypeof",
                "propertyIsEnumerable",
                "toString",
                "toLocaleString",
                "valueOf"
            ];


        keys:function(o){
            //一次定义变量
            var result = [],
                p,
                i;

            for(p in o){
                result.push(p);
            }

            if(hasEnumBug){
                for(i = enumProperties.length-1;i>=0;i--){
                    p = enumProperties[i];
                    if(o.hasOwnProperty(p)){
                        result.push(p);
                    }
                }
            }

            return result;
        };



        /* tangram 2.0.2.4
         * 没有采用push,而是用length++的方式装载
         */
        baidu.object.keys = function(source){
            //参数一次定义
            var result = [],
                resultLen = 0,
                k;

            for(k in source){
                if(source.hasOwnProperty(k)){
                    //没有使用push
                    result[resultLen++] = k;
                }
            }    
        };


        /* qwrap 1.1.5
         * 比较标准,过滤了hasOwnProperty
         */
        keys:function(obj){
            var ret = [];
            for(var key in obj){
                if(obj.hasOwnProperty(key)){
                    ret.push(key);
                }
            }
            return ret;
        };

代码有点多,但是我相信很多同学应该都能从里面看出点东西~

1. 首先解释一下这个方法是干嘛的?

就是从一个Object类型的变量里面取它所有的keys,然后返回一个包含所有keys的数组。

2. 关于各大框架的罗列?

这个是很有心思的,大致是我个人研习的框架的顺序,并不是一般人认为的”远离国产,首选外国货“。

在这里小春真心感谢所有以上源码的作者们~

3. 为什么还标注版本?

我承认我是一个标准的源码版本控,当然这个在我研习各大框架源码实习细节上有很大的帮助,让我看到了作者对一些api的优化过程,从而自我反思一些东西。

这点如果看过我博客的里面介绍《jquery数据存储》的同学,应该有一定的体会。

好吧,入正题

其实想从现有的一些框架源码设计的细节角度,去看看对ECMAScript Object的支持情况。

每次说到js,都会被问道一个很基本的问题,这个针对低版本浏览器不?

关注兼容性的同学可以猛戳: 这里

我们可以看到,其实大致: IE 9+ FF 4+ Chrome 5+ 是支持的,IE6-8目前是不支持的,但是从上面的源码展示里面我们看到underscore是双分支兼容的。

我个人是非常坚持在类库设计的时候来更新这些原生方法的,原因很简单: 浏览器本身支持的方法“基本上”是最快的,当然以前的数组操作还是有一些差异的,不过高级浏览器也优化了部分。

其他一些细节简单地回顾一下:

如何判定参数是Object类型:

        _.isObject = function(obj){
            return obj === Object(obj);
        };


        //tangram 2.0.0.0
        //与原版本参数命名不一样,个人不推荐unknow
        baidu.isObject = function(source){
            return typeof source === "function" || (typeof source === "object"&& source != null)
        };

        //qwrap 1.1.5
        isObject:function(obj){
            return obj !== null && typeof obj == 'object'
        };

很多关注ECMAScript的同学应该和我一样,会有一个问题,既然有keys了,为啥不直接再来一个values呢?

其实它出现过,不是在ES5里面,而是在Ees3.1的一个官方wiki讨论里面

当然也有人在bugzilla讨论Object.keys的时候,给了一个 方案

        //有的外国人就是不喜欢{}这东西
        Object.values = function (O) Object.keys(O).map(function(k) O[k]);

那我们如何应该是values呢?

        /* underscore1.4.1 
         * https://github.com/jashkenas/underscore/blob/1.4.1/underscore.js
         * 依赖_.has的过滤,还是push的方式
         */
        _.values = function(obj){
            var values = [];
            for(var key in obj){
                if(_.has(obj,key)){
                    //只是存的东西变成了obj[key]
                    value.push(obj[key]);
                }
            }

            return values;
        };


        /* underscore 1.5.1第一个版本
         * 依赖_.keys,先取出keys,然后按照keys的length进行for-in
         */ 
        _.values = function(obj){
            //一次定义变量
            var keys = _.keys(obj),
                values = [],
                i = 0,
                length = keys.length;

            for(;i < length;i++){
                values[i] = obj[key[i]];
            }    

            return values;
        };

        //2013-7-24 braddunbar完善了一把
        _.values = function(obj){
            var keys = _.keys(obj),
                length = keys.length,
                //直接定义一个有长度的Array,而不是[]
                values = new Array(length),
                i = 0;

            for(;i < length;i++){
                values[i] = obj[keys[i]];
            }    

            return values;
        };



        /* tangram 2.0.0.0
         * 而是不采用push方式,length++装载值
         */
        baidu.object.values = function(source){
            //一次定义变量
            var result = [],
                resultlen = 0,
                k;
            for(k in source){
                if(source.hasOwnProperty(k)){
                    result[resultLen++] = source[k];
                }
            }    
        };


        /* qwrap 1.1.5
         * 一如既往的朴实风格,和keys基本代码一致,就是装载的东西变了一下下
         */
        values: function(obj){
            var ret = [];
            for(var key in obj){
                if(obj.hasOwnProperty(key)){
                    //和keys实现基本一样,只是push的对象不一样
                    ret.push(obj[key]);
                }
            }

            return ret; 
        };

简单提提kissy:

关于kissy,我个人是一个长期源码观察者,除了代码设计层面给我的一些启发外, 还有一些小细节:

比如关于某一个方法在某些操作系统下的浏览器支不支持的注释, 让我比较佩服,

毕竟测试覆盖到如此之细~ 比如:kissy 1.3 的3474行:ie 8.0.7600.16315@win7 json bug!

所以针对一些源码source版本很干净的,注释很少的,我基本会“适度”研习而之丢弃。

其实对于Object,我们以前在频繁操作它的时候有很多痛: 比如:看看它的长度,也得遍历一遍,取出keys,看keys数组的长度来知道这个Object的长度。

下面是underscore 1.5.1的代码:

        /* underscore 1.5.1
         * return the number of values in the list
         * 参数过滤,支持Array和Object
         */
        _.size = function(obj){
            //参数过滤,如果null或者undefined,返回0
            if(obj == null){
                return 0;
            }
            //支持Array类型,方式还是比较独特的
            //如果是Array,直接length
            //如果是Object,取keys,这个方法上面已经给出
            return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
        }

        //示例:
        _.size({name:"zhangyaochun",job:"fe",for:"w3cplus"});  //3
        _.size([1,2,3]);   //3    

这个方法,返回一个Object或者Array的长度。

 

再看看ECMAScript6 这次给出新的方法~

Object.is

用来判定两个值是否相等

语法:

        var isSame = Object.is(value1,value2);

我们先来看看代码示例:

        Object.is("1",1);   //false
        Object.is("name","name");  //true
        Object.is(0,-0);     //false
        Object.is(NaN,NaN);  //true
        Object.is(NaN,0/0);  //true

基本上,功能和===差不多,但是有特例:0 和 -0

用心一点的同学是不是发现:这个方法也可以用来判定是否是NaN嘛

下面是提供的一种兼容方式:

        if(!Object.is){
          Object.is = function(v1,v2){
            if(v1 === 0 && v2 === 0){
              return 1 / v1 === 1 / v2;
            }
            if(v1 !== v1){
              return v2 !== v2;
            }
            return v1 === v2;
          };
        }

小知识点

braddunbar 这个人有人认识不

他自己的博客描述:

Hey, I'm Brad,a husband,dad,javascripter and enjoyer of peanut butter sandwiches (preferably with honey and raisins).

其实他和undersorce、backbone、jquery、jquery ui都有关系。

在我眼中,他虽然只是一个contributor,但他是一个值得尊敬的人~

扩展阅读:

如需转载,烦请注明出处:http://www.w3cplus.com/js/ecmascript-lesson-2.html


Viewing all articles
Browse latest Browse all 1557

Trending Articles