Особенность циклов for в JavaScript

Недавно столкнулся с небольшой неприятностью, а именно: по разному отрабатывал код с for…in и обычным циклом for.

Передо мной стояла задача отрисовать график на canvas’е, данные которого хранились в JSON объекте. Для перебора всех свойств из объекта используется цикл по свойствам for..in. Так вот, цикл в разных вариациях вел себя по-разному. Чтобы разобраться в проблеме, я покажу как работает код в каждой вариации.

В качестве данных у нас будет вот такой небольшой объект, который представляет собой результат работы php-функции json_encode, которая была использована чтобы конвертировать в JSON выборку из БД. Реальная ситуация практически на любом проекте.


var data = {
    0: { 
        x: 5,
        y: 6
    },
    1: { 
        x: 10,
        y: 4
    },
    3: { 
        x: 15,
        y: 8
    }
};

Данный код должен отрисовать на canvas’е порядковый номер каждого элемента объекта увеличенной на 1 (т.е вместо 0, 1, 2 — 1, 2, 3) оранжевым цветом, шрифт Arial высотой 10 точек, в координатах, заданных в самом объекте.
for..in


var canvas = document.getElementById('chart_canvas');
var context = canvas.getContext('2d');
for (var i in data) {
    context.font = '10pt Arial';
    context.fillStyle = '#ff9900';
    context.fillText((i+1), data[i].x, data[i].y);
}

for


var canvas = document.getElementById('chart_canvas');
var context = canvas.getContext('2d');
for (var i = 0; i < data.length; i++) {
    context.font = '10pt Arial';
    context.fillStyle = '#ff9900';
    context.fillText((i+1), data[i].x, data[i].y);
}

Результат

А что же выйдет на самом деле: первый вариант отобразит вместо ожидаемых 1, 2, 3 - 01, 11, 21. Не верите? Проверьте сами!
Почему? Да потому, что имя свойства обязано быть строкой. Если использовано значение другого типа — JavaScript приведет его к строке автоматически. Когда мы конвертировали массив в JSON, а после передали его в JavaScript, мы и представить не могли, что js посмеет не спросив нас выполнить преобразование типов, и наш int благополучно станет string.
for..in:

console.log(typeof i); // string

for:

console.log(typeof i); // number

Это в данном примере ошибка легко выявилась. Я же, должен признаться, поломал голову, почему у меня циклы по-разному отрабатывают. Тем более, что

console.log(i);

Выдавал одинаковые значения.
Чтобы все было как нам нужно, необходимо выполнить приведение типов:


for (var i in data) {
    console.log(typeof i); // string
    i = +i;
    console.log(typeof i); // number
    context.font = '10pt Arial';
    context.fillStyle = '#ff9900';
    context.fillText((i+1), data[i].x, data[i].y);
}

А вот теперь, все отработает как положено.
Будьте внимательны и не забывайте про типы, и про то, что имя свойства объекта является значение типа string.

Stas Kuryan

Web developer. Перфекционист в написании кода.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *