10 лучших функций на JavaScript

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

UPDATE март 2010: Эта статья была обновлена и переписана, чтобы соответствовать сегодняшнему дню и общему уровню сайта.

Эти функции неоднократно были испытаны и доказали свою полезность всем тем, кто их использовал. Поэтому, без лишних вступлений, вот список из десяти, по моему мнению, величайших пользовательских функций на JavaScript, которые используются в настоящее время.

Современные яваскрипт-фреймворки, конечно же, умеют все эти функции. Но иногда нужно сделать что-то без фреймворка. По разным причинам. Для этого и предназначен данный сборник полезных функций

10) addEvent()

Несомненно, важнейший инструмент в управлении событиями! Вне зависимости от того, какой версией вы пользуетесь и кем она написана, она делает то, что написано у неё в названии: присоединяет к элементу обработчик события.

Простой вариант функции addEvent()

function addEvent(elem, evType, fn) {
	if (elem.addEventListener) {
		elem.addEventListener(evType, fn, false);
	}
	else if (elem.attachEvent) {
		elem.attachEvent('on' + evType, fn)
	}
	else {
		elem['on' + evType] = fn
	}
}

Этот код обладает двумя достоинствами - он простой и кросс-браузерный.

Основной его недостаток - в том, он не передает this в обработчик для IE. Точнее, этого не делает attachEvent.

Простой обход проблемы this

Для передачи правильного this можно заменить соответствующую строку addEvent на:

elem.attachEvent("on"+evType, function() { fn.apply(elem) })

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

Существует два варианта обхода проблемы:

  1. Возвращать функцию, использованную для назначения обработчика:
    function addEvent(elem, evType, fn) {
    	if (elem.addEventListener) {
    		elem.addEventListener(evType, fn, false)
                    return fn
    	}
    
            iefn = function() { fn.call(elem) } 
            elem.attachEvent('on' + evType, iefn)
    	return iefn
    }
    
    function removeEvent(elem, evType, fn) {
    	if (elem.addEventListener) {
    		elem.removeEventListener(evType, fn, false)
                    return
    	}
     
            elem.detachEvent('on' + evType, fn)
    }
    

    Используется так:

    function handler() { 
        alert(this) 
    }
    var fn = addEvent(elem, "click", handler)
    ...
    removeEvent(elem, "click", fn)
    
  2. Можно не использовать this в обработчике события вообще, а передавать элемент через замыкание:

    function handler() { 
       // используем не this, а переменную, ссылающуюся на элемент
        alert(*!*elem*/!*) 
    }
    ...
    

В качестве альтернативы и для примера более серьезной библиотеки обработки событий вы можете рассмотреть статью Кросс-браузерное добавление и обработка событий.

9) onReady()

Для инициализации страницы исторически использовалось событие window.onload, которое срабатывает после полной загрузки страницы и всех объектов на ней: счетчиков, картинок и т.п.

Событие onDOMContentLoaded - гораздо лучший выбор в 99% случаев. Это событие срабатывает, как только готов DOM документ, до загрузки картинок и других не влияющих на структуру документа объектов.

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

Для добавления обработчика можно использовать следующий кроссбраузерный код:

function bindReady(handler){

	var called = false

	function ready() { // (1)
		if (called) return
		called = true
		handler()
	}

	if ( document.addEventListener ) { // (2)
		document.addEventListener( "DOMContentLoaded", function(){
			ready()
		}, false )
	} else if ( document.attachEvent ) {  // (3)

		// (3.1)
		if ( document.documentElement.doScroll && window == window.top ) {
			function tryScroll(){
				if (called) return
				if (!document.body) return
				try {
					document.documentElement.doScroll("left")
					ready()
				} catch(e) {
					setTimeout(tryScroll, 0)
				}
			}
			tryScroll()
		}

		// (3.2)
		document.attachEvent("onreadystatechange", function(){

			if ( document.readyState === "complete" ) {
				ready()
			}
		})
	}

	// (4)
    if (window.addEventListener)
        window.addEventListener('load', ready, false)
    else if (window.attachEvent)
        window.attachEvent('onload', ready)
    /*  else  // (4.1)
        window.onload=ready
	*/
}
readyList = []

function onReady(handler) {

	if (!readyList.length) {
		bindReady(function() {
			for(var i=0; i<readyList.length; i++) {
				readyList[i]()
			}
		})
	}

	readyList.push(handler)
}

Использование:

onReady(function() {
  // ... 
})

Подробное описание функций bindReady, onReady и принципы их работы вы можете почерпнуть в статье Кроссбраузерное событие onDOMContentLoaded.

8) getElementsByClass()

Изначально не написана никем конкретно. Многие разработчики писали свои собственные версии и ничья не показала себя лучше остальных.

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

if(document.getElementsByClassName) {

	getElementsByClass = function(classList, node) {    
		return (node || document).getElementsByClassName(classList)
	}

} else {

	getElementsByClass = function(classList, node) {			
		var node = node || document,
		list = node.getElementsByTagName('*'), 
		length = list.length,  
		classArray = classList.split(/\s+/), 
		classes = classArray.length, 
		result = [], i,j
		for(i = 0; i < length; i++) {
			for(j = 0; j < classes; j++)  {
				if(list[i].className.search('\\b' + classArray[j] + '\\b') != -1) {
					result.push(list[i])
					break
				}
			}
		}
	
		return result
	}
}
classList
Список классов, разделенный пробелами, элементы с которыми нужно искать.
node
Контекст поиска, внутри какого узла искать

