javascript - iOS 上 jQuery.each() 和 Underscore.each() 的神秘失败

标签 javascript jquery ios underscore.js mobile-safari

A brief summary for anyone landing here from Google: There is a bug in iOS8 (on 64-bit devices only) that intermittently causes a phantom "length" property to appear on objects that only have numeric properties. This causes functions such as $.each() and _.each() to incorrectly try to iterate your object as an array.

I have filed an issue report (really a workaround request) with jQuery (https://github.com/jquery/jquery/issues/2145), and there is a similar issue on the Underscore tracker (https://github.com/jashkenas/underscore/issues/2081).

Update: This is a confirmed webkit bug. A fix was comitted on 2015-03-27, but there is no indication as to which version of iOS will have the fix. See https://bugs.webkit.org/show_bug.cgi?id=142792. Currently iOS 8.0 - 8.3 are known to be affected.

Update 2: A workaround for the iOS bug can be found in jQuery 2.1.4+ and 1.11.3+ as well as Underscore 1.8.3+. If you're using any of these versions, then the library itself will behave properly. However, it's still up to you to ensure that your own code isn't affected.

这个问题也可以叫做:“没有长度的物体怎么会有长度?”

我在使用移动版 Safari 时遇到了模糊地带问题(在运行 iOS 8 的 iPhone 和 iPad 上都出现过)。使用 jQuery ($.each()) 和 Underscore (_.each()) 的“each”实现时,我的代码有很多间歇性故障。

经过一些调查,我发现在所有失败的情况下,each 函数都将我的对象视为一个数组。然后它会尝试像数组一样迭代它(obj[0]obj[1] 等),但会失败。

jQuery 和 Underscore 都使用 length 属性来确定参数是对象还是数组/类数组集合。例如,Underscore 使用此测试:

if (length === +length) { ... this is an array

我的对象没有长度参数,但它们触发了上述 if 语句。我通过以下方式双重验证没有length:

  1. 在调用 each() 之前将 obj.length 的值发送到服务器进行日志记录(确认 length 是未定义)
  2. 在调用 each() 之前调用 delete obj.length(这没有改变任何东西。)

我终于能够在调试器中使用连接到 Mac 上的 Safari 的 iPhone 捕获此行为。

下图是$.isArrayLike认为length为7

Debugger stopped in $.isArrayLike

但是,控制台跟踪显示 lengthundefined,正如预期的那样: Console trace

在这一点上,我认为这是 iOS Safari 中的一个错误,特别是因为它是间歇性的。我很想听听其他看到这个问题并可能找到解决方法的人的意见。

更新

我被要求创建一个 fiddle ,但不幸的是我不能。似乎存在时间问题(设备之间甚至可能有所不同),我无法在 fiddle 中重现它。这是我能够重现问题的最少代码集,它需要一个外部 .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();

最佳答案

这不是答案,而是经过大量测试后对幕后发生的事情的分析。我希望在阅读本文后,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 中的错误出现在 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)查看对象中最后一项的,如果它是整数,则向其添加一 (+1)并报告作为对象的长度...即使 obj.hasOwnProperty('length') 返回为 false。

这发生在:

  • iOS 8.1.1、8.1.2、8.1.3 版本的一些 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

关于javascript - iOS 上 jQuery.each() 和 Underscore.each() 的神秘失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28155841/

相关文章:

javascript - 将振荡器正确连接到音频流以创建图形 javascript

ios - 有没有办法从 iOS 中的包标识符中获取 URL 方案?

ios - RedLaser 驾照扫描仪 - 针对每个州进行校准 (iOS)

iOS 8 - UIWindow 方向始终为纵向

javascript - 使用 CoffeeScript 删除数组的重复元素

javascript - 如何优化我的加载 JQuery 函数?

javascript - 谷歌地图 : Embedded code for "directions to" an office?

javascript - HTML 名称属性中的大括号

javascript - 捕获传入的 URL 参数并传递到 div 的 "data-url"属性

javascript - jquery中的(this)和(_this)有什么区别吗?