【原创】parseInt大改造

引言
      还隐约记得第一次遇到parseInt这个诡异问题的时候,以为发现了IE的BUG,兴奋不已。
 还隐约记得得知了来龙去脉,为自己掌握了一个经验而欢呼雀跃。
 还隐约记得被这同一问题折磨了无数次后,无奈与痛下决心的心境。

     首先我必须感谢那些即使这个问题我强调过无数次,也依然反复重复类似错误的人们。
      没有他们反复犯错的鼓励,或许我不会认真考虑这个问题的解决方案。
     其次,必须感谢《JavaScript高级程序设计》的作者和译者。
     在这里我得到了解决该问题的启示,不然我依然要每每强调使用parseInt时应注意什么。

     同时,希望在这里不仅仅留下一个解决方案。
      解决问题的思路与想法,以及对问题举一反三的经验在这里保留一下。

问题:
      很久以前发生的问题不想再痛苦的回忆。
      这次的问题很简单。两个月份比较的时候,因为月份是从字符串中抽取出来的,于是用parseInt转换了一下。
     结果parseInt("08")之后结果是 0
      原因请参看以下《JavaScript高级程序设计》19~20页对 parseInt函数的讲解。

            parseInt() 方法还有基模式,可以把二进制、八进制、十六进制或其他任何进制的字符串转换成整数。
            基是由 parseInt() 方法的第二个参数指定的,所以要解析十六进制的值,需如下调用 parseInt() 方法:
           var iNum1 = parseInt("AF"16);    //返回 175
            当然,对二进制、八进制甚至十进制(默认模式),都可以这样调用 parseInt() 方法:
           var iNum1 = parseInt("10"2);    //返回 2
           var iNum2 = parseInt("10"8);    //返回 8
           var iNum3 = parseInt("10"10);    //返回 10
            如果十进制数包含前导 0,那么最好采用基数 10,这样才不会意外地得到八进制的值。例如:
           var iNum1 = parseInt("010");    //返回 8
           var iNum2 = parseInt("010"8);    //返回 8
           var iNum3 = parseInt("010"10); //返回 10
            在这段代码中,两行代码都把字符 "010" 解析成一个数字。
            第一行代码把这个字符串看作八进制的值,解析它的方式与第二行代码(声明基数为 8)相同。最后一行代码声明基数为 10,所以 iNum3 最后等于 10。


初试身手:
      以前的解决方法是让大家都抛弃 parseInt函数,全部以parseFloat来替换。
     但是作为人“孰能无忘”?
      最好的办法莫过于保留parseInt的“形”,废了parseInt的“神”。
      于是我想到了《JavaScript高级程序设计》87~88页关于“重定义已有方法”的说明。
         3.6.2  重定义已有方法
           就像能给已有的类定义新方法一样,也可重定义已有的方法。
             如前一章所述,函数名只是指向函数的指针,因此可以轻易地使它指向其他函数。如果修改了本地方法,如toString(),会出现什么情况呢?
          Function.prototype.toString = function () {
              
return "Function code hidden";
          }
             前面代码完全合法,运行结果完全符合预期:
             function sayHi () {
                    alert(
"你好!");
             }

             alert(sayHi.toString()); 
//输出"Function code hidden"
             也许你还记得,第2章中介绍过FunctiontoString()方法通常输出的是函数的源代码。
             覆盖该方法,可以返回另一个字符串(在这个例子中,返回"Function code hidden")。
             不过,toString()指向的原始函数怎样了呢?它将被无用存储单元回收程序回收,因为它被完全废弃了。
             没能够恢复原始函数的办法,所以在覆盖原始方法前,存储它的指针比较安全,以便以后的使用。
             你甚至可能在某种情况下在新方法中调用原始方法:
           Function.prototype.originalToString = Function.prtotype.toString;

           Function.prototype.toString 
