Pull to refresh

16 действительно полезных решений для JavaScript

Reading time16 min
Views7.1K
© shamansir.wordpress.com

Представляю вам набор функций, которые у меня лежат в отдельном файле utils.js — это функции, которые я использую чаще всего. Они стараются быть кроссбраузерными и проверены на IE6/7, FF2 и Safari 2 и на боевой, сложной системе, в XHTML документах. Должны, по идее, работать, и на других, но не очень старых версиях браузеров — проверку браузера я использовал только в исключительных случаях. Некоторая часть из них, конечно же, просто нарыта на просторах интернета (где — обычно указано) и заимствована ввиду открытости, а большая часть — сконструирована из многих ресурсов и своих идей (и советов коллег), дабы работать на ура — поскольку часто в разных скриптах не учитываются разные тонкости, которые, тем не менее — при ближайшем рассмотрении — оказываются общностями :), ну и быть довольно читабельными.

Фукнции разделены тематически:
ООПобеспечение (или, вернее сказать — эмуляция) возможности использовать принципы ООП в JavaScript
Объектная модель JSиспользование и расширение встроенных объектов JS
Определение браузерачтобы использовать в тех редких случаях, когда это все-таки неизбежно необходимо
Координаты / Позициионированиевычисление координат и позиционирование объектов — ввиду того, что это часто довольно хитрая штука
DOMработа с объектной моделью документа
AJAXвспомогательные функции для AJAX — так как это средство часто применимо
Логгингиногда без него просто не обойтись

1. Первый блок — набор из трех функций (две из которых пустые), позволяющих применять (эмулировать?) все три принципа ООП в JavaScript. Из нескольких предложенных на AJAXPath и на AJAXPatterns вариантов я выбрал именно этот ввиду его одновременной понятности и быстрой скорости выполнения и немного его видоизменил, так — чтобы отдельно объявленные свойства воспринимались как статические константы.
function Class() { };

Class.prototype.construct = function() { };

Class.extend = function(def) {

  var classDef = function() {
    if (arguments[0] !== Class) {
      this.construct.apply(this, arguments);
    }
  };

  var proto = new this(Class);
  var superClass = this.prototype;

  for (var n in def) {
    var item = def[n];
    if (item instanceof Function) item.$ = superClass;
        else classDef[n] = item;
    proto[n] = item;
  }

  classDef.prototype = proto;
  classDef.extend = this.extend;

  return classDef;
};
* This source code was highlighted with Source Code Highlighter.


2. Следующая функция — простая но изящная — полезна в сочетании с предыдущим набором — она создает функцию-ссылку на метод:
function createMethodReference(object, methodName) {
  return function () {
    return object[methodName].apply(object, arguments);
  };
}
* This source code was highlighted with Source Code Highlighter.


Теперь можно, например, сделать так:
var ScrollingHandler = Class.extend({

  construct:
    function(elementId) {
      this._elementId = elementId;
      this.assignListener();
    },

  assignListener:
    function() {
      var scrollControlElem =
            document.getElementById(this._elementId);
      if (scrollControlElem) {
        scrollControlElem.onscroll =
          createMethodReference(this, "_onElementScroll");
      }
    },

  _onElementScroll:
    function(ev) {
      ev = ev || window.event;
      alert(«please stop scrolling,
            I've already got an event: „
+ ev);
    }
});

var elmScrollHandler = new ScrollHandler('SomeElmId');
* This source code was highlighted with Source Code Highlighter.


Объект этого класса можно будет ассоциировать с событием скроллинга элемента с указанным ID и совершать что-либо по этому случаю.

Объектная модель JS


3. Нижеприведенная функция клонирует любой объект вместе со всеми его свойствами:

function cloneObj(objToClone) {
  var clone = [];
  for (i in objToClone) {
    clone[i] = objToClone[i];
  }
  return clone;
}
* This source code was highlighted with Source Code Highlighter.

Использование — простейшее до невозможности:
var clonedObj = cloneObj(objToClone);* This source code was highlighted with Source Code Highlighter.


4. Конвертер объектов, следующая функция, позволяет удобно использовать всяческие условные (и претендующие ими быть ) конструкции вида
if (tablet.toLowerCase() in oc(['cialis','mevacor','zocor'])) { alert(’I will not!’) };* This source code was highlighted with Source Code Highlighter.

