Pull to refresh

Дифференцирование Object, Array, Number и String

Reading time2 min
Views9.5K
Начну с того, что метод jQuery.each я всегда использую только для работы с jQuery object. В остальном — циклы for / while или свой аналог «each». Вроде это и логично, но в документации говорится о том, что вышеупомянутый метод можно использовать для любых коллекций.
Разрушим этот миф.

Логика jQuery


Давайте рассмотрим логику метода jQuery.each. На входе проверяется тип первого параметра, точнее, объект это или нет:
length = obj.length,
isObj = length === undefined || jQuery.isFunction( obj );

Получается, если у него нет свойства length, то это Object. А если length есть — то это Array.
Не очень универсальный подход. Давайте проверим.

Создадим объект «Obj» с несколькими свойствами, в число которых запишем и length.
var Obj = { name: "test", size: 2, length: 5 };

А теперь отдадим его jQuery.each:
$.each( Obj, function() { console.log( this, arguments ); } );

Результат выполнения:
Window [0, undefined]
Window [1, undefined]
Window [2, undefined]
Window [3, undefined]
Window [4, undefined]

Провал. jQuery определил его как массив, и использовал фиктивное свойство length для цикла for,
Но как же обойти эту проблему? У меня было несколько идей. Сейчас мы с вами постепенно по ним пройдемся, и дойдем до самой подходящей и универсальной реализации.

Способ первый: typeof


Этот способ нам не подходит, т.к. и typeof и объекта и массива — «object». Более того, если нам нужно различать еще и строки и числа, то мы снова получим «object» в случае их создания с помощью оператора new.

Способ второй: instanceof


Вот тут я ошибался. Думал, дело в шляпе, подходящий способ найден.
Но избежать очередных подводных камней не удалось:
var a = {};
a instanceof Object;
 => true

var b = [];
b instanceof Array;
 => true

var c = 5;
c instanceof Number;
 => false

var d = "";
d instanceof String;
 => false

С объектом и массивом проблем нет. А вот с числами и строками instanceof корректно работает только при условии их создания через оператор new. Идем дальше.

Способ третий: constructor


Вот он, работающий способ. Сравнение конструкторов.
var a = {};
a.constructor === Object;
 => true

var b = [];
b.constructor === Array;
 => true

var c = 5;
c.constructor === Number;
 => true

var d = "";
d.constructor === String;
 => true

Для полной картины, таблица сравнения результатов:
Способ typeof instanceof constructor
new Array() «object» true true
[] «object» true true
new Object() «object» true true
{} «object» true true
new String() «object» true true
"" «string» false true
new Number() «object» true true
5 «number» false true

Вывод:


Возможно, метод each должен выглядеть примерно так (кусок с args я не стал брать для демонстрации):
each: function( obj, callback, args ) {
		var name,
			i = 0,
			length = obj.length,
			isObj = obj.constructor === Object,
			isArray = obj.constructor === Array || obj.constructor === String;

		if ( isObj ) {
			for ( name in obj ) {
				if ( callback.call( obj[ name ], name, obj[ name ] ) === false ) {
					break;
				}
			}
		} else if ( isArray ) {
			for ( ; i < length; ) {
				if ( callback.call( obj[ i ], i, obj[ i++ ] ) === false ) {
					break;
				}
			}
		}
}

Всем спасибо за внимание.

upd.
Спасибо за полезные комментарии. Цель статьи, в первую очередь, показать способ дифференцирования типов данных в JavaScript, а не выявить баг в jQuery.
Соглашусь, что способ с использованием «toString» более подходящий. В скором времени внесу правки в статью. Еще раз спасибо за отклики. Для меня эта тема интересна, соответственно я тоже хочу разобраться, как лучше.
Tags:
Hubs:
Total votes 25: ↑20 and ↓5+15
Comments21

Articles