Later Ctrl + ↑

Генератор обработчиков массивов

Пусть есть функция, которая знает как обрабатывать объект. Генератор вернет такую функцию, которая сможет обрабатывать списки таких объектов. Возьмем для примера функцию hide, которая должна скрывать DOM узлы. 
function add_class (node, class_name) {
    var node_classes;

    node_classes = node.className;
    node.className = (node_classes ? node_classes + ' ' : '') + class_name;
}


function remove_class (node, class_name) {
	node.className = node.className.replace(new RegExp(' *' + class_name + ' *', 'g'), '');
}
И превратим их в обработчкики списков с помощью функции ниже: 
function make_iterable (func) {
	var arr_proto;

	// храним ссылку на прототип массива
	arr_proto = Array.prototype;
	return function (el) {
		var res,
			args,
			i;

		// сюда будут складываться результаты множественных вызовов
		res = [];

		// структура типа [undefined, все_кроме_первого_аргумента]
		args = [undefined].concat(arr_proto.slice.call(arguments, 1));

		// проверка на «массивность». Специально сделана, чтобы корректно
		// обработала коллекции типа document.links, document.images
		if ('length' in el) {
			for (i = 0; i < el.length; i += 1) {
				// подменяем в структуре первый элемент на элемент массива
				// [элемент_массива, все_кроме_первого_аргумента]
				args.splice(0,1,el[i]);

				// запоминаем результат вызова обработчика
				// с этим набором аргументов
				res[i] = func.apply(this, args);
			}
		} else {
			// если передался не массив аргументов, вызываем
			// обработчик с указанными аргументами
			res.push(func.apply(this, arguments));
		}

		// массив результатов вызовов обработчика
		return res;
	};
}
Теперь можно подняться на уровень выше, и мыслить категориями коллекций, а не отдельных нод.
var add_class_to_multiple = make_iterable(add_class);

function hide (selector) {
    add_class_to_multiple(document.querySelectorAll(selector), 'hidden');
}

Пример построения наследования на прототипах в javascript

В предыдущей статье я описал механизмы наследования, предоставляемые языком. В этой опишу практическое их применение для решение боевой задачи. А вот и она: описать новый класс, который наследует от стандартного Array.

Мы создаем новый класс, а не дописываем необходимые методы в прототип, так как расширение стандартных прототипов допускается в случаях, когда интерпретатор не поддерживает функции нового стандарта. Например Array.prototype.indexOf, Object.create. Если же прототипы стандартных объектов расширяются с другой целью, это может привести к поломке используемых библиотек или недоумению у человека, читающего ваш код.

В общем виде задача сводится к созданию конструктора, прототип которого содержит в __proto__ ссылку на Array.prototype. Обратите внимание, что создаваемых нами объект, хоть и выглядит как массив в консоли браузера, но таковым не является. Это именно объект с набором свойств, которые обновляются методами массива.

Собственно сам закомментариеный пример. 
/*
 Возвращает пустой объект, у которого
 __proto__ ссылается на obj

 Тоже самое делает метод Object.create
 из новой редакции стандарта ECMA. Вместо
 того, чтобы писать такую реализацию, можно
 взять готовую и дополнить ею стандартный Object
*/
function create_object (obj) {
	var F = function () {};
	F.prototype = obj;
	return new F();
}

/*
 Функция, которая будет использоваться как конструктор экземпляров класса, наследующих
 от стандартного Array
*/
function SuperArray () {}

/*
 Экземпляры SuperArray получают в __proto__
 ссылку на Array.prototype. Следовательно,
 наследуют от Array.prototype
*/
SuperArray.prototype = create_object(Array.prototype);
/*
 Так как конструктором Array.prototype является
 Array, подменяем свойство конструктор на тот,
 который будет являться конструктором де-факто.

 Если бы мы этого не сделали, то равеснтво
 s.constructor === SuperArray; // false
 было бы неверным
*/
SuperArray.prototype.constructor = SuperArray;

/*
 Создаем методы в прототипе. Они будут доступны
 для каждого экземпляра класса SuperArray
*/

/*
 Принимает аргументом массив или массивоподобный
 объект, элементы массива будут являться ключами
 возвращаемого объекта. Значениями — значения
 экземпляра класса SuperArray
*/
SuperArray.prototype.to_object = function (keys) {
	var res, i;

	for (i = 0; i < keys.length; i += 1) {
		res[ keys[i] ] = this[i];
	}
	return res;
};

