Объявленная через устаревший 
var переменная 
i всплывает наверх, видна отовсюду и 
поёт похабные частушки сохраняет видимое для всех созданных функций единое значение 
3 по окончании цикла. Это не то, что задумано.
Использование спортивных, современных 
const и 
let чудесным образом исправляет досадное недоразумение:
const arr = []; // ведь сам массив не планируется заменять
for (let i = 0; i <= 2; i++) { // главное: i теперь видна только внутри цикла
   arr[i] = function () {
      console.log(i);
   };
}
arr[0](); // 0
arr[arr.length - 1](); // 2
в исходном коде только лишь заменили 
var на 
const (не так важно) и 
let (вот это изменило всё!).
Можно использовать и стрелочную функцию: 
const arr = [];
for (let i = 0; i <= 2; i++) {
   arr[i] = () => console.log(i);
}