jQuery $.each() возится, если объект, отличный от массива, имеет свойство длины

Я работаю над очень, очень, очень простой библиотекой, чтобы предоставить некоторые удобные функции для работы с собственными объектами JavaScript, в идеале (в конечном итоге) в стиле jQuery.

У меня есть очень простая функция: crawlObject, которую я изменил, чтобы использовать jQuery each() вместо цикла for(var key in obj).

function crawlObject(thisObj, onSuccess, doRecursion) {
    var stopCrawling = false;
    if (isFunction(onSuccess) && ($.isPlainObject(thisObj) || isArray(thisObj))) {
        $.each(thisObj, function(childKey, value) {
            var childObj = thisObj[childKey];
            if (false === stopCrawling) {
                stopCrawling = isTrue(onSuccess(childObj, childKey, thisObj, value));
            }
            if (false === stopCrawling && doRecursion) {
                stopCrawling = isTrue(crawlObject(childObj, onSuccess, doRecursion));
            }
        });
    }
    return stopCrawling;
}

Преимущество этого заключается в том, что он сканирует как объекты Array, так и «простые» объекты JS без дополнительной логики.

Но.

Если я передам «простой» объект JS, у которого есть имя свойства «длина», каждый () взорвется, как недееспособный феникс. Это может произойти, если я рекурсивно работаю с большим объектом, определяющим элементы DOM, которые могут включать свойство длины, предназначенное для указания длины отображения символов в пользовательском интерфейсе. Значение 200 здесь катастрофично: внезапно каждый() повторяет 0-199 по значению реквизита.

Прежде чем я буду инвестировать в какой-либо дальнейший рефакторинг, кто-нибудь наткнулся на решение этой проблемы?


person Christopher    schedule 09.09.2011    source источник


Ответы (2)


В документации jQuery для jQuery.each() четко указано, что если объект имеет свойство .length, то он выполняет итерацию по числовому индексу от 0 до длины-1 (как и следовало ожидать для массива или объекта, подобного массиву).

Если у вас есть объект со свойством .length и вещи, которые вы хотите перебрать, не являются числовыми индексами от 0 до length-1, тогда jQuery.each() не будет делать то, что вы хотите, и вам не следует его использовать.

Вот конкретный код для jQuery.each() из источника jQuery. Вы можете видеть во 2-й и 3-й строках функции, что если есть object.length, isObj будет false, и позже он не будет обрабатывать его как объект:

// args is for internal usage only
each: function( object, callback, args ) {
    var name, i = 0,
        length = object.length,
        isObj = length === undefined || jQuery.isFunction( object );

    if ( args ) {
        if ( isObj ) {
            for ( name in object ) {
                if ( callback.apply( object[ name ], args ) === false ) {
                    break;
                }
            }
        } else {
            for ( ; i < length; ) {
                if ( callback.apply( object[ i++ ], args ) === false ) {
                    break;
                }
            }
        }

    // A special, fast, case for the most common use of each
    } else {
        if ( isObj ) {
            for ( name in object ) {
                if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
                    break;
                }
            }
        } else {
            for ( ; i < length; ) {
                if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) {
                    break;
                }
            }
        }
    }

    return object;
},
person jfriend00    schedule 09.09.2011
comment
Я хотел бы не согласиться с тем, что документация ясна: «Массивы и массивоподобные объекты со свойством длины (например, объект аргументов функции)» не совсем передает всю степень импорта этого выбора дизайна (на jQuery часть). Это (для меня) указывает на некоторую проверку прототипа в дополнение к hasOwnProperty('length'). Но ты прав. Вот оно. Тем не менее, я имею дело со сложными, произвольными объектами. Только небольшая часть может иметь свойство «длина» — я не против добавить больше условной логики для обработки пограничного случая, мне просто интересно, есть ли другой способ. - person Christopher; 09.09.2011
comment
Вы всегда можете посмотреть код jQuery для jQuery.each(). Соответствующая строка такова: length = object.length, isObj = length === undefined || jQuery.isFunction( object );. Вы можете ясно видеть, что он будет рассматривать его как объект, только если нет .length. - person jfriend00; 09.09.2011
comment
вот оно. Что ж, это то, что есть. Это ответ на вопрос, спасибо. - person Christopher; 09.09.2011

я думаю, что if(thisObj.hasOwnProperty("length")) может сделать это за вас

person dstarh    schedule 09.09.2011
comment
Традиционно да - это правильный способ повторения циклов. Но в этом случае проблема заключается в jQuery: в $.each({ name: 'My Thrifty Prop', length: 200}, function(key, value) { alert(key); } ); вы получите серию предупреждений 0-199. Оценка для $.isArray() и hasOwnProperty('length') означает обход $.each(), но я не думаю, что это решает проблему. Конечно, это не может быть решено с помощью $.each() - мне просто любопытно, есть ли способ заставить это работать. - person Christopher; 09.09.2011