Около 2-х месяцев назад я и TheShock собирали вопросы по JavaScript в теме FAQ по JavaScript: задавайте вопросы. Первая часть, те вопросы, которые достались мне, появилась буквально через несколько дней JavaScript F.A.Q: Часть 1, а вот вторая часть все не выходит и не выходит. TheShock сейчас переезжает в другую страну и поэтому ему не до ответов. Он попросил меня ответить на его часть. Итак вторая часть ответов — те вопросы, которые достались тоже мне.
В этой части:
- Почему eval = evil?
- Чем заменить eval?
- Как проиграть звук используя JS без flash? Какие форматы поддерживаются?
- На сколько использование глобальных переменных замедляет скрипты?
- Как запретить выделение текста на JS?
- (function() {})(); Для чего используется такая конструкция?
- Как сбросить события и восстановить состояние элемента?
- Как корректно передавать js-команды с параметрами в ajax приложениях?
- Для чего в jQuery передавать window если он и так глобальный и зачем undefined?
- Как узнать в какую сторону юзер крутит колесо мыши?
- Есть ли событие focus у элемента, если нет, то возможно ли его реализовать?
- Как сделать неизменяемый объект (freez)?
- Возможно ли сделать кастомную реализацию DomElement так чтобы при разборе для подмножества тегов использовалась она?
- Какой клиентский MVC-фреймворк посоветуете?
- Обход ограничений по Referer
- GOTO в Javascript
- Наследование в JavaScript. Как идеологически правильно заэкстэндить Man и Woman от Human
- Возможно ли устроиться javascript программистом без копания в верстке и server-side?
- Какие есть практики передачи данных с сервера в код, который исполняется из .js?
- Можно ли подцепить(эмулировать) поведение DOMInputElement'a с помощью обыных DOMElemen'ов(div)?
- Проблема приватных и публичным методов
- Как с помощью JS сделать одновременный выбор и загрузку нескольких файлов?
- При загрузке файлов с использованием File и FormData как определить не пытается ли пользователь загрузить директорию?
- Есть ли в JavaScript аналоги декларациям/аннотациям (термины из Python/Java соответственно)?
1. Почему eval = evil?
1. Eval нарушает привычную логику программы, порождая код.
2. Он выполняет код в текущем контексте и может его изменять — потенциальное место для атак. Контекст можно подменить, выполнив такой трюк:
(1,eval)(code);
Что тут происходит: eval — это та же функция, которая зависит от способа вызова. В этом случае мы меняем контекст на глобальный.
3. Порожденный eval-ом код сложнее отлаживать.
4. Eval очень долго выполняется.
Почитать
Global eval. What are the options?
Куча вопросов на stackoverflow:
stackoverflow.com/questions/197769/when-is-javascripts-eval-not-evil
stackoverflow.com/questions/646597/eval-is-evil-so-what-should-i-use-instead
stackoverflow.com/questions/86513/why-is-using-javascript-eval-function-a-bad-idea
…
2. Чем заменить eval?
Лучшая замена eval — хорошая структура кода, которая не предполагает использование eval. Однако от eval не всегда легко отказаться (Парсинг JSON, Шаблоонизатор от Резига).
Хорошая замена eval — конструктор Function. Но это тот же eval, который выполняется в глобальном контексте.
new Function('return ' + code)();
// Это практически эквивалентно
(1,eval)(code);
3. Как проиграть звук используя JS без flash? Какие форматы поддерживаются?
Самое простое решение:
<audio src="http://developer.mozilla.org/@api/deki/files/2926/=AudioTest_(1).ogg" autoplay>
Your browser does not support the <code>audio</code> element.
</audio>
Но этим HTML5 audio не ограничивается. Форматы: Ogg Vorbis, WAV PCM, MP3, AAC, Speex (зависит от конкретного браузера) О форматах: Audio format support
Почитать
HTML5 Audio и Game Development: баги браузеров, проблемы и их решения, идеи
WHATWG — The audio element
MDN — HTML audio
Пример: Creating a simple synth
Пример: Displaying a graphic with audio samples
Пример: Visualizing an audio spectrum
Пример: Creating a Web based tone generator
4. На сколько использование глобальных переменных замедляет скрипты?
Зависит от конкретного браузера. В современных браузерах этот момент оптимизирован и разницы нет. В старых доступ до глобальной переменной может быть медленнее в 2 раза.
Вот тест: jsperf.com/global-vs-local-valiable-access
Пожалуйста, учитывайте тот факт, что это тест, каждая операция прогоняется до 1 миллиона раз в секунду. Даже если прирост для теста 30%, то фактический прирост для 1й операции будет мизерный (микросекунды).
5. Как запретить выделение текста на JS?
Есть несколько вариантов.
1. Выполнить
preventDefault
для событий onselectstart
и onmousedown
var element = document.getElementById('content');
element.onselectstart = function () { return false; }
element.onmousedown = function () { return false; }
2. Добавить атрибут
unselectable
$(document.body).attr('unselectable', 'on')
3. Добавить стиль
user-select: none
.g-unselectable {
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
user-select: none;
}
Есть и безумные решения данной проблемы:
4. По таймеру чистить
Range, TextRange, Selection
Не буду объяснять. Как работать с Selection можно посмотреть тут: Range, TextRange и Selection
5. Рендерить текст на канвасе
6. Использовать disabled textarea
<textarea disabled="disabled" style="border:none;color:#000;background:#fff; font-family:Arial;">
Как запретить выделение текста на JS?
</textarea>
Вот микроплагин для jQuery, использующий методы 1-3
$.fn.disableSelection = function() {
function preventDefault () {
return false;
}
$(this).attr('unselectable', 'on')
.css('-moz-user-select', 'none')
.css('-khtml-user-select', 'none')
.css('-o-user-select', 'none')
.css('-msie-user-select', 'none')
.css('-webkit-user-select', 'none')
.css('user-select', 'none')
.mousedown(preventDefault);
.each(function() {
this.onselectstart = preventDefault;
});
};
Этот плагин не спасет от Ctrl+A Ctrl+C в тех браузерах, которые не поддерживают user-select или unselectable. Помните: запрещать выделение — зло.
6. (function() {})(); Для чего используется такая конструкция?
Это называется Immediately-Invoked Function Expression (IIFE) — Функция-выражение, которая была вызвана сразу же после создания.
Вот несколько способов создать IIFE
(function(){ /* code */ })(); // Обычная IIFE
(function(){ /* code */ }()); // Крокфорд рекомендует
// Поставить функцию в состояние "значение"
true && function(){ /* code */ }();
0,function(){ /* code */ }();
// Можно создать, используя оператор в качестве префикса
!function(){ /* code */ }(); // Facebook style
~function(){ /* code */ }();
-function(){ /* code */ }();
+function(){ /* code */ }();
// Используя new
new function(){ /* code */ }
new function(){ /* code */ }() // Если необходимы аргументы, то ставим скобки
Где это используется:
1. Если вам нужно, чтобы какие-то переменные не попали во внешнее окружение (подобие let)
var APPLICATION_ID = function(){
var keys = {
'pew.ru': 15486,
'pew.pew.ru': 15487,
'pew.pew.pew.ru': 1548
};
return keys[window.location.host];
}();
2. Создание локального замыкания — основа для модулей, объектов, запоминающих состояние
var Module = (function(){
function privateMethod () {
};
return {
publicMethod: function () {
return privateMethod();
}
};
}());
Почитать
Immediately-Invoked Function Expression (IIFE)
ECMA-262-3 in detail. Chapter 5. Functions
Functions and function scope
Named function expressions
JavaScript Module Pattern: In-Depth
Closures explained with JavaScript
7. Как сбросить события и восстановить состояние элемента?
У нас есть какой-нибудь DOM-объект с изначально заданными при помощи HTML и CSS параметрами «А». В процессе работы мы динамически меняем параметры на «В». Существует ли простой способ «сбросить» состояние объекта первоначальное состояние «А»? Аналогично с событиями. Как сделать откат на первоначальное состояние?Откатить состояние объекта не получится. DOM не хранит состояние потому, что элементы могут быть получены различным образом: непосредственно из кода html и сгенерированные JavaScript.
С событиями все проще. В DOM есть хороший метод cloneNode. Мы просто клонируем элемент со всем его содержимым (он не будет иметь событий) и подменяем старый элемент новым.
// Для теста
$('input').click(function(){alert('')});
function resetEvents(element) {
var newElement = element.cloneNode(true);
element.parentNode.insertBefore(newElement, element); // fixed by @spmbt
element.parentNode.removeChild(element);
return newElement;
}
resetEvents($('input')[0]);
// Клик на инпут ничего не даст
8. Как корректно передавать js-команды с параметрами в ajax приложениях?
Как корректно передавать js-команды с параметрами в ajax приложениях без возни с кавычками? Это относится к передаче данных в ajax-системе, описанной выше способом. При передаче сложных команд с несколькими уровнями вложенности параметров (например, «setTimeout('alert(»Бум!!!");', 200);" ) наблюдаются проблемы при использовании кавычек. Как их разрешить или есть ли какие-нибудь общие правила их оформления?Пересылать код от сервера к клиенту — это плохо. Это аналогично проблеме с eval. Правильно, быстро и безопасно передавать не код, а данные, которые будет обрабатывать браузер.
Можно использовать RPC. Очень прозрачный RPC можно получить, используя NowJS. NowJS — фреймворк, позволяющий выполнять функции на сервере как будето вы выполняете их на клиенте.
Вот пример с сайта:
На сервере: Node.JS server
var nowjs = require("now");
// тут создаем http сервер
var everyone = nowjs.initialize(httpServer);
everyone.now.getServerInfo = function(callback){
db.doQuery(callback);
}
На клиенте
now.getServerInfo(function(data){
// Данные запроса
});
Просто, прозрачно и красиво!
Почитать
Getting Started with NowJS
9. Для чего в jQuery передавать window если он и так глобальный и зачем undefined?
jQuery создает замыкание и в него пробрасывает window и undefined.
1.
undefined
передается для уменьшение объема кода и обхода проблемы с перезаписью undefined
. jQuery часто сравнивает с undefined
и при сжатии undefined
превратиться в однобуквенную переменную.2.
window
передается для уменьшение объема кода. jQuery применяет хорошую политику, отделяя методы окна от своих, используя window.setInterval()
и прочие. При сжатии window
превратиться в однобуквенную переменную, в коде будет c.setInterval()
10. Как узнать в какую сторону юзер крутит колесо мыши?
Есть 2 события, реагирующие на скролл колесом '
DOMMouseScroll
' (Firefox) и 'mousewheel
' (остальные)Как навесить событие, думаю всем понятно, посмотрим его обработчик
function wheel(event){
var delta = 0;
if (!event) event = window.event; // Событие IE.
// Установим кроссбраузерную delta
if (event.wheelDelta) {
// IE, Opera, safari, chrome - кратность дельта равна 120
delta = event.wheelDelta/120;
} else if (event.detail) {
// Mozilla, кратность дельта равна 3
delta = -event.detail/3;
}
if (delta) {
// Отменим текущее событие - событие поумолчанию (скролинг окна).
if (event.preventDefault) {
event.preventDefault();
}
event.returnValue = false; // для IE
// если дельта больше 0, то колесо крутят вверх, иначе вниз
var dir = delta > 0 ? 'Up' : 'Down',
}
}
Для jQuery Есть Mouse Wheel Plugin jQuery Mouse Wheel Plugin Demos
Не все элементы могут порождать скролл-событие. Зависит от конкретного браузера. Подробнее: scroll and mousewheel
11. Есть ли событие focus у элемента, если нет, то возможно ли его реализовать?
Фокус — это логическое событие, показывающее то, что объект сейчас выделен.
Если почитать стандарт то там написано, что события
focus
и blur
есть только у элементов формы: LABEL, INPUT, SELECT, TEXTAREA, и BUTTON. По стандарту никакие другие элементы не могут генерировать это событие. Для всех остальных элементов есть другие события DOMFocusIn
и DOMFocusOut
. IE, как всегда имеет свои события: focusin
и focusout
. Firefox тоже не особо подчиняется стандарту у него некоторые элементы, кроме формы могут выбрасывать
focus
и blur
. Посмотрите тест событий
focus, blur, focusin, focusout, DOMFocusIn, DOMFocusOut
:www.quirksmode.org/dom/events/tests/blurfocus.html
Если слушать все 3 события у элемента, то мы можем определить получил ли любой элемент фокус или нет.
Почитать
blur and focus
12. Как сделать неизменяемый объект (freeze)?
В ECMAScript 3 (стандарт который поддерживают все движки JavaScript) не было возможности замораживать объект, но в ECMAScript 5 появились сразу несколько функций, ограничивающих изменение объекта.
Object.preventExtensions
— самое слабое ограничение. Объект не может получать дополнительные параметрыObject.seal
— preventExtensions
+ любые параметры не могут удалятьсяObject.freeze
— preventExtensions
+ seal
+ параметры становятся только на чтение Эти новые методы поддерживаются далеко не во всех браузерах
Почитать
New in JavaScript 1.8.5
Object.preventExtensions/seal/freeze Function (JavaScript)
ECMAScript 5 compatibility table
13. Возможно ли сделать кастомную реализацию DomElement так чтобы при разборе для подмножества тегов использовалась она?
Напрямую нет. DOM отделен от JavaScript и поэтому есть некоторые ограничения.
1. Нет конструкторов конкретных элементов — есть только фабрика элементов —
document.createElement('')
и некоторые методы строки для создания элементов — String.prototype.anchor
и тп. HTMLElement
и HTMLDivElement
— это не конструкторы.2. Даже если мы эмулируем конструктор, то DOM «очистит» объект после вставки в дерево.
Пример
// Создаем наш какбы-конструктор
var HTMLZzzElement = function () {
var self = document.createElement('zzz');
self.__proto__ = HTMLZzzElement.prototype;
return self;
};
// Наследуем от HTMLUnknownElement
function F(){}
F.prototype = HTMLUnknownElement.prototype;
HTMLZzzElement.prototype = new F();
HTMLZzzElement.prototype.constructor = HTMLZzzElement;
HTMLZzzElement.prototype.pewpewpew = function () {};
// Создаем
var zzz = new HTMLZzzElement();
// Да, работает
zzz.innerHTML = 'Yohoho!';
// Проверяем
console.log(zzz instanceof HTMLZzzElement); // true
console.log(zzz instanceof HTMLUnknownElement); // true
console.log(zzz instanceof HTMLElement); // true
console.log(typeof zzz.pewpewpew); // function
//Все хорошо - все есть
// Засовываем в DOM
document.body.appendChild(zzz);
// Получаем обратно
zzz = document.querySelector('zzz')
// Объект очистили
console.log(zzz instanceof HTMLZzzElement); // false
console.log(zzz instanceof HTMLUnknownElement); // true
console.log(zzz instanceof HTMLElement); // true
console.log(typeof zzz.pewpewpew); // undefined
Однако, мы можем дописать к прототипу «класса» HTML элемента методы и свойства и потом использовать в будущем:
HTMLUnknownElement.prototype.pewpewpew = function () {};
console.log(typeof zzz.pewpewpew); // function
Цепочка наследования «классов» такая
Node -> Element -> HTMLElement -> HTMLDivElement/HTMLMetaElement ...
Вы можете дописать метод к прототипу элемента, однако помните, что пропатчивать чужие объекты — это плохо!
Этот способ будет работать только в современных браузерах. В ИЕ8 и ниже html элементы берутся как будето из воздуха, поэтому их прототипы нельзя переписать на низком уровне:
>>document.createElement('div').constructor
//undefined -- даже не null...
>>document.createElement('div') instanceof Object
//false -- забавно, правда? хотя магия с toString говорит обратное
>>Object.prototype.toString.call(document.createElement('div'));
//"[object Object]"
14. Какой клиентский MVC-фреймворк посоветуете?
Фреймворков много:
JavaScriptMVC
Backbone.js
SproutCore
TrimJunction
Посмотрите все и выберете лучше тот, который вам по душе. Я плотно работал только с Backbone.js
Почитать
Написание сложных интерфейсов с Backbone.js
И опять про MVC
MVC в JavaScript
15. Обход ограничений по Referer
На сервере blabla.ru лежит картинка, сервер не отдает её если передал Referer с другого сервера (pewpew.com). Как получить содержимое картинки и показать пользователю?Напрямую из браузера никак. Можно сделать серверный прокси, который будет менять Referer.
16. GOTO в Javascript
Собственно вопрос как повторить поведение goto в JavaScript. (Про хороший стиль и goto рассказывать не нужно :) речь идёт про генерированный код. и восстановление блоков и циклов процесс весьма не тривиальный)В JavaScript есть метки, но нет goto (есть только зарезервированное слово). Метка выглядит так же как и во всех других языках, но работают они иначе. Метка выделяет блок. Внутри блока могут быть другие помеченые блоки. На метку можно переходить используя break и continue, которые есть внутри этого блока.
Пример:
loop1: for (var a = 0; a < 10; a++) {
if (a == 4) {
break loop1; // Только 4 попытки
}
alert('a = ' + a);
loop2: for (var b = 0; b < 10; ++b) {
if (b == 3) {
continue loop2; // Пропускаем 3
}
if (b == 6) {
continue loop1; // Продолжаем выполнят loop1 'finished' не будет показано
}
alert('b = ' + b);
}
alert('finished')
}
block1: {
alert('hello'); // Отображает 'hello'
break block1;
alert('world'); // Никогда не будет отображено
}
goto block1; // Parse error - goto нет
17. Наследование в JavaScript. Как идеологически правильно заэкстэндить Man и Woman от Human
Идеологически правильно использовать прототипное наследование. Процесс наследования хорошо описан в статье "Основы и заблуждения насчет JavaScript"
18. Возможно ли устроиться javascript программистом без копания в верстке и server-side?
Зависит от проекта, размера компании и вашего опыта.
Если вы пойдете разработчиком браузерных-игр в крупную компанию, то шанс, что бы будете заниматься только JavaScript очень высок. Крупная компания может себе позволить отдельного интерфейсного программиста, а мелкая — нет. Вот повезет ли вам без опыта в крупной?!
Если вы только начинаете изучать веб, то вам стоит пойти туда где хотят всего.
19. Какие есть практики передачи данных с сервера в код, который исполняется из .js?
Способов передачи данных очень много: XHR, SSE, WebSockets, JSONP:
Реализация HTTP server push с помощью Server-Sent Events
Новые возможности XMLHttpRequest2
Создание приложений реального времени с помощью Server-Sent Events
Много всего про ajax
Какой транспорт и формат выбрать вам — зависит от требований к приложению. Например, если это чат, то лучше SSE или WebSockets (время доставки сообщения критично). А если этот чат должен работать кроссдоменно и в ИЕ6, то придется добавить магии с iframe или flash.
20. Можно ли подцепить(эмулировать) поведение DOMInputElement'a с помощью обыных DOMElemen'ов(div)?
На низком уровне нельзя. Причину я объяснил в 13-м примере. Можно сделать обертку, которая будет эмулировать Input, в основе которой будет DOMDivElement.
Я набросал простенький эмулятор jsfiddle.net/azproduction/Kz4d9 — получился очень кривой инпут (не умеет сабмититься, не умеет терять фокус и т.п.).
Эту же проблему можно решить более элегантно(спасибо Finom, что напомнил), добавив всего 1 атрибут: contenteditable, который поддерживают только десктопные браузеры — получился тот же инпут, но гораздо проше и лучше jsfiddle.net/azproduction/ZTqRe
21. Проблема приватных и публичным методов
Если возникает желание использовать частные переменные и методы объектов, в JavaScript есть проблема доступа к частным методам из публичных. А привилегированные методы, определенные внутри конструктора, если я правильно понимаю, дублируются для каждого экземпляра, что не очень хорошо с точки зрения производительности. Как насчет такого подхода?TheShock:
Неплохой подход. По крайней мере лучше, чем объявлять все методы в конструкторе.
// our constructor function Person(name, age){ this.name = name; this.age = age; }; // prototype assignment Person.prototype = new function(){ // we have a scope for private stuff // created once and not for every instance function toString(){ return this.name + " is " + this.age; }; // create the prototype and return them this.constructor = Person; this._protectedMethod = function (){ // act here }; this.publicMethod = function() { this._protectedMethod(); }; // "magic" toString method this.toString = function(){ // call private toString method return toString.call(this); } };
Модульный подход:
var Module = (function (window) {
function privateFunction() {
console.log('pass');
};
var privateClass = function () {};
var publicClass = function () {};
publicClass.prototype.publicMethod = function () {
privateFunction(); // ...
};
return { // Экспортируем
publicClass: publicClass
};
}(this));
// Используем
(new Module.publicClass()).publicMethod();
22. Как с помощью JS сделать одновременный выбор и загрузку нескольких файлов?
Все уже давно и не по разу написано:
1. HTML5 File API: множественная загрузка файлов на сервер
2. Загрузка файлов с помощью HTML5 и сколько раз мы сказали нехорошие слова
3. Загрузка браузером нескольких файлов
4. ajax загрузка нескольких файлов с php формой
5. Загрузка файлов с помощью html5 File API, с преферансом и танцовщицами
23. При загрузке файлов с использованием File и FormData как определить не пытается ли пользователь загрузить директорию?
При загрузке файлов с использованием File и FormData — как определить не пытается ли пользователь загрузить директорию. Свойство File.type к сожалению не помогает (для некоторых типов, как и для дир — пустая строка), свойство size — тоже (ff и сафари по разному показывают)?Насчет
File.type
говорится, что это mime-тип и если он не определен, то он становится пустой строкой. mime-тип, как водится получается из расширения файла/папки. Если мы подсунем настоящий файл с именем «pewpew», то mime будет "", а если директорию с именем «pewpew.html», то mime будет "text/html"
. Нам это не подходит.Размер папки всегда 0, но могут быть и файлы размером 0 байт. Для 100% уверенности нам это не подходит.
В стандарте www.w3.org/TR/FileAPI не говорится о директориях отдельно, но там отмечено, что браузеры могут выкидывать
SECURITY_ERR
если пользователь пытается работать с не корректным файлов (защищенным, не доступным на чтение и т.п.) Будем исходить из этого.В основу я взял пример Drag and Drop
Будем использовать
FileReader
для определения типа ресурса. В фаерфоксе если читать папку как файл, то метод reader.readAsDataURL
выбрасывает исключение. В хроме это исключение не выбрасывается, но вызывается событие onerror
у reader
. Скрестим и получим функцию для определения файл это или нет.function isReadableFile(file, callback) {
var reader = new FileReader();
reader.onload = function () {
callback && callback(true);
callback = null;
}
reader.onerror = function () {
callback && callback(false);
callback = null;
}
try {
reader.readAsDataURL(file);
} catch (e) {
callback && callback(false);
callback = null;
}
}
В функции я для подстраховки удаляю callback, чтобы он не вызвался повторно (в будущем поведение
FileReader
может измениться).Пример использования jsfiddle.net/azproduction/3sV23/8
Можно, было ограничится размером файла. Если 0, то считать что это не файл (ибо странно загружать файл размером 0 байт).
24. Есть ли в JavaScript аналоги декларациям/аннотациям (термины из Python/Java соответственно)?
Прямых аналогов нет.
PS Если я забыл ответить на какой-то вопрос — задайте его ещё раз. Если у вас есть вопросы, на которых нет ответов ни в первой ни во второй части — задавайте — они будут в третей ;)