/*
 Принимает аргументом функцию. Если для всех элементов экземпляра SuperArray она возвращает
 true, то методы возвращает true.
*/
SuperArray.prototype.all = function (test) {
	var i;

	for (i = 0; i < this.length; i += 1) {
		if (!test(this[i])) {
			return false;
		}
	}
	return true;
};

/*
 Принимает аргументом функцию. Если для одного
 элемента экземпляра SuperArray она возвращает
 true, то метод вернет true
*/
SuperArray.prototype.any = function (test) {
	var i;

	for (i = 0; i < this.length; i += 1) {
		if (test(this[i])) {
			return true;
		}
	}
	return false;
};

var s = new SuperArray();
/*
 Метод push берется из цепочки прототипов.
 Он обновляет значение объекта а так-же его
 свойство length
*/
s.push(5);
s.push(3);
s.push(8);
s.push(4);
s.push(1);
s.push(7);
s.push('foo');

s.all(function (el) {
	return typeof el === 'number';
}); // false

s.any(function (el) {
	return typeof el === 'string';
}); // true

s.any(function (el) {
	return typeof el === 'number';
}); // true

/*
 ВАЖНО:
 созданный объект не является масивом,
 а только наследует его методы. Это
 значит, что для него не сработают
 конструкции, которые бы сработали для
 массива
*/
s.length === 7; // true
/*
 После такого присвоения, длина массива
 изменяется. В нашем случае она остается
 неизменной.
*/
s[99] = true;
s.length === 100; // false
s[99] === true; // true
 

Cнова декоратор

Под впечатлением от этой и этой статей обновил декоратор.

Раньше он позволял только выполнить функцию перед или после другой функцией. Новая, расширенная версия, хоть и наложила ограничения на функции-обертки, имеет больше возможностей:
  1. подмена передаваемых аргументов;
  2. отмена выполнения;
  3. подмена возвращаемого значения.
Сам код, собственно (на github):
function decorate (initial, decorate_before, decorate_after) {
	return function () {
		var initial_call_result,
			updated_params,
			updated_result;

		if (typeof decorate_before === 'function') {
			updated_params = decorate_before.apply(this, arguments);
			if (!updated_params) {
				return;
			}
		}
		initial_call_result = initial.apply(this, updated_params || arguments);
		if (typeof decorate_after === 'function') {
			updated_result = decorate_after.apply(this, arguments);
		}
		return updated_result || initial_call_result;
	};
}
Из изменений: результат первой функции оберки используется как аргументы для вызова оригинальной функции.

Значит можно явно создавать свои варианты биндинга для функций. Например, чтобы при работе с jquery не указывать каждый раз контекст, можно создать обертку, в которой контекст явно задан:
var $iframe;
var iframe_context = $('iframe').eq(0);

$iframe = decorate($, function (selector, context) {
	return [selector, iframe_context];
});

// вместо явного указания:
var images = $('img', iframe_context);

// используем обертку
var images = $iframe('img');
Когда первая обертка не возвращает массив, считается, что оригинальную функцию не надо вызывать. Такой подход можно применять, когда надо подправить поведение плагина, но не хочется влезать в тонкости его реализации. При обновлении плагина не надо будет снова тормошить его внутренности в поисках изменений, так как код реализации и код допиливания будет разделен.

К тому-же некоторые баннерные сети до сих пор используют document.write для вставки ского кода. Если появляется желание обхитрить сеть и вставить их код асинхронно, то реализовать желание можно с помощью переписывания document.write. 
// отмена поведения метода плагина
plugin.method = decorate(plugin.method, function () {
	if (!ok_flag) {
		return;
	} else {
		return arguments;
	}
});

// вставка кода рекламного блока в обход document.write
document.write = decorate(document.write, function () {
	if (is_advert(arguments)) {
		on_advert_embeding.apply(this, arguments);
	} else {
		return arguments;
	}
});
Результат работы удобно подменять удобно при отладке кода. Когда часть системы уже готова, и известно как будет работать работать вторая часть, можно эмулировать ее поведение, подменой результатов ответа. Или же для юнит-тестирования частей системы.
get_products = decorate(get_products, function () {
	if (debug) {
		return;
	}
}, function () {
	if (debug) {
		return ['1111', '2222', '3333', '4444'];
	}
});

Прототипное наследование

В форме таких заключений прототипное наследование лежит у меня в голове. Примеры, подкрепляющие положения, смотреть в фаерфоксе или хроме, ибо __proto__ не является частью стандарта. Разработчики хрома и фаерфокса позволили напрямую обращение к этому свойству.

1. Каждый объект имеет скрытое* свойство __proto__, которое ссылается на некий другой объект (исключение — объект Object.prototype, у которого __proto__ ссылается на null). Это и называется цепочкой прототипов.
var arr = [],
    obj = {};
typeof arr.__proto__ === 'object'; // true
typeof obj.__proto__ === 'object'; // true
arr.__proto__ === Array.prototype // true. Экземпляр класса "Массив" наследует от Array.prototype
arr.__proto__.__proto__ === Object.prototype // true он-же наследует от Object.prototype
arr.__proto__.__proto__.__proto__ // null Последнее звено цепи прототипов
2. При чтении свойства в объекте, оно ищется непосредственно в объекте. Если в объекте не находится, то интерпретатор ищет свойство в __proto__. Поиск останавалиается при первом нахождении или когда ссылка __proto__ ведет на null;
function F() {}
F.prototype.state = false; // состояние по умолчанию
F.prototype.state_on = function () {
	this.state = true; // запись свойства в эжкземпляр класса
}
F.prototype.reset_state = function () {
	delete this.state; // удаление свойства из экземпляра класса
}

var f = new F;
f.state;         // false
f.state_on();    // добавили свойство в экземпляр класса
f.state;         // true
f.reset_state(); // удаление свойства из объекта
f.state;         // false (значение взялось из прототипа)
f.test;          // undefined (свойства нет в объекте и цепочке прототипов)
3. __proto__ устанавливает в момент создания объекта, и ссылается туда-же, куда и Конструктор_объекта.prototype;

4. Каждая функция при объявлении получает свойство prototype.
function F () {};
typeof F.prototype === 'object'; // true
var f = new F;
f.__proto__ === F.prototype; // true

Вызов. Апдейт массива

В комментариях — все возможные варианты добавления элемента в начало и конец массива.

// в начало
var arr;
var temp = [insert];
while (arr.length) {
	temp.push(arr.shift());
}
arr = temp;

arr = arr.concat([insert]); // в конец
arr = arr.slice(1); // с начала
arr = arr.slice(0, arr.length - 1); // с конца

Skripatch

a.push(element)
a.unshift(element)
a.splice(0, 0, element)
a.splice(a.length, 0, element);

Илья Панасенко

a[a.length] = element; // в конец

Neklesa

a = a.concat([element]); // в конец
a.reverse().push(element).reverse(); // в начало

Как дождаться выполнения асинхронных функций

Состояние, когда такое надобится попахивает промахом в проектировании (или специфическими задачами). Однако проблема есть и требует решения.

Идея

Создать обертки для асинхронных функций. Эти обертки будут проверять состояние выполнения других оберток. Когда желаемое состояние достигается, вызывается главный коллбек.

Реализация

Обертки создаются с помощью декорирования коллбеков. Ключом к набору асинхронных функций и их состояний будет функция коллбек (реализуется через два массива). Смотреть можно на гитхабе или ниже, но с комментариями.
function decorate (initial, decorate_before, decorate_after) {
	return function () {
		var initial_call_result;

		if (typeof decorate_before === 'function') {
			if (!!decorate_before.apply(this, arguments) === false) {
				return;
			}
		}
		initial_call_result = initial.apply(this, arguments);
		if (typeof decorate_after === 'function') {
			decorate_after.apply(this, arguments);
		}
		return initial_call_result;
	};
}


function chain (func, callback) {
	var callbacks,
		func_states;

	// Массив для коллбеков. Индекс коллбека соответствует
	// индексу набора состояний
	callbacks = [];
	// Состояния асинхронных функций
	func_states = [];
	chain = function (func, callback) {
		var callback_index,
			chained,
			func_index;

		// получаем объект состояний
		callback_index = callbacks.indexOf(callback);
		if (callback_index === -1) {
			// если его нет, создаем новый
			chained = {
				func : [],
				state : [],
				// если все функции выполнились, запускаем коллбек
				test : function () {
					for (var i = 0; i < this.func.length; i += 1) {
						if (!this.state[i]) {
							return;
						}
					}
					callback();
					this.state = [];
				}
			};
			callbacks.push(callback);
			func_states.push(chained);
		} else {
			chained = func_states[callback_index];
		}

		// оборачиваем функцию, чтобы она обноавляла состояние
		func_index = chained.func.push(func) - 1;
		func = decorate(func, null, function () {
			chained.state[func_index] = true;
			chained.test();
		});
		return func;
	};
	return chain.apply(this, arguments);
}
И пример использования:
function on_all_async_ready () {
	console.log(‘all ready’);
}

