Array 的方法们 和 object 那些让人纠结的问题 ......

昨天 一个朋友 问了这样一个问题.  本来类似的代码测试,以前做过好多... 但发现当我试图从根源上找出原因的时候,却有些无所适从. 特记录下来,待以后求证.


简单的问题 :
var con = function(){};
var a = con.prototype = [];
var obj = new con;
obj.push(1);
alert(obj[0]);
alert(obj.length); //ie 6 7 8(8怪异模式下)  输出0 , ie8标准模式 以及 ie9 和其他浏览器 输出1  
在ie 下 1 被 push 到哪去了.
显然在push中 浏览器都有 this[this.length++] = value 类似的逻辑. (代码肯定不是这样, 而维护this.length 的地方 各个浏览器可能有区别,这也是ie 6 7 8实现不同的地方之一)
另外一个版本 : 
接上面obj.push(1);
我们再次 obj.push(2);
alert(obj[0]);
alert(obj[1]); 
悲剧出现了.
究其原因. 无非是ie6  7 8(怪异模式)没有正常的维护到 this.length 属性.
让我们换个思路 :
var obj = {length : 0};
[].push.call(obj,1);
[].push.call(obj,2);
alert([obj[0],obj[1],obj.length]);
这次所有浏览器的行为都正常了.
好吧.原来只有obj自身具备.length属性 而不是原型链上继承来的.貌似ie就ok了. 我们回到初始的版本.并做些修改.再次尝试下看看.
     var con = function(){};
     var a = con.prototype = [];
     var obj = new con;
     obj.length = 0 ;
     //obj.push(1);
     //obj.push(2);
     a.push.call(obj,1);
     a.push.call(obj,2);
     alert(obj[0]);
     alert(obj[1]);
ie6 7 8  仍然是老问题.. this.length 没有被正确维护.  具体原因..我仍然纠结着...  做过几种尝试. 比如修改constructor  甚至a.constructor 都 无甚改观. 唯一我触碰不到的 是 obj.__proto__了 . 最终留下一个猜测. 当obj 的__proto__指向一个数组的时候.  ie6 7 8反倒脑残了....
接着. 我们可能要尝试一些其他特别的对象了... 也引发出了另外一些奇怪的问题. 
关于 arguments . 
首先这次ie系的表现不赖.至少和大多数浏览器的行为保持一致... 虽然目前所有我所知道的浏览器,没有一个是严格遵守 ecma262 v3 or v5 规范. 
在arguments 和 Array原型方法配合演出之前. 我们还要做一些小的demo,来说明一些问题.
一个 可能大家都知道 而 NCZ 大神.最近在其blog 提到的有趣问题是 : 
     function f(a){
          arguments[0] = 10;
          alert(a);
     }
     f();
除了chrome 或用了它v8引擎的浏览器以外 都打印undefined .
原因自然是因为 chrome 对arguments的实现 与其他浏览器区别 所导致的...
很明显 chrome 在玩简单暴利 .  一直关联.并维护arguments[index]与对应形参的关系... 而其他浏览器的问题则是 同样不考虑 形参,实参数目是否匹配. 一样把他们关联起来了.只不过,关联维护的对象,不是 arguments 而是其可以通过索引访问的成员们..(可以参考 ref variable)
而且chrome 这里还有一个 很悲剧的bug ...
(chrome15已修复.无论argument或eval,是否存在.下面的代码中,a都是中是undefined.原因是对es5的遵守.不再无条件相互共享形参和arguments的对应成员的值了)

     Object.prototype['0'] = 'franky'; 
     function f(a){ 

          alert(a); 
          arguments; //如果注释掉arguments调用.则 alert(a)的结果将完全不同.

        //eval(''); 
     } 
     f();

这里 eval('') 和 arguments; 都可以导致 打印 franky 而不是 undefined....
事实上这个bug 有些类似于 早期的ff (ff3.0之前的版本) spider monkey 引擎 的一个bug .
Object.prototype.attr = 'franky';
void function(){
     var attr = 'ooxx' ;
     void function fn(){
          alert(attr);// 它十分荣幸的打印了 franky .而不是我们期望的 ooxx.
     }();
}();
see ? 一样的问题.
早期ff 为了遵守ecma标准.(实现“具名”函数表达式 只能在该函数自身作用域内 查找自身的函数名.)把 一个object 放进 当前作用域链对象 的顶端. 而这个object.__proto__ == Object.prototype .. 并忘记了做额外的保险处理. 导致了这个 问题的发生.