Код заимствован отсюда.

function oc(a) {
  var o = {};
  for(var i=0;i<a.length;i++) {
    o[a[i]]=”;
  }
  return o;
}
* This source code was highlighted with Source Code Highlighter.


Для примера возьмем ситуацию, когда сначала требуется определить, входит ли объект в какое-либо множество одиночных объектов, а затем — не входит ли он в сочетании с другим объектом в другое множество пар объектов. Допустим, на вечеринку пускают одиночек только с определенными именами, либо пары из списка с позволенными сочетаниями имен:
function isPersonAllowed(maleName, femaleName) {
  var pairsAllowed = new Array([ “John», «Yoko» ],
      [ «Bill»«Monica» ], [ «Phil»«Sue» ],
      [ «Jason»«Harrison» ], [ «Adam»«Eve» ]);
  var singlesAllowed = new Array(”Michael”, “Pete”, “John”,
      “Dave”, “Matthew”);
  return (femaleName
      ? ([maleName, femaleName] in oc(pairsAllowed))
      : (maleName in oc(singlesAllowed)));
}

alert(isPersonAllowed(”Jack”)); // false
alert(isPersonAllowed(”Adam”)); // false
alert(isPersonAllowed(”John”)); // true
alert(isPersonAllowed(”Phil”,”Marlo”)); // false
alert(isPersonAllowed(”Jason”,”Harrison”)); // true
alert(isPersonAllowed(”Martin”,”Luther”)); // false
* This source code was highlighted with Source Code Highlighter.


5. Функция, позволяющая создавать хэш сначала кажется немного излишней: объекты в JavaScript — те же хеши, но вот иногда в качестве имени проперти/ключа требуется задать значение значение переменной и тогда приходит на помощь функия Hash. (да-да, конечно же есть встроенные возможности, но так возможно просто немного очевиднее — можете исключить эту функцию из полезных, если хотите

function Hash()
{
  this.length = 0;
  this.items = new Array();
  for (var i = 0; i < arguments.length; i++) {
    this.items[arguments[i][0]] = arguments[i][1];
  }
}
* This source code was highlighted with Source Code Highlighter.


Доступ к элементам производится за счет свойства items (кстати, следует, может, в более тяжелой версии добавить keys):
var Game = Class.extend({
  STG_STOP: 0,
  STG_START: 1,
  STG_LOADING: 2,
  STG_MENU: 3,
  STG_PROCESS: 4,

  construct:
    function() { this._stage = Game.STG_LOADING; },

  getStage:
    function() { return this._stage; }

});

var stateMap = new Hash(
      [ Game.STG_START,  «start»  ],
      [ Game.STG_LOADING, «loading» ],
      [ Game.STG_MENU,  «menu»   ],
      [ Game.STG_PROCESS, «process» ],
      [ Game.STG_STOP,  «stopping» ]);

var someGame = new Game();
alert(”You are in “+stateMap.items[someGame.getStage()]+” stage!”);
* This source code was highlighted with Source Code Highlighter.


6. Три других функции просто упрощают и/или делают очевиднее некоторые операции: getTime на 11 символов сокращает доступ к получению текущего времени, getTimeDelta позволяет найти промежуток в милисекундах между отрезками времени (или указанным моментом и текущим временем, в формате с одним параметром), а последняя функция расширяет свойства объекта Number для того чтобы при его значении NaN можно было чуть быстрее получить 0.
function getTime() {
  return new Date().getTime();
}

function getTimeDelta(timeBegin, timeEnd) {
  timeEnd = timeEnd || getTime();
  return timeEnd — timeBegin;
}

Number.prototype.NaN0=function() { return isNaN(this)? 0: this; }
* This source code was highlighted with Source Code Highlighter.


Определение браузера


7. Небольшой объект, поименованные по названиям браузеров свойства которого — суть условия. Этим достигается более читабельное (но не настолько скурпулезное насколько могло бы быть) определение большинства типов браузеров. Этот объект был заимствован мной из проекта, в котором я учавствовал — и как-то прижился, но, думаю, истинные авторы всё-таки где-то в сети, да и код не так уж сложен и громоздок чтобы на него сильно претендовать :). Кроме того, он конечно не идеально надежен (а некоторые говорят что не надежен вообще), но пока на перечисленных браузерах он меня не подвел ни разу :). Если вас не устраивает такое положение дел — вы можете использовать нечто похожее с HowToCreate. И повторюсь: данное определение я стараюсь использовать (как и сказано, например, по ссылке) “только в случае если известен конкретный баг в конкретном браузере и его нужно обойти”. Также — несложно пересобрать этот объект в одно длинное условие, для меньшей скорости исполнения (см., опять же, ссылку)

