iOS 上 jQuery.each() 和 Underscore.each() 的神秘失败
Posted
技术标签:
【中文标题】iOS 上 jQuery.each() 和 Underscore.each() 的神秘失败【英文标题】:Mysterious failure of jQuery.each() and Underscore.each() on iOS 【发布时间】:2015-01-26 17:54:41 【问题描述】:简要总结给从 Google 登陆这里的任何人:ios8 中存在一个错误(仅限 64 位设备),它会间歇性地导致幻像“长度”属性出现在只有数值属性。这会导致 $.each() 和 _.each() 等函数错误地尝试将对象迭代为数组。
我已经向 jQuery (https://github.com/jquery/jquery/issues/2145) 提交了问题报告(实际上是一个解决方法请求),并且在 Underscore 跟踪器 (https://github.com/jashkenas/underscore/issues/2081) 上也存在类似问题。
更新:这是一个已确认的 webkit 错误。 2015 年 3 月 27 日提交了一个修复程序,但没有迹象表明哪个版本的 iOS 将有修复程序。见https://bugs.webkit.org/show_bug.cgi?id=142792。目前已知 iOS 8.0 - 8.3 会受到影响。
更新 2: 可以在 jQuery 2.1.4+ 和 1.11.3+ 以及 Underscore 1.8.3+ 中找到 iOS 错误的解决方法。如果您使用这些版本中的任何一个,则库本身将正常运行。但是,您仍需确保自己的代码不受影响。
这个问题也可以叫做:“没有长度的对象怎么会有长度?”
我在使用移动 Safari 时遇到了一些问题(在运行 iOS 8 的 iPhone 和 iPad 上都可以看到)。我的代码在使用 jQuery ($.each()
) 和 Underscore (_.each()
) 的“每个”实现时出现了很多间歇性故障。
经过一番调查,我发现在所有失败的情况下,each
函数都将我的对象视为一个数组。然后它会尝试像数组一样迭代它(obj[0]
、obj[1]
等)并且会失败。
jQuery 和 Underscore 都使用 length
属性来确定参数是对象还是类数组/数组的集合。例如,Underscore 使用这个测试:
if (length === +length) ... this is an array
我的对象没有长度参数,但它们触发了上述if
语句。我再次确认没有length
by:
-
在调用
each()
之前将obj.length
的值发送到服务器进行日志记录(确认length
was undefined
)
在调用 each()
之前先调用 delete obj.length
(这并没有改变任何东西。)
我终于能够在调试器中捕获此行为,并将 iPhone 连接到 Mac 上的 Safari。
下图显示$.isArrayLike认为length
是7。
但是,控制台跟踪显示 length
是 undefined
,正如预期的那样:
在这一点上,我认为这是 iOS Safari 中的一个错误,尤其是因为它是间歇性的。我很想听听其他看到此问题并可能找到解决方法的人。
更新
我被要求创建一个小提琴,但不幸的是我做不到。似乎存在时间问题(设备之间甚至可能不同),我无法在小提琴中重现它。这是我能够重现问题的最少代码集,它需要一个外部 .js 文件。这段代码在我运行 8.1.2 的 iPhone 6 上 100% 发生。如果我更改任何内容(例如,使 JS 内联、删除任何不相关的 JS 代码等),问题就会消失。
代码如下:
index.html
<html>
<head>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="script.js"></script>
</head>
<body>
Should say 3:
<div id="res"></div>
<script>
function trigger_failure()
var obj = 1: '1', 2: '2', 3: '3' ;
print_last(obj);
$(window).load(trigger_failure);
</script>
</body>
</html>
script.js
function init_menu()
var elemMenu = $('#menu');
elemMenu
.on('mouseenter', function() )
.on('mouseleave', function() );
elemMenu.find('.menu-btn').on('touchstart', function(ev) );
$(document).on('touchstart', function(ev) );
return;
function main_init()
$(document).ready(function()
init_menu();
);
function print_last(obj)
var a = $($.parseHTML('<div></div>'));
var b = $($.parseHTML('<div></div>'));
b.append($.parseHTML('foo'));
$.each(obj, function(key, btnText)
document.getElementById('res').innerHTML = ("adding " + btnText);
);
main_init();
【问题讨论】:
你能展示你是如何构建你的对象的吗?你能在小提琴中重现这个问题吗? 您的观察的许多可能原因浮现在脑海中。我们确实需要更多代码来为您提供诊断。 那个特定的对象是用内联代码简单地构建的 ` 1: 'Just me', 2: '2', 3: '3', 4: '4', 5: '5' , 6: 'More than 5' 并作为参数传递给另一个名为 $.each() 的函数。 您能在fiddle 中重现问题吗? @dystroy 我看看能不能搞定它。 【参考方案1】:这不是一个答案,而是在经过多次测试后对幕后发生的事情的分析。我希望在阅读完这篇文章后,无论是 Safari 移动端还是 iOS 端 javascript VM 的人都可以看看并纠正这个问题。
我们可以确认 _.each() 函数将 js 对象 视为数组 [],因为 safari 浏览器将对象的“长度”属性作为整数返回。 但仅在某些情况下。
如果我们使用键是整数的对象映射:
var obj =
23:'some value',
24:'some value',
25:'some value'
obj.hasOwnProperty('length'); //...this comes out as 'false'
var length = obj.length; //...this returns 26!!!
使用移动 Safari 浏览器上的调试器进行检查,我们清楚地看到 obj.length 是“未定义的”。但是进入下一行:
var length = obj.length;
长度变量显然被赋值为 26,它是一个整数。整数部分很重要,因为 underscore.js 的 bug 出现在 underscore.js 代码的这两行:
var i, length = obj.length;
if (length === +length) //... it treats an object as an array because
//... it has assigned the obj (which has no own
//... 'length' property) an actual length (integer)
但是,如果我们稍微改变一下有问题的对象并添加一个键值对,其中键是一个字符串(并且是对象中的最后一项),例如:
var obj =
23:'some value',
24:'some value',
25:'some value',
'foo':'bar'
obj.hasOwnProperty('length'); //...this comes out as 'false'
var length = obj.length; //...this now returns 'undefined'
更有趣的是,如果我们再次更改对象和键值对,例如:
var obj =
23:'some value',
24:'some value',
25:'some value',
75:'bar'
obj.hasOwnProperty('length'); //...this comes out as 'false'
var length = obj.length; //...this now returns 76!
似乎该错误(无论发生在何处:Safari/JavaScript VM)会查看对象中最后一项的 key,如果它是整数,则将其加一 (+1)并报告为对象的长度...即使 obj.hasOwnProperty('length') 返回为 false。
这发生在:
iOS 版本 8.1.1、8.1.2、8.1.3 的一些 iPad(但不是我们拥有的所有 iPad) 它确实发生在 iPad 上,它始终如一地发生......每次 仅限 iOS 上的 Safari 浏览器这不会发生在:
我们尝试使用 iOS 8.1.3 的任何 iPhone(同时使用 Safari 和 Chrome) 任何装有 iOS 7.x.x 的 iPad(同时使用 Safari 和 Chrome) iOS 上的 chrome 浏览器 我们尝试使用上述 iPad 创建的任何 js fiddles 始终会产生错误因为我们无法用 jsFiddle 真正证明这一点,所以我们做了次好的事情,并通过调试器获取了它的屏幕截图。我们在 youtube 上发布了视频,可以在这个位置看到:
https://www.youtube.com/watch?v=IR3ZzSK0zKU&feature=youtu.be
如上所述,这只是对问题的更详细分析。我们希望有更深入了解的人可以对这种情况发表评论。
一个简单的解决方案是不使用 _.each 函数(或等效的jQuery)。我们可以确认使用 angular.js forEach 函数可以解决这个问题。但是,我们非常广泛地使用 underscore.js,并且 _.each 几乎在我们遍历数组/集合的所有地方都使用。
更新
这已被确认为一个错误,并且在 2015 年 3 月 27 日的 WebKit 上已对此错误进行了修复:
修复: http://trac.webkit.org/changeset/182058
原始错误报告: https://bugs.webkit.org/show_bug.cgi?id=142792
【讨论】:
【参考方案2】:对于任何查看此内容并使用 jQuery 的人,请注意,此问题现已在 2.1.4 和 1.11.3 版本中得到修复,特别是仅包含对以上问题:
http://blog.jquery.com/2015/04/28/jquery-1-11-3-and-2-1-4-released-ios-fail-safe-edition/
【讨论】:
【参考方案3】:升级到最新版本的 lodash (3.10.0) 为我解决了这个问题。
请注意,在这个 lodash 版本中,_.first
与 _.take
之间存在重大变化。
对于不熟悉 lodash 的任何人 - 它是下划线的一个分支,现在 (imo) 是一个更好的解决方案。
非常感谢@OzSolomon 对这个问题的解释、描述和解决。
如果我能悬赏某个问题,我会做的。
【讨论】:
【参考方案4】:我已经处理了一段时间可能类似或相同的问题。我工作的公司有一款使用 KineticJS 4.3.2 的游戏。在 html5 画布上对图形对象进行分组时,Kinetic 会大量使用数组。 发生的问题是有时数组上缺少推送,或者存储在数组中的对象上的属性丢失。该问题发生在 Safari 中以及在 iOS8 中从主屏幕运行时。由于某种原因,在 iOS 上的 Chrome 中运行时没有出现此问题。连接调试器时也没有出现该问题。 在网上大量测试和搜索后,我们认为这是 iOS 上 webkit 的 JIT 优化错误。一位同事找到了以下链接。
TypeError: Attempted to assign to readonly property. in Angularjs application on iOS8 Safari
https://github.com/angular/angular.js/issues/9128
http://tracker.zkoss.org/browse/ZK-2487
目前,我们通过在访问不起作用的数组时删除点符号来解决问题(.children 已替换为 [ "children" ])。 我无法创建 jsFiddle。
【讨论】:
【参考方案5】:问题在于用户代码,而不是 iOS8 webkit。
var obj =
23: 'a',
24: 'b',
25: 'c'
当应该使用字符串时,上面的映射键是整数。它不是 javascript 标准的有效映射,因此行为未定义,Apple 选择将“obj”解释为 26 个元素(索引 0 到 25 包括在内)的数组。
使用字符串键,长度问题就会消失:
var obj =
'23': 'a',
'24': 'b',
'25': 'c'
【讨论】:
这不是真的。整数键是完全有效的(尽管 IIRC js 引擎应该将它们转换为字符串(或至少假装)。虽然你的修复可以从我读过的内容中工作。这种行为只是一个错误。 指向 ECMAscript 规范中的部分。您描述的是常见做法,而不是标准。 这就是我如何解释对象文字的语法规范(注意属性名下的数字文字)``` ObjectLiteral : PropertyNameAndValueList PropertyNameAndValueList , PropertyNameAndValueList : PropertyAssignment PropertyNameAndValueList , PropertyAssignment PropertyAssignment : PropertyName : AssignmentExpression PropertyName : IdentifierName StringLiteral NumericLiteral ``` ecma-international.org/ecma-262/5.1/#sec-11.1.5以上是关于iOS 上 jQuery.each() 和 Underscore.each() 的神秘失败的主要内容,如果未能解决你的问题,请参考以下文章