Яндекс.Метрика

    Javascript

    JavaScript как язык с утиной типизацией

    JavaScript это мощный язык с динамической типизацией и это позволяет писать код с утиной типизацией. В двух словах утиная типизация позволяет выполнять функцию для обьектов которые обладают нужными нам свойствами (т.к. js функицональный язык то и метод является полноправным свойством). Я уверен что многие используют утиную типизацию, но я думаю данная заметка будет все равно интересна некоторому подмножеству множества аудитории хабра. В этой заметке мы проследим эволюцию функции range из работающей только с числами до функции которая работает с любыми обьектами.


    Итак, давайте предположим что мы пишем функцию range, думаю не стоит расписывать что она делает. Вероятнее всего вы уже встречались с этой функцией, и выглядела она примерно так:
    function range (a, b) {
    	var list = [];
    	
    	do {
    		list.push(a);
    	} while (++a < b)
    	
    	return list;
    }
    

    Окей, функция с блеском возвращает массив в нужном нам интервале:
    range(0, 4); //=> [0, 1, 2, 3]
    

    Но давайте попробуем передать функции не число.
    range('a', 'f'); //=> ["a"]
    

    В других реализациях ответ мог содержать список из NaN, но это не суть важно.

    Текущяя реализация range плоха тем что она работает только с числами. Давайте будем использовать числа не как числа, а как элемент перечисляемой последовательности, для этого добавим метод succ в прототип числа:
    Number.prototype.succ = function () {
    	return this + 1;
    };
    

    Теперь перепишем range:
    function range (a, b) {
    	var list = [];
    	
    	do {
    		list.push(a);
    		a = a.succ();
    	} while (a < b)
    	
    	return list;
    }
    

    Окей, функция отрабатывает как и раньше, и также не поддерживает работу со строками, давайте допишем метод succ для строк.
    String.prototype.succ = function () {
    	return this.slice(0, this.length - 1) +
    		String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
    }
    

    Окей, метод работает (хотя и не достаочно хорошо, но это сейчас не важно), но мы все равно не можем получить последовательности для строк, давайте немного изменим нашу реализуцию range:
    function range (a, b) {
    	var list = [];
    	
    	do {
    		list.push(a);
    		a = a.succ();
    	} while (a !== b)
    	
    	return list;
    }
    

    Хорошо, теперь range работает и для строк:
    range('a', 'f'); //=> ['a', 'b', 'c', 'd', 'e']
    


    Но у нас все еще есть проблемы, давайте попробуем написать простой класс с методом succ:
    function Digit (d) {
    	this.digit = d % 10;
    }
    
    Digit.prototype = {
    	succ : function () {
    		return new Digit(this.digit + 1);
    	}
    };
    

    Теперь если мы попробуем запустить Range для двух экземпляров класса Digit то у нас ничего не выйдет, и причина в том что foo === bar только тогда когда foo и bar есть ссылки на один и тот же обьект, и даже если foo и bar обладают одинаковыми свойствами они все равно не равны, и для этого есть причины. Так что давайте перепишем наш range немного подругому:
    function range (a, b) {
    	var list = [];
    	
    	do {
    		list.push(a);
    		a = a.succ();
    	} while (a.equalTo !== undefined ? a.equalTo(b) : a !== b)
    	
    	return list;
    }
    

    А также допишем метод equalTo для класса Digit.
    equalTo : function (a) {
    	return this.digit === a.digit;
    }
    

    И давайте протестируем:
    range(new Digit(3), new Digit(8)); //=> [{digit:3}, {digit:4}, {digit:5}, {digit:6}, {digit:7}]
    


    Теперь мы можем смело сказать что наша реализация функции range, будет работать с любым обьектом который удовлетворяет её потребностям.

    Хочу заметить что в данной заметке я расширял прототипы, с одной стороны это плохо, но с другой стороны это просто, но давайте расматривать это как генную инженерию, вы можете этим пользоваться, а можете и не пользоваться ;)