Например:

var div = document.getElementById("mydiv")
elements = getElementsByClass('class1 class2', div)

7) addClass() / removeClass()

Следующие две функции добавляют и удаляют класс DOM элемента.

function addClass(o, c){
    var re = new RegExp("(^|\\s)" + c + "(\\s|$)", "g")
    if (re.test(o.className)) return
    o.className = (o.className + " " + c).replace(/\s+/g, " ").replace(/(^ | $)/g, "")
}
 
function removeClass(o, c){
    var re = new RegExp("(^|\\s)" + c + "(\\s|$)", "g")
    o.className = o.className.replace(re, "$1").replace(/\s+/g, " ").replace(/(^ | $)/g, "")
}

6) toggle()

Если быть честным, наверное для этой функции существует больше различных вариантов, чем было бы нужно.

Этот вариант никоим образом он не претендует на звание универсальной функции-"переключателя", но он выполняет основную функциональность показывания и спрятывания.

функция toggle(), слова народные

function toggle(el) {
    el.style.display = (el.style.display == 'none') ? '' : 'none'
}

Обратите внимание, в функции нет ни слова про display='block', вместо этого используется пустое значение display=''. Пустое значение означает сброс свойства, т.е. свойство возвращается к значению, указанному в CSS.

Таким образом, если значение display для данного элемента, взятое из CSS - none (элемент спрятан по умолчанию), то эта функция toggle не будет работать.

Этот вариант функции toggle красив и прост, однако этот и некоторые другие недостатки делают его недостаточно универсальным. Более правильный вариант toggle, а также функции show и hide описаны в статье Правильные show/hide/toggle.

5) insertAfter()

Как и getElementsByClass, этой функции почему-то нет в стандарте DOM. Возможно, чтобы избежать дублирования функционала, т.к. insertAfter реализуется всего одной строчкой.

function insertAfter(parent, node, referenceNode) {
	parent.insertBefore(node, referenceNode.nextSibling);
}

4) inArray()

Очень жаль, что это не часть встроенной функциональности DOM. Зато теперь у нас есть возможность всё время вставлять такие вот замечания!

Для поиска эта функция использует проверку ===, которая осуществляет поиск по точному сравнению, без приведения типов.

Метод Array.prototype.indexOf поддерживается не во всех браузерах, поэтому используется, если существует.

inArray = Array.prototype.indexOf ?
    function (arr, val) {
        return arr.indexOf(val) != -1
    } :
    function (arr, val) {
        var i = arr.length
        while (i--) {
            if (arr[i] === val) return true
        }
        return false
    }

3, 2 и 1) getCookie(), setCookie(), deleteCookie()

В javascript нет способа нормально работать с cookie без дополнительных функций. Не знаю, кто проектировал document.cookie, но сделано на редкость убого.

Поэтому следующие функции или их аналоги просто необходимы.

// возвращает cookie если есть или undefined
function getCookie(name) {
	var matches = document.cookie.match(new RegExp(
	  "(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)"
	))
	return matches ? decodeURIComponent(matches[1]) : undefined 
}

// уcтанавливает cookie
function setCookie(name, value, props) {
	props = props || {}
	var exp = props.expires
	if (typeof exp == "number" && exp) {
		var d = new Date()
		d.setTime(d.getTime() + exp*1000)
		exp = props.expires = d
	}
	if(exp && exp.toUTCString) { props.expires = exp.toUTCString() }

	value = encodeURIComponent(value)
	var updatedCookie = name + "=" + value
	for(var propName in props){
		updatedCookie += "; " + propName
		var propValue = props[propName]
		if(propValue !== true){ updatedCookie += "=" + propValue }
	}
	document.cookie = updatedCookie

}

// удаляет cookie
function deleteCookie(name) {
	setCookie(name, null, { expires: -1 })
}

Аргументы:

name
название cookie
value
значение cookie (строка)
props
Объект с дополнительными свойствами для установки cookie:

expires
Время истечения cookie. Интерпретируется по-разному, в зависимости от типа:

  • Если число - количество секунд до истечения.
  • Если объект типа Date - точная дата истечения.
  • Если expires в прошлом, то cookie будет удалено.
  • Если expires отсутствует или равно 0, то cookie будет установлено как сессионное и исчезнет при закрытии браузера.
path
Путь для cookie.
domain
Домен для cookie.
secure
Пересылать cookie только по защищенному соединению.

Последняя, но зачастую полезная: функция byId

Она позволяет функции работать одинаково при передаче DOM-узла или его id.

function byId(node) {
        return typeof node == 'string' ? document.getElementById(node) : node
}

Используется просто:

function hide(node) {
    node = byId(node)
    node.style.display = 'none'
}

function animateHide(node)
   node = byId(node)
   something(node)
   hide(node)
}

Здесь обе функции полиморфны, допускают и узел и его id, что довольно удобно, т.к. позволяет не делать лишних преобразований node <-> id.


Надеюсь, этот небольшой и удобный список JavaScript-функций будет столь же полезен вам, сколь он полезен мне.