var USER_DATA = {
  Browser: {
    KHTML: /Konqueror|KHTML/.test(navigator.userAgent) &&

        !/Apple/.test(navigator.userAgent),
    Safari: /KHTML/.test(navigator.userAgent) &&
        /Apple/.test(navigator.userAgent),
    Opera: !!window.opera,
    MSIE: !!(window.attachEvent && !window.opera),
    Gecko: /Gecko/.test(navigator.userAgent) &&
        !/Konqueror|KHTML/.test(navigator.userAgent)
  },
  OS: {
    Windows: navigator.platform.indexOf(«Win») > -1,
    Mac: navigator.platform.indexOf(«Mac») > -1,
    Linux: navigator.platform.indexOf(«Linux») > -1
  }
}
* This source code was highlighted with Source Code Highlighter.


Координаты / Позициионирование


8. Набор функций, позволяющих получить координаты элемента на экране пользователя. Если ваш документ статичен относительно окна и не имеет скроллбаров — лучше использовать функцию getPosition — так будет быстрее. В обратном случае используйте getAlignedPosition — она учитывает положения скроллбаров. Только обратите внимание: значение top у элемента может быть орицательным, если элемент верхней частью за пределами окна — для синхронизации с курсором мыши иногда нужно обнулить в этом случае высоту. Основной скрипт позаимствован из одного блога, Aligned-версия — результат поисков по сусекам и совмещения с информацией из двух статей (при обнаружении DOCTYPE IE входит в свой собственный, несколько непредсказуемый, режим). Также этот метод скомбинирован с получением позиций из исходников руководства по Drag’n'Drop. Обратите внимание: здесь используется функция NaN0 из пункта 6, вам нужно будет добавить ее в скрипт чтобы все работало как надо (спасибо, Homer).
var IS_IE = USER_DATA['Browser'].MSIE;

function getPosition(e){
  var left = 0;
  var top = 0;

  while (e.offsetParent) {
    left += e.offsetLeft + (e.currentStyle?
      (parseInt(e.currentStyle.borderLeftWidth)).NaN0(): 0);
    top += e.offsetTop + (e.currentStyle?
      (parseInt(e.currentStyle.borderTopWidth)).NaN0(): 0);
    e = e.offsetParent;
  }

  left += e.offsetLeft + (e.currentStyle?
      (parseInt(e.currentStyle.borderLeftWidth)).NaN0(): 0);
  top += e.offsetTop + (e.currentStyle?
      (parseInt(e.currentStyle.borderTopWidth)).NaN0(): 0);  

  return {x:left, y:top};
}

function getAlignedPosition(e) {
  var left = 0;
  var top = 0;

  while (e.offsetParent) {
    left += e.offsetLeft + (e.currentStyle?
      (parseInt(e.currentStyle.borderLeftWidth)).NaN0(): 0);
    top += e.offsetTop + (e.currentStyle?
      (parseInt(e.currentStyle.borderTopWidth)).NaN0(): 0);
    e = e.offsetParent;
    if (e.scrollLeft) {left -= e.scrollLeft; }
    if (e.scrollTop) {top -= e.scrollTop; }
  }

  var docBody = document.documentElement?
    document.documentElement: document.body;

  left += e.offsetLeft +
    (e.currentStyle?
        (parseInt(e.currentStyle.borderLeftWidth)).NaN0()
        : 0) +
    (IS_IE? (parseInt(docBody.scrollLeft)).NaN0(): 0) —     (parseInt(docBody.clientLeft)).NaN0();
  top += e.offsetTop +
    (e.currentStyle?
        (parseInt(e.currentStyle.borderTopWidth)).NaN0()
        : 0) +
    (IS_IE? (parseInt(docBody.scrollTop)).NaN0(): 0) —     (parseInt(docBody.clientTop)).NaN0();

  return {x:left, y:top};
}
* This source code was highlighted with Source Code Highlighter.


9. Определить текущие координаты курсора мыши и смещение элемента относительно курсора легко, если использовать соответствующие функции:
function mouseCoords(ev) {
  if (ev.pageX || ev.pageY) {
    return {x:ev.pageX, y:ev.pageY};
  }

  var docBody = document.documentElement
            ? document.documentElement
            : document.body;

  return {
    x: ev.clientX + docBody.scrollLeft — docBody.clientLeft,
    y: ev.clientY + docBody.scrollTop - docBody.clientTop
  };
}

function getMouseOffset(target, ev, aligned) {
  ev = ev || window.event;
  if (aligned == null) aligned = false;

  var docPos  = aligned
    ? getAlignedPosition(target)
    : getPosition(target);
  var mousePos = mouseCoords(ev);

  return {
    x: mousePos.x — docPos.x,
    y: mousePos.y — docPos.y
  };
}
* This source code was highlighted with Source Code Highlighter.


Последняя функция также может использоваться в двух режимах засчет атрибута aligned и предназначена для удобного использования в обработчиках событий, например:
function onMouseMove(elm, ev) {
  var mouseOffset = getMouseOffset(elm, ev);
  console.log(«x: %d; y: %d», mouseOffset.x, mouseOffset.y);
}
...
<div id=«someId» onmousemove=«onMouseMove(this, event);
    return false;»
></div>
* This source code was highlighted with Source Code Highlighter.


10. Определение высоты элемента иногда более нелегкая задача чем определение других его параметров, но эти две функции придут на помощь:
function findOffsetHeight(e) {
  var res = 0;
  while ((res == 0) && e.parentNode) {
    e = e.parentNode;
    res = e.offsetHeight;
  }
  return res;
}

function getOffsetHeight(e) {
  return this.element.offsetHeight ||
      this.element.style.pixelHeight ||
      findOffsetHeight(e);
}
* This source code was highlighted with Source Code Highlighter.


DOM


11. Иногда нужно пройти рекурсивно по дереву DOM, начиная с некоторого элемента и выполняя некоторую функцию над каждым из потомков, забираясь в самую глубь. В DOM есть объект TreeWalker, но он не работает в IE и не всегда удобен/прост в использовании. Функция walkTree позволяет выполнить некоторую другую функцию над каждым из элементов и позволяет также передать в нее некоторый пакет данных. Функция searchTree отличается от нее тем, что останавливает проход по дереву при первом удачном результате и возвращает результат в точку вызова:
function walkTree(node, mapFunction, dataPackage) {
  if (node == null) return;
  mapFunction(node, dataPackage);
  for (var i = 0; i < node.childNodes.length; i++) {
    walkTree(node.childNodes[i], mapFunction, dataPackage);
  }
}

function searchTree(node, searchFunction, dataPackage) {
  if (node == null) return;
  var funcResult = searchFunction(node, dataPackage);
  if (funcResult) return funcResult;
  for (var i = 0; i < node.childNodes.length; i++) {
    var searchResult = searchTree(node.childNodes[i],
              searchFunction, dataPackage);
    if (searchResult) return searchResult;
  }
}
* This source code was highlighted with Source Code Highlighter.


В примере используются функции setElmAttr и getElmAttr, которые будут рассмотрены позже — в пункте 13. По сути они делают то же что и getAttribute и setAttribute. Пояснения к используемой функции oc вы можете посмотреть в пукте 4. В первой части примера корневому элементу атрибут “nodeType” устанавливается в “root”, а всем его потомкам — в “child”. Во второй части демонстрируется также передача пакета данных — при нахождении первого элемента с атрибутом “class”, равным одному из перечисленных в пакете имен, атрибут “isTarget” ему устанавливается в “true”.

var rootElement = document.getElementById('rootElm');

setElmAttr(rootElement, «nodeType», «root»);
var childNodeFunc = function(node) {
  if (node.nodeName && (node.nodeName !== '#text')
            && (node.nodeName !== '#comment')) {
    setElmAttr(node, «nodeType», «child»);
  }
}
walkTree(rootElement, childNodeFunc);

var findTargetNode = function(node, classList) {
  if ((node.nodeName && (node.nodeName !== '#text')
          && (node.nodeName !== '#comment')) &&
          (getElmAttr(node, «class») in oc(classList))) {
    return node;
  }
}
var targetNode = searchTree(rootElement, findTargetNode,
          ['headingClass', 'footerClass', 'tableClass']);
setElmAttr(targetNode, “isTarget”, true);
* This source code was highlighted with Source Code Highlighter.


NB! (будьте осторожны с использованием этих функций и постарайтесь избежать их чересчур частого вызова (более раза в секунду) даже на средней ветвистости дереве — они могут пожрать немало ресурсов. или, по крайней мере, вызывайте их в фоне через setTimeout)

12. Удаление узлов — иногда необходимая задача. Иногда нужно удалить сам узел, а иногда — только его потомков. Функция removeChildrenRecursively рекурсивно удаляет всех потомков указанного узла, не затрагивая, конечно, его самого. Функция removeElementById, как и сказано в названии, удалает узел по его id — при всей простоте задачи способ относительно хитрый:
function removeChildrenRecursively(node)
{
  if (!node) return;
  while (node.hasChildNodes()) {
    removeChildrenRecursively(node.firstChild);
    node.removeChild(node.firstChild);
  }
}

function removeElementById(nodeId) {
  document.getElementById(nodeId).parentNode.removeChild(
              document.getElementById(nodeId));
}
* This source code was highlighted with Source Code Highlighter.


13. Казалось бы — элементарная задача работы с атрибутами элемента — иногда наталкивает на абсолютно неожиданные проблемы: например, IE бросает исключение при попытке доступа к атрибутам высоты/ширины элемента table, а у Safari отличается способ доступа к атрибутам с пространствами имен. Приведенные ниже функции обходят все встреченные мной проблемы без сильного ущерба к скорости выполнения (конечно же, в стандартных случаях лучше использовать встроенные функции):
var IS_SAFARI = USER_DATA['Browser'].Safari;

function getElmAttr(elm, attrName, ns) {
  // IE6 fails getAttribute when used on table element
  var elmValue = null;
  try {
    elmValue = (elm.getAttribute
          ? elm.getAttribute((ns? (ns + NS_SYMB): ”)
          + attrName): null);
  } catch (e) { return null; }
  if (!elmValue && IS_SAFARI) {
    elmValue = (elm.getAttributeNS
          ? elm.getAttributeNS(ns, attrName)
          : null);
  }
  return elmValue;
}

function setElmAttr(elm, attrName, value, ns) {
  if (!IS_SAFARI || !ns) {
    return (elm.setAttribute
          ? elm.setAttribute((ns? (ns + NS_SYMB): ”)
          + attrName, value): null);
  } else {
    return (elm.setAttributeNS
          ? elm.setAttributeNS(ns, attrName, value)
          : null);
  }
}

function remElmAttr(elm, attrName, ns) {
  if (!IS_SAFARI || !ns) {
    return (elm.removeAttribute
          ? elm.removeAttribute((ns? (ns + NS_SYMB): ”)
          + attrName): null);
  } else {
    return (elm.removeAttributeNS
          ? elm.removeAttributeNS(ns, attrName)
          : null);
  }
}
* This source code was highlighted with Source Code Highlighter.


Логгинг


14. Представленная ниже функция для помощи в ведении логов очень проста, добавьте в нужное место в документе элемент <div id="LOG_DIV"></div>, задайте ему необходимую высоту, и в него будет сбрасываться информация + обеспечиваться ее скроллинг:
function LOG(informerName, text) {
  var logElement = document.getElementById('LOG_DIV');
  if (logElement) {
    logElement.appendChild(document.createTextNode(
            informerName + ': ' + text));
    logElement.appendChild(document.createElement('br'));
    logElement.scrollTop += 50;
  }
}
* This source code was highlighted with Source Code Highlighter.


15. В замечательном плагине Firebug для браузера Firefox есть замечательная консоль, в которую с широкими возможностями можно производить логгинг. Однако, если вы отлаживаете параллельно код в других браузерах — обращения к ней могут вызывать ошибки. Для того чтобы не очищать каждый раз код от логов, можно использовать такую заглушку:
var Console = Class.extend({
  // the stub class to allow using console when browser have it,
  // if not — just pass all calls
  construct: function() {},
  log: function() { },
  info: function() { },
  warn: function() { },
  error: function() { }
});

if (!window.console) {
  console = new Console();
}
* This source code was highlighted with Source Code Highlighter.


UPD: Поправил ссылки на Source Code Highlighter, спасибо.
Tags:
Hubs:
+69
Comments73

Articles