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

Oz *_*mon 24 javascript jquery mobile-safari ios underscore.js

从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: iOS错误的解决方法可以在jQuery 2.1.4+和1.11.3+以及Underscore 1.8.3+中找到.如果您正在使用这些版本中的任何一个,那么库本身将表现正常.但是,您仍然需要确保自己的代码不受影响.

这个问题也可以称为:"没有长度的物体如何长度?"

我在移动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
Run Code Online (Sandbox Code Playgroud)

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

  1. obj.length在调用之前将值发送到服务器以进行记录each()(确认length was undefined)
  2. 在打电话delete obj.length之前打电话each()(这没有改变任何东西.)

我终于能够在调试器中捕获此行为,并在Mac上将Safari连接到Safari.

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

调试器在$ .isArrayLike中停止

然而,一个控制台跟踪显示lengthundefined,符合市场预期: 控制台跟踪

此时我相信这是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>
Run Code Online (Sandbox Code Playgroud)

的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();
Run Code Online (Sandbox Code Playgroud)

Nik*_*Nik 8

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

我们可以确认_.each()函数将js对象{}视为arrays [],因为safari浏览器将对象的'length'属性作为整数返回.但仅在某些情况下.

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

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!!!
Run Code Online (Sandbox Code Playgroud)

在移动Safari浏览器上使用调试器进行检查,我们清楚地看到obj.length是"未定义的".然而步入下一行:

var length = obj.length;
Run Code Online (Sandbox Code Playgroud)

长度变量显然被赋值为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)
Run Code Online (Sandbox Code Playgroud)

但是,如果我们稍微更改有问题的对象并添加一个键值对,其中键是一个字符串(并且是对象中的最后一项),例如:

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'
Run Code Online (Sandbox Code Playgroud)

更有趣的是,如果我们再次更改对象和键值对,例如:

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!
Run Code Online (Sandbox Code Playgroud)

似乎该错误(无论它发生在哪里:Safari/JavaScript VM)查看对象中最后一项的,如果它是一个整数,则向它添加一个(+1)并将其报告为对象的长度. ..尽管obj.hasOwnProperty('length')回来是假的.

这发生在:

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

这不会发生在:

  • 我们尝试使用iOS 8.1.3的任何iPhone(同时使用Safari和Chrome)
  • 任何配备iOS 7.xx的iPad(同时使用Safari和Chrome)
  • iOS上的Chrome浏览器
  • 我们试图使用上面提到的始终创建错误的iPad创建的任何js小提琴

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

https://www.youtube.com/watch?v=IR3ZzSK0zKU&feature=youtu.be
Run Code Online (Sandbox Code Playgroud)

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

一个简单的解决方案是不使用 _.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