打印

SJ5003: 各浏览器中用 for in 可以遍历对象中被更新的内置方法存在差异

作者:钱宝坤

标准参考

在 ECMAScript 中,原生对象的 prototype 中的预置 property 是不会被遍历出来的,因为这些 property 的 [[Enumerable]] 属性都为 'false'。

关于 Property Attributes 请参考 ECMAScript 规范 8.6.1 Property Attributes 中的内容。

关于 for...in 语法的说明请参考 ECMAScript 规范 12.6.4 The for-in Statement 中的内容。

关于 Function Prototype Object 的说明请参考 ECMAScript 规范 15.3.4 Properties of the Function Prototype Object 中的内容。

问题描述

改变 Date、Array 或 String 等对象的原型对象中的属性或方法的引用后,再用 for in 遍历一个 Date、Array 或 String 对象时,在 Safari Chrome 中可以遍历出这个修改过的属性或方法名。

造成的影响

代码执行在各浏览器下效果可能不一致。

受影响的浏览器

Chrome Safari

问题分析

分析以下代码:

Array.prototype.pop=function(){};
var a=[];
for(var i in a)document.write(i," ");

各浏览器表现如下:

  IE Firefox Oprea Chrome Safari
Array.prototype 无内容 pop

本例中,Array.prototype.pop 被重指向为一个新的函数,在这种情况下,仅在 Chrome Safari 中,'pop' 也将被遍历出来。

将 Array 替换为 Date String 对象后,分别修改它们的属性或方法名,这个修改过的属性或方法名也将被遍历出来。此外,如果覆盖 Math 对象的内置方法,也同样会被遍历,具体可看测试用例:

  String.prototype.charCodeAt = function(){};
  var s = '';
  document.writeln('<p style="background:#CCC" >for in String :</p>');
  for(var i in s)
    document.write(i, ' ');

  Date.prototype.getFullYear = function(){};
  var d = new Date();
  document.writeln('<p style="background:#CCC" >for in Date :</p>');
  for(var i in d)
    document.write(i, ' ');

各浏览器表现如下:

  IE Firefox Oprea Chrome Safari
Date.prototype 无内容 getFullYear
String.prototype 无内容 charCodeAt
Math 无内容 abs

但是,如果覆盖 Object Number 对象原型中的 toString valueOf 内置方法则不会被遍历,如下例:

  Number.prototype.valueOf = function(){}
  Number.prototype.toString = function(){}
  var n = new Number(1);
  document.writeln('<p style="background:#CCC" >for in Number :</p>');
  for(var i in n)
    document.write(i, ' ');

  Object.prototype.valueOf = function(){};
  Object.prototype.toString = function(){};
  var o = new Object();
  document.writeln('<p style="background:#CCC" >for in Object :</p>');
  for(var i in o)
    document.write(i, ' ');

各浏览器表现如下:

  所有浏览器
Number.prototype.toString 不可被遍历
Number.prototype.valueOf 不可被遍历
Object.prototype.toString 不可被遍历
Object.prototype.valueOf 不可被遍历

此外还有特殊情况存在,如下:

  document.writeln('<p style="background:#CCC" >for in Function :</p>');  
  document.writeln('Function.prototype.valueOf is:', Function.prototype.valueOf ? true : false);
  Function.prototype.valueOf = function() {};
  Function.prototype.toString = function() {};
  var f = function(){};
  for(var i in f)
    document.write(i, ' ');

各浏览器表现如下:

  IE6 IE7 IE8 Chrome Safari Firefox IE9 Beta Oprea
Function.prototype.valueOf true true true
Function.prototype1 无内容输出 valueOf prototype valueOf

【注】:Function.prototype 并不是一个 javaScript 中指的 object。ECMAScript 中规定了 Function.prototype 需要存在 [[Call]] 内部方法,并且需要实现 call 和 apply 两种 JavaScript 方法。因此,从 JavaScript 语言层面来看, Function.prototype 实质上是个 function 而非 object。

根据 ECMAScript 5th 以及 ECMAScript 3th 中描述,Function.prototype 内没有规定实现 valueOf 方法。但是测试结果证明,所有浏览器都实现了他。但由于这种实现为非标准的,Chrome Safari Firefox Opera IE9 Beta 都没有强制保护这个 valueOf 方法不被枚举。valueOf 原生方法中自带 [[Enumerable]] 属性与原始设定值,由于覆盖这个方法的的新函数并不具备内置的 [[Enumerable]] 属性,导致他可被遍历。Opera 的情况更为特殊,Function.prototype 被修改或扩充后,prototype 对象的 [[Enumerable]] 属性丢失或其值或被修改为 true,使得他也被枚举出来。

另外,Function.prototype 的 toString 方法被覆盖后依然无法被枚举,这证明了这个方法被做了特殊处理,即使被覆盖,其原始的 [[Enumerable]] 属性值依然不变。

综上所有情况可见:

  • 在 Chrome Safari 浏览器的脚本引擎实现中,原生方法自带 [[Enumerable]] 属性与原始设定值,由于覆盖内置方法的的新函数并不具备内置的 [[Enumerable]] 属性,导致他们可被枚举。
  • 而 IE6 IE7 IE8 Opera 中,可能是将 [[Enumerable]] 属性是与原生方法名放置在对照表内,同名新方法覆盖老方法后,检查对照表后,同名新方法的 [[Enumerable]] 属性值不变,因此依然无法被枚举。
  • Function.prototype 的情况较为特殊,所有浏览器中均实现了原生的 valueOf 方法,并且在他被覆盖后 [[Enumerable]] 属性值丢失,变得可被枚举。这说明 IE6 IE7 IE8 Opera 中,此原生方法并未放置在可能的存在的对照标表中,而是使用如同 Chrome Safari 机制来处理。
  • 所有浏览器中,对于位于 Object 与 Number 原型对象上的 toString 和 valueOf 内置方法都做了特殊处理,即使被原生方法被覆盖,其 [[Enumerable]] 属性值依然不变,无法被枚举。
  • Opera 中,当 Function.prototype 被修改或者扩充后,prototype 原始的 [[Enumerable]] 属性丢失或其值或被修改为 true,使他变得可被枚举。

解决方案

对于数组,避免用 for...in 方式而采用索引即数字下标的形式枚举数组成员。

对于 Date String 以及其他对象,没有必要使用 for...in 来枚举它们的成员,因此一般不会有兼容性问题。

参见

知识库

相关问题

测试环境

操作系统版本: Windows 7 Ultimate build 7600
浏览器版本: IE6
IE7
IE8
Firefox 3.6.13
Chrome 4.0.302.3 dev
Safari 5.0.2
Opera 11.01
测试页面: for_in_native_code.html
本文更新时间: 2011-02-18

关键字

Date Array String prototype property for in enumerable DontEnum 枚举 原型 属性