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

时间:2015-01-26 17:54:41

标签: javascript jquery ios underscore.js mobile-safari

  

对于从Google登陆此处的所有人的简短摘要:iOS8中存在一个错误(仅限64位设备),间歇性地导致幻像"长度"要在仅具有数字属性的对象上显示的属性。这会导致$ .each()和_.each()等函数错误地尝试将对象作为数组进行迭代。

     

我已经使用jQuery(https://github.com/jquery/jquery/issues/2145)提交了问题报告(实际上是变通方法请求),并且在Underscore跟踪器(https://github.com/jashkenas/underscore/issues/2081)上存在类似的问题。

     

更新:这是一个已确认的webkit错误。修复程序在2015-03-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

  1. 在调用obj.length之前将each()的值发送到服务器以进行记录(确认length was undefined
  2. 在致电delete obj.length之前致电each()(这并没有改变任何事情。)
  3. 我终于能够在调试器中捕获此行为,并在Mac上将Safari连接到Safari。

    下图显示$ .isArrayLike认为length是7。

    Debugger stopped in $.isArrayLike

    但是,控制台跟踪显示lengthundefined,符合预期: Console trace

    此时我认为这是iOS Safari中的一个错误,特别是因为它是间歇性的。我很乐意听到其他人看到这个问题,也许还有办法解决这个问题。

    更新

    我被要求创造一个这样的小提琴,但不幸的是我无法做到。似乎有时间问题(设备之间甚至可能不同),我无法在小提琴中重现它。这是我能够重现问题的最小代码集,它需要一个外部.js文件。使用此代码在我的iPhone 6上运行8.1.2时会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();
    

5 个答案:

答案 0 :(得分:8)

这不是一个答案,而是分析经过多次测试后发生的事情。我希望,在阅读本文后,有人可以在Safari端移动端或iOS端的JavaScript VM上查看并更正问题。

我们可以确认_.each()函数将js对象{}视为数组[],因为safari浏览器会返回&#39; length&#39;对象的属性为整数。 但仅限于某些情况

如果我们使用键是整数的对象映射:

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是&#34;未定义&#34;。然而步入下一行:

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(&#39;长度&#39;)返回为假。

这发生在:

  • iOS版本8.1.1,8.1.2,8.1.3
  • 的一些iPad(但不是我们所有的)
  • 它确实发生的iPad,它始终如一地发生......每次
  • 仅适用于iOS上的Safari浏览器

这不会发生在:

  • 我们尝试使用iOS 8.1.3(同时使用Safari和Chrome)的任何iPhone
  • iOS 7.x.x(同时使用Safari和Chrome)的任何iPad
  • iOS上的Chrome浏览器
  • 我们尝试使用上述一致创建错误的iPad创建的任何js小提琴

因为我们无法用jsFiddle真正证明这一点,所以我们做了下一个最好的事情,并通过调试器获得了屏幕截图。我们在youTube上发布了视频,可以在以下位置看到:

https://www.youtube.com/watch?v=IR3ZzSK0zKU&feature=youtu.be

如上所述,这只是对问题的更详细分析。我们希望有更多谅解的人可以评论这种情况。

一个简单的解决方案是 NOT USE _.each函数(或等效的jQuery)。我们可以确认使用angular.js forEach函数可以解决这个问题。但是,我们使用underscore.js非常广泛地使用_.each几乎在我们遍历数组/集合的任何地方使用。

<强>更新

这被证实是一个错误,现在有一个修复此问题的WebKit截至2015-03-27:

修复: http://trac.webkit.org/changeset/182058

原始错误报告: https://bugs.webkit.org/show_bug.cgi?id=142792

答案 1 :(得分:1)

对于任何看过这个并使用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/

答案 2 :(得分:1)

升级到最新版本的lodash(3.10.0)为我解决了这个问题。

请注意,此{lodash}版本与dc1394 vs new Model({id:4}) .fetchOne({withRelated: ['thing']}) .then(function(model) { // retrive the relation var things = model.things(); // you already had the query done }); 发生了重大变化。

对于任何不熟悉lodash的人来说 - 它是现在(imo)更好的解决方案的下划线。

非常感谢@OzSolomon解释,解释和解决这个问题。

如果我能够对问题给予赏金,我就会做到。

答案 3 :(得分:0)

我已经用了一些类似或相同的问题。我工作的公司有一个使用KineticJS 4.3.2的游戏。在html5画布上对图形对象进行分组时,Kinetic会大量使用Arrays。 发生的问题是有时数组上缺少推送,或者缺少存储在数组中的对象上的属性。在iOS8中从主屏幕运行时,问题出现在Safari中。出于某种原因,在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。

答案 4 :(得分:-4)

问题是用户代码,而不是iOS8 webkit。

var obj = {
  23: 'a',
  24: 'b',
  25: 'c'
}

当使用字符串时,上面的地图键是整数。它不是javascript标准的有效映射,因此行为未定义,Apple选择将`obj'解释为包含26个元素的数组(索引0到25,包括0和25)。

使用字符串键,长度​​问题就会消失:

var obj = {
  '23': 'a',
  '24': 'b',
  '25': 'c'
}