var on_2_of_3_ready = function () {
	console.info(‘1 and 2 ready’);
}

var async_1 = chain(function () {
	console.info(‘first async’);
}, on_all_async_ready);

async_1 = chain(async_1, on_2_of_3_ready);


var async_2 = chain(function () {
	console.info(‘second_async’);
}, on_all_async_ready);

async_2 = chain(async_2, on_2_of_3_ready);


var async_3 = chain(function () {
	console.info(‘third async’);
}, on_all_async_ready);


setTimeout(function () {
	async_1();
	setTimeout(async_2, 1000);
	setTimeout(async_3, 2000);
}, 2000);

С кодом будь строг. Настройка jshint

Сейчас использую, и лучшего не видел jshint. До него я работал с js2-mode для emacs и его более продвинутой версией, с jshint от дядюшки Крокфорда. jshint использую в качестве плагина к sublimetext2. Чтобы начать пользоваться им
  • надо установить node.js
  • через package control установить SublimeLinter
  • покрутить настройки до удовлетворяющего уровня
Кстати, этот инструмент доступен для ряда других редакторов (notepad++, eclipse).
Остановлюсь на настройках и на случаях, которые они покрывают. Мой файл настроек можно взять тут. Что такое .jshintrc можно прочитать в документации

curly : true

Заставляет ставить фигурные скобки даже тогда, когда они не обязательны. С одной стороны улучшается читаемость кода, с другой, при простейшей обфускации код продолжает корректно работать.
if (ready())
	onready();
else
	on_not_ready();
// VS
if (ready()) {
	onready();
} else {
	on_not_ready();
}

eqeqeq : true

Контролирует использование строгого сравнения. Выигрыш получается не только в производительности (он мизерный на самом деле), а в чуть большей прозрачности кода (не надо задумываться о том, какого типа данные хранятся в переменных). Подробнее про автоматическое приведение типов при нестрогом сравнении.
['1'] == 1;     // true
['1'] === 1;    // false
month === '7';  // очевидно, что month содержит строку

forin : true

Ошибочным считает перебор свойств объекта без проверки hasOwnProperty (этого метода нет в ie7). Особенно полезно создателям 3-d party библиотек или встраиваемых скриптов. Подобная проверка ограничит код от удовлетворенных порывов к monkey patching.

immed : true

Обязывает оборачивать моментально вызываемые функции в скобки. Таким образом читающему становится ясно, что фишка в вызове функции, а не в ее определении (при этом ясно как читая с начала, так и с конца определения).

latedef : true

Обращает внимание на вызов функций перед их определением. Хотя это и естественная практика (так как определением функций происходит на этапе чтения кода, и функции уже объявлены на этапе интерпретации), для не знающего тонкостей ecma, подобная запись будет сюрпризом.

newcap : true

Стимулирует использование заглавных первых букв в именах конструкторов.

noarg : true


Негодует при использовании arguments.calle, arguments.calle.caller (см. strict mode).

noempty : true

Не приемлет использование пустых блоков кода. Чаще всего такие конструкции связаны с ошибкой или опиской.
if (document) {} // вероятнее всего описка

plusplus : true

Поощряет использование += и -= вместо ++ и --. Запись чуть длиннее, зато понятно что происходит и в каком порядке.

undef : true

Присвоение неопределенной переменной выдаст ошибку. В нестрогом режиме в таком случае создает глобальная переменная (см. strict mode).

asi : false

Принуждает к расстановке точек с запятой. Стандарт говорит, что точку с запятой ставить не обязательно, интерпретатор самостоятельно их расставит. Правда сделать это он может неоднозначно. Код ниже приведет к ошибке из-за отсутствия точки с запятой после объявления объекта. Подобную неожиданность можно встретить при конкатенации файлов, при длинном объявлении объекта или при большом расстоянии между объявлением объекта и вызовом функции (в виде комментария, например). Одно из решений — всегда ставить точку с запятой перед вызовом анонимной функции.
var obj = {
	prop : 'val'
}

(function () {
	// полезная функция
}()); // ошибка

funcscope : false

Возбраняет использование функций и переменных, объявленных в лексических областях видимости. Для новичка не прозрачно то, что все вар-ы создаются на этапе чтения скрипта. К тому-же на сегодняшний момент код ниже генерирует ошибку в фаерфоксе.
if (true) {
	function a () {}
} else {
	function b () {}
}
b();

Теперь у вас в руках остро наточенный инструмент, который позволит не задумываться над синтаксисом, а сконцентрироваться на логике.
Earlier Ctrl + ↓