= function () {
       
if(this.originalToString().length >100) {
         
return "Function too leng to display."
       } 
else {
         
return this.originalToString();
  }  
             在这段代码中,第一行代码把对当前toString()方法的引

             用保存在属性originalTo- String中。然后用定制的方法覆盖了toString()方法。
             新方法将检查该函数源代码的长度是否大于100
             如果是,就返回错误消息,说明该函数代码太长,否则调用originalToString()方法,返回函数的源代码。


      根据这个例子,只要照葫芦画瓢写一行
            Global.prototype.parseInt = Global.prototype.parseFloat;
      那么 parseInt函数真的就变成徒有其表,肚子里面干的却是parseFloat勾当的函数了。
       但是,同时一个致命的问题点也摆在眼前。
      那就是JavaScript中的Global对象,就跟神一样,只是个概念。
      说明请参见下面《JavaScript高级程序设计》70页关于“内置对象”的说明。
             Global对象是ECMAScript中最特别的对象,因为实际上它根本不存在。
                          如果尝试编写下面的代码,将得到错误:
                  var pointer = Global;
                         错误消息显示Global不是对象,但刚才不是说Global是对象吗?
                         没错。这里需要理解的主要概念是,在ECMAScript中,不存在独立的函数,所有函数都必须是某个对象的方法。
                         本书前面介绍的函数,如isNaN()、isFinite()、parseInt()和parseFloat()等,看起来都像独立的函数。
                         实际上,它们都是Global对象的方法。       

      于是,上网大查怎样获取Global对象,或怎么使用Global.prototype来改变parseInt函数。
      结果可想而知,神就是神,就连著名的“搜神网”Google也查不出来。
      欲放弃之时,果然应了那句“死地而后生”。突然想到parseInt就像个全局函数一样,根本不用什么对象调用。
      那是不是说,只要把上面那句改成 parseInt = parseFloat;就可以了?
      果然,神,无处不在!!! 好用了!!!


深度考究:
      问题基本上解决了。只有一点需要注意的,就是JavaScript加载出错的时候,后面的语句就不加载执行了。
      所以这句一定要放在第一句执行。现在正好建一个JavaScript通用方法库,要求以后每个页面必须引入该库文件。
      所以这一句放在该JavaScript通用方法库的第一行,从此便可高枕无忧。
      但是当我在为该段代码写注释,特别是列举如何应用时,发现如下代码的问题
       alert(parseInt("010"2));  //10
       alert(parseInt("010"8));  //10
       alert(parseInt("010"10)); //10
       每一个处理的返回值都是10,也就是说可以处理二进制,八进制,十六进制的parseInt从此消失了。
      如果说单个参数的parseInt惹出了不少麻烦,我们对于没有惹祸的两个参数的parseInt还是希望保留其特异功能的。
      于是需要进一步的改进。
      那么就要根据使用parseInt函数时,传递参数的个数来进行判断处理。
      如果只有一个参数,那么就调用parseFloat返回结果。
      如果有两个以上的参数,那么就调用parseInt两个参数的处理,返回结果。
      这里判断参数个数用到arguments对象,参见《JavaScript高级程序设计》53~54页关于arguments对象的说明。
            在函数代码中,使用特殊对象 arguments,开发者无需明确指出参数名,就能访问它们。
               例如,在函数 sayHi() 中,第一个参数是 message。
               用 arguments[0] 也可以访问这个值,即第一个参数的值(第一个参数位于位置 0,第二个参数位于位置 1,依此类推)。
               因此,无需明确命名参数,就可以重写函数:
           function sayHi() {
               
if (arguments[0== "bye") {
                   
return;
               }

               alert(arguments[
0]);
           }
        
      于是就有了如下代码:
          originalparseInt = parseInt;
          parseInt 
= function (){
          
if(arguments.length == 1){
                
return parseFloat(arguments[0]);
          } 
else {
                
return originalparseInt(arguments[0], arguments[1]);
          }

      这段代码里我们改造了parseInt,让其通过参数个数的不同进行不同的处理。
      用一个新的变量originalparseInt保留了parseInt的原型。
      这样我们即便改造了parseInt,依然能通过保留的原型变量originalparseInt使用parseInt的原始功能。


返璞归真:
      代码写到这本以为一切都OK了,却被人说彻底抹杀了parseInt函数对2进制,8进制的处理。
      想想也是。处理的过于极端,只想着用parseFloat彻底替换掉讨厌的parseInt函数。
      如果我们正的用到2进制或8进制的数字转换,还得用我们费劲保留的parseInt函数的原型新变量originalparseInt。
      其实我们的愿望很简单。
      parseInt函数中只有一个参数的时候就想让它简单的处理10进制的转换,别再因为首位的0来产生一些头疼的bug。
      当我们用到第二个参数,想让它处理2进制,8进制的时候,我还依然能用parseInt既存的功能。
      于是有了下面最终的代码:
      $parseInt = parseInt;
      parseInt 
= function () {
          
var a = arguments;
          
if(a.length == 1) {
              
return $parseInt(a[0],10);
          } 
else {
              
return $parseInt(a[0], a[1]);
          }
      };
      考虑到js文件体积的问题,尽量减少代码量。于是把 originalparseInt 换成了 $parseInt。
     另外把超级长的内置对象名arguments直接换成了一个字母 a 这样该对象用了4次节省的代码量就非常可观了。
             
 
举一反三:
      对parseInt函数的再造就完成了。
      那么其实我们可以根据这次改造的经验,改造与parseInt具有类似的烦人特性的JavaScript方法。
      譬如,escape,unescape这种已经被 W3C组织不推荐使用的方法就可以用被推荐的方法替换掉
        escape = encodeURI;
        unescape 
= decodeURI;
      那么基于这次的经验,今后遇到类似的问题就可以考虑到用这种乾坤大挪移的方法去解决了。
posted @ 2009-09-05 22:05  涩道  阅读(344)  评论(1编辑  收藏  举报