回头看看chrome干的好事..明显 eval 和arguments 把其对arguments这个对象的某些保护机制给干掉了.... 
最初,我是这样想的.但仔细想想 不大对劲... arguments;或 eval('') 是放到alert后面也生效的...那么问题应该出在词法分析期.而不是执行期,那么我们就可以推断出,chrome 在分析一个函数主体上下文的时候,会看你是不是有 arguments或eval 如果有 则给这个函数arguments 对象,反之则没有.这本质上 是一种优化. 也就是说chrome程序员,压根就没有给arguments任何保护机制.阻止其访问Object.prototype.

证明这一推论的 论据并不是很充分.但是我还是尝试给出2个...
如果eval 也会触发这个bug .那么我们是不是可以考虑为 . eval 具备一个隶属当前执行上下文环境的(作用于当前作用域),独特的执行上下文环境. 他内部一但出现arguments.则就是当前函数的arguments.  所以chrome不得不在 eval出现时, 也派发给当前函数对象 arguments对象.从而出现了这个bug..
因为 不仅仅eval,new Fcunction(),window.eval('') 也都具备动态执行语句的特点..而区别则在于 作用域.即 除了 eval(''),其他两种方法 无法访问到当前函数的arguments. 

为了鼓励我们的chrome. 我们接着说其他浏览器的 一个有趣的问题 :

function f(a,b,c){ 
          for(var i = arguments.length ; i--; [].shift.call(arguments)); 
          alert(arguments[0]); 
          alert([a,b,c]) ;
} 
f(1,2,5);

这个例子 比较特殊..如果你的浏览器足够多. 你会像我曾经那样,表情呆滞的一直看着屏幕....
好在,我找到了它的答案.
ie 8 : 标准模式下 , ie6 , ie7 , ie9, ff , safari , opera9.6     :     undefined     5,5,5 
ie 8 : 怪异模式下 ,opera 10     :     5     5,5,5
ie 8在怪异模式下完全可以解释成一种脑残行为.
因为ie 8怪异模式可能考虑要兼容早期页面的一些代码. 所以此模式下,ie8 把 window.JSON 都给干掉了. 而一些山寨浏览器,当使用内核同为ie8时.直接开启所谓兼容模式.同样没有JSON.
所以 所有使用ie8内核的山在浏览器 跑这个测试的结果 一率会是 5   5,5,5 .除非你有办法让他不进入兼容模式.
但是我所不能理解的是.为什么ie8 jscript引擎 在进入 所谓兼容模式时,会这么奇怪..它兼容的是谁?难道是ie5.5 ? 

再看看opera10 ...这个浏览器一直让人很无语的地方时..很多特性 经常变来变去.时好时坏. 无视之.
现在来解释下 5,5,5出现的原因...
shift 方法是这样工作的 :
arguments[0] : 1
arguments[1] : 2
arguments[2] : 5

第一次shift  它要做的就是 修改[0] 的值,即把 [1]的值给[0] .
然后把[2]的值给[1]. 然后 delete arguments[2] . 
你可以想象出,3次以后 如果delete 是有效的 那么 
[0] [1] [2] 的值都应该是 undefined 因为这三个key 被从arguments object上 移除了.
但是 和他们一一关联的(具备相同堆栈 引用 或 内存地址.具体情况视数据类型而定 ) 参数 a , b , c却把delete 之前的被新赋予的值保留下来了... 自然就都是 5 ...
opera10,arguments[0] 没有被删除成功.我可以理解为. opera团队 打算真的遵守 ecma262 v5的规范了...严格限制arguments的操作.  至于ie8.... 我仍然无法理解....因为ie9也没有向标准致敬..何况 是ie8的 所谓兼容模式?
本来还想 继续写  Array 原型上的其他方法 ,诸如 splice  sclie不同浏览器区别 以及对应 ie nodeList   和 ff等浏览器 .length 只读特性 等等等等....    以及 push shift unshift pop 等等 在ie下比较听话的原因之类的... 突然发现 实在是无体力了... 如果大家有兴趣..可以自己试试看. 
ps : 标题之所以说 object 而不是 arguments 本意也是如此...但是现在看来 有些标题党了.
posted @ 2010-11-12 16:27  Franky  阅读(2536)  评论(12编辑  收藏  举报