Каррирование - способ предварительного заполнения первых нескольких аргументов функции
(а также возврата новой функции).
Т.е., я могу в коде предварительно заполнить несколько аргументов, а при вызове указать
недостающие?
Function.prototype.curry = function () {
// Запомнить функцию и предварительно заполнить ее аргументами переменные
var fn = this,
args = Array.prototype.slice.call(arguments);
// Эта функция должна получить на вход сохраненные аргументы и новые
return function() {
var arg = 0;
// Объединить заранее подготовленные аргументы сохраненные в замыкании с
новыми аргументами
for (var i = 0; i < args.length && arg < arguments.length; i++) {
if (args[i] === undefined) {
args[i] = arguments[arg++];
}
}
// вызвать каррирующую функцию со всеми аргументами
return fn.apply(this, args);
};
};
Т.е., если каррировать данную функцию:
function sum (a,b)
return a + b;
То можно вызвать ее следующим образом:
var addTwo = sum.carry(2);
addTwo(5); // Увеличение на 2
// Но на выходе NaN
Правильно ли я все понял с каррированием, и где может быть ошибка?
Ответы
Ответ 1
Этот код рабочий.
Function.prototype.curry = function () {
// Запомнить функцию и предварительно заполнить ее аргументами переменные
var fn = this,
args = Array.prototype.slice.call(arguments);
// Эта функция должна получить на вход сохраненные аргументы и новые
return function() {
var arg = 0;
// Объединить заранее подготовленные аргументы сохраненные в замыкании с
новыми аргументами
for (var i = 0; i < args.length || arg < arguments.length; i++) {
if (args[i] === undefined) {
args[i] = arguments[arg++];
}
}
// вызвать каррирующую функцию со всеми аргументами
return fn.apply(this, args);
};
};
function sum (a,b) {
return a + b;
}
var addTwo = sum.curry(2);
console.log(addTwo(5)); // Выводит 7
Ваши ошибки: надо в уcловии цикла, где объединяете массивы аргументов писать || а
не &&, чтобы ориентироваться на самый длинный из массивов. Также вы допустили описку
в var addTwo = sum.carry(2); - функция называется curry.
Я извиняюсь за такой вопрос, но спросить больше не у кого. Почему не работает код?
function add(a) {
return function() {
return a + 1;
}
}
var f = add(5);
console.log(f);
/*
объявляем функцию add и передаем ей параметр со значением 5, который станет в теле
функции локальной переменной (var a = 5;).
В функции add имеется анонимная функция которая не имеет своего объекта переменных(лексического
окружения), поэтому она по scope находит значение переменной "a" во внешнем объекте
переменных, после чего выполняется и возвращается в функцию add виде выражения 5+1=6.
В переменную f мы записываем результат выполнения всей инструкции function add, равный
6. На деле не так, выводится код функции, подскажите, где я запутался?
*/
Ответы
Ответ 1
Надо вызвать функцию, добавив после f круглые скобочки.
function add(a) {
return function() {
return a + 1;
}
}
var f = add(5);
console.log(f());
я проверял код до написания вопроса в jslint, но он вместо подсказки о
необходимости добавления скобок, написал Expected ';' and instead saw
'}'.
Либеральные js парсеры позволяют не ставить ";", если потом идет, например, перенос
строки. Но jslint на такие вещи реагирует.
Речь в этом предупреждении идет об отсутствии ";" после значения в return:
return function() {
return a + 1;
}; // вот здесь
Отсутствие скобок после переменной f тоже синтаксической ошибкой не является - может
быть Вы хотели вывести в консоль текст кода функции.
На этот вопрос уже даны ответы здесь:
Как работают замыкания в JavaScript
(7 ответов)
Закрыт 2 года назад.
Знаю что написано на эту тему уже немало, но всё же
Итак, шло все как по маслу (в плане изучения js) пока я к замыканиям не дошел) читаю,
смотрю ролики на эту тематику - и просто ну нивкакую не могу понять, как это работает
и зачем это нужно..?
если с определением/пониманием что такое замыкания более менее понятно... хотя тут
тоже все не так однозначно... вот что пишут несколько источников по даному вопросу:
MDN: Замыкание — это комбинация функции и лексического окружения, в
котором эта функция была определена.
javascript.ru: Замыкание – это функция вместе со всеми внешними
переменными, которые ей доступны.
в неткоторых недалеких источниках:
это функция внутри другой функции
)
Вот так вот, поди пойми эти замыкания, если многие даже их определить нормыльным
языком не могут (
перейдём непосредственно к коду
вот пример замыканий с сайта w3schools:
Counting with a local variable.
0
Вопрос - зачем нужны все эти renurn'ы? если первый как я понимаю еще возвращает функцию,
то зачем нужен второй и почему без него у counter'a значение undefined?
function makeAdder(x) {
return function(y) {
return x + y;
};
};
var add5 = makeAdder(5);
var add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12
почему нельзя поставить измененные параметры сразу при вызове (напр., makeAdder(2)
а не (add5(2)) ), то есть зачем нужно сперва присваивать функцию в переменную?
процитирую наконец еще один источник (тот же MDN):
Замыкания — это функции, ссылающиеся на независимые (свободные)
переменные. Другими словами, функция, определённая в замыкании,
«запоминает» окружение, в котором она была создана. Это окружение состоит из произвольного
количества локальных переменных, которые были в области действия функции во время создания
замыкания.
что здесь понимается под "окружением" кроме переменных или только переменные? насколько
"долго" замыкание может помнить такое окружение?
p.s. не кидайтесь тапкми за возможно по нубски сформулированные вопросы)
Ответы
Ответ 1
Можете почитать по дубликату о подробностях.
При создании функции, ей создаётся ссылка на родительское окружение, в котором она
создана.
Т. е. при отсутствии у себя в окружении, допустим, переменной, движок может пойти
по ссылке в родительский скоуп и там отыскать.
Функция add вызывается сама при создании, возвращает новую функцию, но с захваченной
переменной-счетчиком в замыкании.
При вызове add, сработает внутренняя функция, изменит значение и вернёт его.
Почему return нужен? Ну без него функции возвращают undefined (кроме стрелок. Иногда).
Синтетический пример просто.
Но показывающий возможности замыкания - создал фабрикой (паттерн) особую функцию,
уже заряженную чем-либо (тут просто особенной цифрой) и можно ею манипулировать, зная
что везде, где применяешь, будут заданные ранее параметры.
Представим функцию, при создании которой всегда создаётся её окружение (кладём туда
переменные, определённые в ней, внутреннюю информации и т. п.).
Внутри создаём ещё одну функцию, для которой тоже создаётся объект окружения, но
так же есть сокрытое от программиста свойство [[scope]], которое ссылается на родительский
объект окружения.
При обращении внутренней функции к переменной, сначала смотрится собственное окружение.
Если нету - идём к родителю. Если и там нет - к его родителю. И так до конца - объекта
global (window в браузерах).
В картинках:
function b(){ | b.LexicalScoping = {x: 42, c: function} | b.[[scope]] = window
let x = 42;
function c(){ | c.LexicalScoping = {y: 3} | c.[[scope]] = b.LexicalScoping
let y = 3;
alert(w);
}
}
Когда интерпретатор выполняет alert(w), он ищет переменную w и смотрит так: c.LexicalScoping,
если пусто - c.[[scope]]. Если и там пусто - b.[[scope]].
Если и там нет - выбросит ошибку.
Это, конечно, не копирка стандартов, но для новичка объяснение нормально, так что
не надо тут флагать, @Grundy!
На этот вопрос уже даны ответы здесь:
Как работают замыкания в JavaScript
(7 ответов)
Закрыт 3 года назад.
Что такое замыкания мне понятно, но в ходе их исследования возникли нюансы:
Есть код:
Не могу осознать благодаря чему переменная numberOfCalls в функции createCounter
не обнуляется? Это фишка такая или этому есть разумное объяснение?
Ответы
Ответ 1
"Вы вызываете один раз функцию createCounter".
"Замыкание" - создание объекта, содержащего экземпляры всех переменных из внешних
областей видимости, используемых функцией.
Вызовите функцию еще раз , а потом начинайте вызывать два результата в произвольном
порядке:
$(document).ready(function() {
function createCounter() {
var numberOfCalls = 0;
return function() {
return ++numberOfCalls;
};
}
function appendOutput(aText) {
$("#output").append(aText + " ");
}
$("#btnRun").click(function() {
$("#output").html("");
var fn1 = createCounter();
var fn2 = createCounter();
appendOutput("fn1: " + fn1());
appendOutput("fn1: " + fn1());
appendOutput("fn1: " + fn1());
appendOutput("fn2: " + fn2());
appendOutput("fn2: " + fn2());
appendOutput("fn1: " + fn1());
});
});
output
Ответ 2
Её [переменную] один раз инициализировали, возвращённая функция находится вложеннее,
и, так как переменная используется в функции, сборщик не убирает её.
Полезное чтение почему так происходит.
Ответ 3
Особенность вложенных функций состоит в том, чтобы получать значения из нее. В данном
случае, внутри одной функции создаётся другая и возвращается в качестве результата.
В разработке интерфейсов это совершенно стандартный приём, функция затем может назначаться
как обработчик действий посетителя.
function createCounter() {
// LexicalEnvironment = { numberOfCalls: undefined }
var numberOfCalls = 0;
// LexicalEnvironment = { numberOfCalls : 0 }
return function() { // [[Scope]] -> LexicalEnvironment (**)
return numberOfCalls++;
};
}
Как видно, мы получили независимый счётчик function(), каждый из которых, незаметным
снаружи образом, сохраняет текущее количество вызовов.
Если подробнее описать происходящее:
1) В строке (*) запускается createCounter(). При этом создаётся LexicalEnvironment
для переменных текущего вызова. В функции есть одна переменная var numberOfCalls, которая
станет свойством этого объекта. Она изначально инициализируется в undefined, затем
в процессе выполнения получит значение 0.
2) В процессе выполнения createCounter() создаёт функцию в строке (**). При создании
эта функция получает внутреннее свойство [[Scope]] со ссылкой на текущий LexicalEnvironment.
3) Далее вызов createCounter() завершается.
почему в ниже лежащей функции конечное значение переменной равно первоначальному?
Хотя во внутренней функции данной переменной присвоено другое значение посредством
замыкания? Как сделать, что бы сохранялось новое значение?
function s(){
var a = 1;
function d(c){
console.log(c);
c = 2;
console.log(c);
}
d(a)
console.log(a)
}
вывод в консоль:
s()
1
2
1
Ответы
Ответ 1
Потому что примитивные значения (строки, числа, булевы значения, null/undefined)
передаются в качестве аргументов по значению, а не по ссылке.
Т.е. грубо говоря ваш аргумент c - это не ссылка на переменную а, это новая переменная
c, со значением которое вы туда передали.
Если сделать объект, то он будет передаваться по ссылке -
function s(){
var a = {b:1};
function d(c){
console.log(c.b);
c.b = 2;
console.log(c.b);
}
d(a);
console.log(a.b)
}
s();
Смысл замыкания как раз в том, что вы делаете одну функцию внутри другой, и из неё
видно все локальные переменные родительской функции. вам не надо ничего никуда передавать
чтоб с ними работать:
function s(){
var a = 1;
function d(){
console.log(a);
a = 2;
console.log(a);
}
d(a);
console.log(a)
}
s();
Замыкание — это особый вид функции. Она определена в теле другой
функции и создаётся каждый раз во время её выполнения. При этом
вложенная внутренняя функция содержит ссылки на локальные переменные
внешней функции.
(с) Википедия
Имеется следующий код:
function foo() {
var count = 0;
return function foo2() {
count++;
console.log(count);
}
}
var d = foo();
d();
d();
d();
Пример простого замыкания.
Вопрос: почему, для того, чтобы работал код нужно создавать переменную и помещать
туда вызов функции? Почему если вызвать функции напрямую foo(); (без переменной), то
не работает?
Ответы
Ответ 1
Просто, не происходит вызов функции... Без переменной правильной будет такая конструкция
function foo() {
var count = 0;
return function foo2() {
count++;
console.log(count);
}
}
foo()() // 1
foo()() // 1
foo()() // 1
Проблема в том, что каждый вызов функции foo создает новую переменную count. Если
count будет объявлена вне функции или частью её прототипа, то инкремент будет происходить
корректно
function foo() {
foo.prototype.count=foo.prototype.count|0;
return function foo2() {
foo.prototype.count++;
console.log(foo.prototype.count);
}
}
foo()() // 1
foo()() // 2
foo()() // 3
Сегодня попробовал google closure.
Заметил, что цифры в условиях он ставит на первое место:
Оригинал
A.keyLis.blockCtrlAlt.fined(e.keyCode) !== -1 && e.ctrlKey
После closure
-1 !== A.keyLis.blockCtrlAlt.fined(a.keyCode) && a.ctrlKey
Вопрос следующий- как это оптимизирует код?
Ответы
Ответ 1
Нет, это не оптимизация. Это Йода стайл. Когда то он помогал избежать ошибок.
Если вместо a == 1 написать a = 1, то будет немного не то, что ожидается, но многие
старый компиляторы/интерпретаторы пропускали. А вот так 1 = a сразу ошибка.
Сейчас многие компиляторы/интерпретаторы умеют "видеть" код вида a=1 в условиях и
ругаются. Как по мне, то сейчас так уже писать не нужно.
Этот вопрос не дубликат, так как он затрагивает не только замыкания, но и иные распространённые
ошибки в JS, а также их устранения используя новые возможности спецификации ES 2015.
При переборе элементов не получается правильно зарегистрировать обработчики события
click перебираемых элементов. События всех перебираемых элементов, регистрируются анонимной
функцией, предназначенной для самого последнего элемента.
По задумке.
1 При наведении на fieldset сообщения подсвечивается иконка удаления каждого сообщения,
при отведении мышки иконка удаления, соответствующего сообщения, снова угасает.
2 При наведении на иконку удаления всплывает подсказка со временем соответствующего
сообщения.
3 При клике на иконку соответствующего сообщения удаления всплывает алерт со временем
соответствующего сообщения.
Имею.
1 При наведении на любой fieldset сообщения подсвечивается иконка удаления последнего
сообщения, при отведении мышки иконка удаления, последнего сообщения, снова угасает.
2 Это единственное, что работает правильно.
3 При клике на иконку удаления любого сообщения всплывает алерт со временем последнего
сообщения.
Что я делаю не так, что я тут не понимаю и как мне сделать правильно?
window.addEventListener('load',function()
{
var imgs=document.getElementsByClassName('right')[0].getElementsByClassName('del');
for(var i=0,l=imgs.length;i0&&!isNaN(img.dataset.fid)&&img.dataset.fid>0&&img.dataset.title)
alert('Are you sure you want to delete '+img.dataset.title+' message?');
});
}
});
*
{
margin:0;
padding:0;
border:0;
border-radius:5px;
transition:all 0.2s linear;
}
html,body
{
height:100%;
min-height:100%;
}
body
{
background-color:#fff;
color:#000;
font:12px/18px Arial,Helvetica,sans-serif;
margin:0 auto;
}
fieldset
{
border:1px solid #ccc;
}
legend
{
font-weight:bold;
}
div.message
{
min-width:400px;
width:400px;
max-height:400px;
margin:0 auto;
padding:10px;
text-align:center;
}
div.message div.left, div.message div.right
{
float:left;
height:100%;
overflow:auto;
}
div.message div.right
{
width:250px;
text-align:center;
}
div.message div.right hr
{
border-top:1px solid #ccc;
margin-top:10px;
}
div.message div.right hr+span
{
background-color:#fff;
font-weight:bold;
padding:0 10px;
position:relative;
bottom:10px;
}
div.message div.right fieldset
{
margin:0 0 10px;
padding:10px;
text-align:left;
position:relative;
}
div.message div.right fieldset.from
{
margin-right:50px;
border-color:#6f6;
background-color:#dfd;
color:#040;
}
div.message div.right fieldset.to
{
margin-left:50px;
border-color:#66f;
background-color:#ddf;
color:#004;
}
div.message div.right fieldset img.del
{
width:16px;
top:0;
right:8px;
}
div.message img.del
{
position:absolute;
opacity:0.3;
}
div.message img.del:hover
{
cursor:pointer;
opacity:1;
}
Test
08.10.2016
Ответ: Самый простой и быстрый способ объявить все переменные через let вместо var.
Ответы
Ответ 1
На первый взгляд может показаться, что здесь классическая проблема с замыканием,
и это она действительно и есть.
Но! Решение здесь намного проще: так как в обработчике подразумевается работа с элементом
по которому нажали, проблема замыкания решается использованием this, вместо переменной
img. Так как, в данном случае this - будет, как раз тем элементом, по которому кликнули.
window.addEventListener('load', function() {
var imgs = document.getElementsByClassName('right')[0].getElementsByClassName('del');
for (var i = 0, l = imgs.length; i < l; i++) {
var img = imgs[i];
var doc = document.getElementById('mes-' + img.dataset.date + '-' + img.dataset.fid);
doc.addEventListener('mouseover', function() {
// здесь проблема остается, так как обработчик вешается на fieldset
img.style.opacity = 1;
});
doc.addEventListener('mouseout', function() {
// здесь проблема остается, так как обработчик вешается на fieldset
img.removeAttribute('style');
});
img.setAttribute('title', 'Delete ' + img.dataset.title + ' message');
img.addEventListener('click', function() {
if (!isNaN(this.dataset.date) && this.dataset.date > 0 && !isNaN(this.dataset.fid)
&& this.dataset.fid > 0 && this.dataset.title)
alert('Are you sure you want to delete ' + this.dataset.title + ' message?');
});
}
});
legend {
font-weight: bold;
}
div.message {
width: 400px;
}
div.message div.right fieldset {
text-align: left;
position: relative;
border-color: #6f6;
background-color: #dfd;
color: #040;
}
div.message div.right fieldset.to {
background-color: #ddf;
}
div.message div.right fieldset img.del {
width: 16px;
top: 0;
right: 8px;
position: absolute;
opacity: 0.3;
}
08.10.2016
Причины ошибки:
Переменные объявленные с помощью ключевого слова var имеют функциональную область
видимости. Это значит, что ни доступны внутри всей функции в которой объявлены и внутри
объявленных вложенных функций.
Несмотря на то, что объявление переменной находится внутри for будет объявлена всего
одна переменная, значение которой будет меняться.
Так как обработчики выполняются не сразу внутри цикла, а когда наступает определенное
событие, они берут значение указанной переменной в момент выполнения, а это, в данном
случае, значение присвоенное на последней итерации цикла.
Возможные решения:
Создание функции, создающей конкретный обработчик: в данном случае проблема решается
за счет создание функции-фабрики, которая будет создавать функцию обработчика на основе
параметра. Частным случаем этого подхода является использование IIFE, когда функция-фабрика
вызывается сразу при объявлении
Пример с именованной фабрикой:
function createClickHandler(img) {
return function() {
if (!isNaN(img.dataset.date) && img.dataset.date > 0 && !isNaN(img.dataset.fid)
&& img.dataset.fid > 0 && img.dataset.title)
alert('Are you sure you want to delete ' + img.dataset.title + ' message?');
};
}
function createMouseoverHandler(img) {
return function() {
img.style.opacity = 1;
};
}
function createMouseoutHandler(img) {
return function() {
img.removeAttribute('style');
};
}
window.addEventListener('load', function() {
var imgs = document.getElementsByClassName('right')[0].getElementsByClassName('del');
for (var i = 0, l = imgs.length; i < l; i++) {
var img = imgs[i];
var doc = document.getElementById('mes-' + img.dataset.date + '-' + img.dataset.fid);
doc.addEventListener('mouseover', createMouseoverHandler(img));
doc.addEventListener('mouseout', createMouseoutHandler(img));
img.setAttribute('title', 'Delete ' + img.dataset.title + ' message');
img.addEventListener('click', createClickHandler(img));
}
});
legend {
font-weight: bold;
}
div.message {
width: 400px;
}
div.message div.right fieldset {
text-align: left;
position: relative;
border-color: #6f6;
background-color: #dfd;
color: #040;
}
div.message div.right fieldset.to {
background-color: #ddf;
}
div.message div.right fieldset img.del {
width: 16px;
top: 0;
right: 8px;
position: absolute;
opacity: 0.3;
}
08.10.2016
Пример с IIFE:
window.addEventListener('load', function() {
var imgs = document.getElementsByClassName('right')[0].getElementsByClassName('del');
for (var i = 0, l = imgs.length; i < l; i++) {
var img = imgs[i];
var doc = document.getElementById('mes-' + img.dataset.date + '-' + img.dataset.fid);
doc.addEventListener('mouseover', function createMouseoverHandler(imgParam) {
return function() {
imgParam.style.opacity = 1;
};
}(img));
doc.addEventListener('mouseout', function createMouseoutHandler(imgParam) {
return function() {
imgParam.removeAttribute('style');
};
}(img));
img.setAttribute('title', 'Delete ' + img.dataset.title + ' message');
img.addEventListener('click', function createClickHandler(imgParam) {
return function() {
if (!isNaN(imgParam.dataset.date) && imgParam.dataset.date > 0 && !isNaN(imgParam.dataset.fid)
&& imgParam.dataset.fid > 0 && imgParam.dataset.title)
alert('Are you sure you want to delete ' + imgParam.dataset.title + ' message?');
};
}(img));
}
});
legend {
font-weight: bold;
}
div.message {
width: 400px;
}
div.message div.right fieldset {
text-align: left;
position: relative;
border-color: #6f6;
background-color: #dfd;
color: #040;
}
div.message div.right fieldset.to {
background-color: #ddf;
}
div.message div.right fieldset img.del {
width: 16px;
top: 0;
right: 8px;
position: absolute;
opacity: 0.3;
}
08.10.2016
Использование let/const. Использование данных ключевых слов, которые появились в
ES2015, позволяет объявлять переменные область видимости которых текущий блок. Это
означает, что на каждой итерации цикла будет создаваться новая переменная, поэтому
во время выполнения обработчика будет использоваться корректное значение
window.addEventListener('load', function() {
var imgs = document.getElementsByClassName('right')[0].getElementsByClassName('del');
for (var i = 0, l = imgs.length; i < l; i++) {
let img = imgs[i];
var doc = document.getElementById('mes-' + img.dataset.date + '-' + img.dataset.fid);
doc.addEventListener('mouseover', function() {
img.style.opacity = 1;
});
doc.addEventListener('mouseout', function() {
img.removeAttribute('style');
});
img.setAttribute('title', 'Delete ' + img.dataset.title + ' message');
img.addEventListener('click', function() {
if (!isNaN(img.dataset.date) && img.dataset.date > 0 && !isNaN(img.dataset.fid)
&& img.dataset.fid > 0 && img.dataset.title)
alert('Are you sure you want to delete ' + img.dataset.title + ' message?');
});
}
});
legend {
font-weight: bold;
}
div.message {
width: 400px;
}
div.message div.right fieldset {
text-align: left;
position: relative;
border-color: #6f6;
background-color: #dfd;
color: #040;
}
div.message div.right fieldset.to {
background-color: #ddf;
}
div.message div.right fieldset img.del {
width: 16px;
top: 0;
right: 8px;
position: absolute;
opacity: 0.3;
}
08.10.2016
Использование вместо for, функции forEach. Так как в данном случае в замыкании будет
использован параметр функции, а не переменная, значение в момент выполнения обработчика
события будет корректным. Из-за того, что функция getElementsByClassName возвращает
не массив, а коллекцию, использовать у нее функцию forEach нельзя. Но можно преобразовать
эту коллекцию в массив, либо использовать call.
Пример:
window.addEventListener('load', function() {
var imgs = document.getElementsByClassName('right')[0].getElementsByClassName('del');
[...imgs].forEach(function(img) {
var doc = document.getElementById('mes-' + img.dataset.date + '-' + img.dataset.fid);
doc.addEventListener('mouseover', function() {
img.style.opacity = 1;
});
doc.addEventListener('mouseout', function() {
img.removeAttribute('style');
});
img.setAttribute('title', 'Delete ' + img.dataset.title + ' message');
img.addEventListener('click', function() {
if (!isNaN(img.dataset.date) && img.dataset.date > 0 && !isNaN(img.dataset.fid)
&& img.dataset.fid > 0 && img.dataset.title)
alert('Are you sure you want to delete ' + img.dataset.title + ' message?');
});
});
});
legend {
font-weight: bold;
}
div.message {
width: 400px;
}
div.message div.right fieldset {
text-align: left;
position: relative;
border-color: #6f6;
background-color: #dfd;
color: #040;
}
div.message div.right fieldset.to {
background-color: #ddf;
}
div.message div.right fieldset img.del {
width: 16px;
top: 0;
right: 8px;
position: absolute;
opacity: 0.3;
}
08.10.2016
Использовать в обработчиках this, и искать элементы относительно элемента this. Так
как, при добавлении обработчика с помощью addEventListener, внутри обработчика this
будет указывать на элемент, к которому добавляли обработчик, то при клике на картинку,
вместо замыкания можно использовать this. В случае, когда обработчик добавляется на
fieldset можно найти нужную картинку внутри this, например с помощью querySelector
Пример
window.addEventListener('load', function() {
var imgs = document.getElementsByClassName('right')[0].getElementsByClassName('del');
for (var i = 0; i < imgs.length; i++) {
var img = imgs[i];
var doc = document.getElementById('mes-' + img.dataset.date + '-' + img.dataset.fid);
doc.addEventListener('mouseover', function() {
this.querySelector('img').style.opacity = 1;
});
doc.addEventListener('mouseout', function() {
this.querySelector('img').removeAttribute('style');
});
img.setAttribute('title', 'Delete ' + img.dataset.title + ' message');
img.addEventListener('click', function() {
if (!isNaN(this.dataset.date) && this.dataset.date > 0 && !isNaN(this.dataset.fid)
&& this.dataset.fid > 0 && this.dataset.title)
alert('Are you sure you want to delete ' + this.dataset.title + ' message?');
});
}
});
legend {
font-weight: bold;
}
div.message {
width: 400px;
}
div.message div.right fieldset {
text-align: left;
position: relative;
border-color: #6f6;
background-color: #dfd;
color: #040;
}
div.message div.right fieldset.to {
background-color: #ddf;
}
div.message div.right fieldset img.del {
width: 16px;
top: 0;
right: 8px;
position: absolute;
opacity: 0.3;
}
Делегация событий вместо цикла. В данном подходе обработчик вешается не на конкретную
картинку, а на контейнер, где лежат все картинки. Внутри обработчика уже определяется
по какому элементу щелкнули и выполняются нужные функции.
Пример
window.addEventListener('load', function() {
var right = document.getElementsByClassName('right')[0];
right.addEventListener('click', function(e) {
if (e.target.className === 'del') {
if (!isNaN(e.target.dataset.date) && e.target.dataset.date > 0 && !isNaN(e.target.dataset.fid)
&& e.target.dataset.fid > 0 && e.target.dataset.title)
alert('Are you sure you want to delete ' + e.target.dataset.title + ' message?');
}
});
[...right.querySelectorAll('img')].forEach(img => img.setAttribute('title', 'Delete
' + img.dataset.title + ' message'));
var fieldSets = right.querySelectorAll('fieldset');
for (var i = 0; i < fieldSets.length; i++) {
fieldSets[i].addEventListener('mouseover', function() {
this.querySelector('img').style.opacity = 1;
});
fieldSets[i].addEventListener('mouseout', function() {
this.querySelector('img').removeAttribute('style');
});
}
});
legend {
font-weight: bold;
}
div.message {
width: 400px;
}
div.message div.right fieldset {
text-align: left;
position: relative;
border-color: #6f6;
background-color: #dfd;
color: #040;
}
div.message div.right fieldset.to {
background-color: #ddf;
}
div.message div.right fieldset img.del {
width: 16px;
top: 0;
right: 8px;
position: absolute;
opacity: 0.3;
}
Ответ 2
Ответ находятся тут. https://javascript.ru/basic/closure#primer-oshibochnogo-ispolzovaniya
Причём вступление автора "С вопроса "Почему это не работает?" люди обычно начинают
изучение замыканий."
Просто, как никогда, попадает в точку и бьёт напролом!
Рабочий пример будет выгладить так.
window.addEventListener('load',function()
{
var imgs=document.getElementsByClassName('right')[0].getElementsByClassName('del');
for(var i=0,l=imgs.length;i0&&!isNaN(x.dataset.fid)&&x.dataset.fid>0&&x.dataset.title)
alert('Are you sure you want to delete '+x.dataset.title+' message?');
}
}(img));
}
});
Но всё же, стандарт ECMA Script 2015 позволяет воспользоваться более простым и изящным
методом, объявляя переменные не через var, а через let.
window.addEventListener('load',function()
{
let imgs=document.getElementsByClassName('right')[0].getElementsByClassName('del');
for(let i=0,l=imgs.length;i0&&!isNaN(img.dataset.fid)&&img.dataset.fid>0&&img.dataset.title)
alert('Are you sure you want to delete '+img.dataset.title+' message?');
});
}
});
Ответ 3
В качестве примера могу привести следующий код:
//функция создает объекты в циле, из интерисующих нас параметров box- контейнер в
котором находятся DOM эелементы и plate - сами элементы
function createPlates(box, plate, step, speed, bool) {
var box = document.querySelector(box);
if(!box) {
return false;
} else {
var plates = Array.prototype.slice.apply(box.querySelectorAll(plate));
//в цикле создаем объекты с помощью конструктора (будет описан ниже)
for(var i = 0; i < plates.length; i++) {
plates[i] = new ServisePlate (plates[i], step, speed, bool);
}
// на контейнер вешаем обработчик на клик
box.addEventListener('click', function(event) {
// И перебираем массив plates с созданными объектами
plates.forEach(function(element, i) {
// если объект события совпадает со ссылкой на DOM элемент, сохраненной
в объекте, запускаем его метод.
if(event.target == plates[i].elem) {
plates[i].disclose();
// Здесь проверка на клик по дочернему элементу необходимого нам элемента
}else if(plates[i].elem.contains(event.target)){
for(var j = 0; j < plates.length; j++) {
if(plates[j].elem.style.height) {
plates[j].disclose();
}
}
plates[i].disclose();
}else {
return false;
}
});
});
}
}
createPlates('.servises', '.servises_content', 5, 20, true);
Собственно сам конструктор:
function ServisePlate(plate, step, speed, headSet) {
this.elem = plate;
this.header = plate.querySelector('h3');
this.body = plate.querySelector('div');
this.link = plate.querySelector('a');
if(this.link) {
this.bodySize = this.body.offsetHeight + this.link.offsetHeight;
}else{
this.bodySize = this.body.offsetHeight;
}
this.settings = {
step: step,
speed: speed
}
this.initialSize = this.elem.offsetHeight;
var self = this;
function setHead() {
if(29 < self.header.innerHTML.length) {
self.header.classList.add('servises_content_header--p18');
}
if(59 < self.header.innerHTML.length) {
self.header.classList.remove('servises_content_header--p18');
self.header.classList.add('servises_content_header--p1');
}
}
if(headSet === true) {
setHead();
}
}
И его метод disclose, сохраненный в прототипе:
ServisePlate.prototype.disclose = function() {
function showBody() {
if(this.elem.offsetHeight < (this.bodySize + this.initialSize)) {
this.elem.style.height = this.elem.offsetHeight + this.settings.step
+ 'px';
setTimeout(showBody.bind(this), this.settings.speed);
}
}
function hideBody() {
if(this.elem.offsetHeight > this.initialSize) {
this.elem.style.height = this.elem.offsetHeight - this.settings.step
+ 'px';
setTimeout(hideBody.bind(this), this.settings.speed);
}else {
this.elem.style.height = '';
}
}
if(this.elem.offsetHeight == this.initialSize || this.elem.offsetHeight >=
(this.bodySize + this.initialSize)) {
if(this.elem.offsetHeight == this.initialSize) {
showBody.call(this);
} else {
hideBody.call(this);
}
}
}
Правда некоторые участки кода у меня самого вызывают подозрения на правильность,
так как я уже говорил что мой js тянет на 3 с минусом.
Но суть не в правильности тех или иных участков, а в подходе в целом.
for (j=1;j<=5;j ++)
{
(function(i){
setTimeout(function(){console.log(i)}, j*500)
})(j)
}
Интересует конкретно данный кусок кода, почему задержка не увеличивается на 500 мс?
Был бы благодарен если кто нибудь описал принцип работы функции.
Ответы
Ответ 1
Вы по сути одновременно запускаете пять функций, т.е. ваш код идентичен:
(function(i){setTimeout(function(){console.log(i)}, 1*500)})(1)
(function(i){setTimeout(function(){console.log(i)}, 2*500)})(2)
(function(i){setTimeout(function(){console.log(i)}, 3*500)})(3)
(function(i){setTimeout(function(){console.log(i)}, 4*500)})(4)
(function(i){setTimeout(function(){console.log(i)}, 5*500)})(5)
Что приводит к эффекту вывода пяти сообщений в консоль с интервалом 500 миллисекунд.
Здравствуйте!
Разбираю тему замыкания в JS и не могу понять двух моментов в коде с MDN по теме.
Объясните пожалуйста (вопросы после кода).
function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName; // (1 Вопрос)
};
var myFunc = makeFunc(); // (2 Вопрос)
myFunc();
Функция возвращается без скобок. Что именно будет возвращено?
Зачем функцию makeFunc() сохранять в переменную myFunc, ведь можно вызвать makeFunk()
напрямую? Для того чтобы "обернуть" замыкание (displayName + ее окружение)?
Ответы
Ответ 1
Функция возвращается без скобок. Что именно будет возвращено?
Как ни странно, будет возвращена именно эта функция.
Зачем функцию makeFunc() сохранять в переменную myFunc, ведь можно вызвать makeFunk()
напрямую? Для того чтобы "обернуть" замыкание (displayName + ее окружение)?
из пункта выше следует, что makeFunc() возвращает новую функцию, которая и сохраняется
в переменную, для последующего вызова.
Действительно, можно не сохранять результат и вызвать его сразу
makeFunc()();
Но для наглядности результат первого вызова был вынесен отдельно.
Лексическое окружение понятно, а как понять замыкания в JS. Какую функцию она выполняет.
Можете в простом примере простыми словами объяснить. Почти все видео-уроки бесполезны.
В нижеприведенном коде замыкания какую функцию выполняет?
function person() {
let name = 'Peter';
return function displayName() {
console.log(name);
};
}
let peter = person();
peter(); // 'Peter'
Ответы
Ответ 1
Замыкание - это возможность внутри функции обращаться к переменным, объявленным не
в самой функции, а в одном из содержащих её блоков.
Ещё замыканием можно назвать всё множество таких переменных.
В твоём примере переменная name доступна через замыкание.
Ответ 2
Замыкание – это функция вместе со всеми внешними переменными, которые ей доступны.
То есть, замыкание – это функция + внешние переменные.
Тем не менее, в JavaScript есть небольшая терминологическая особенность.
Обычно, говоря «замыкание функции», подразумевают не саму эту функцию, а именно внешние
переменные.
Иногда говорят «переменная берётся из замыкания». Это означает – из внешнего объекта
переменных. (источник: https://learn.javascript.ru/closures)
Ответ 3
Способ, с помощью которого я навсегда запомнил замыкания — это сравнение их с рюкзаком.
Когда функция создана и передаётся куда-либо, или возвращается из другой функции, то
она носит с собой рюкзак. А в этом рюкзаке хранятся все переменные, которые были в
области видимости во время создания этой функции.
(Источник: https://medium.com/nuances-of-programming/я-никогда-не-понимал-замыкания-в-javascript-часть-первая-3c3f02041970 )
Сейчас я прохожу самостоятельное обучение на одном из онлайн-ресурсов по JavaScript.
В нем помимо теории есть еще и задачки. Так вот на одной из задач в теме "Область видимости.
Замыкания" я столкнулся с полным непониманием решения задачи, которую предлагает автор
(свое решение у меня было только одно, и его я рассматривать не буду - ниже задача
с кодом и примерами от автора).
Задание:
Следующий код создает массив функций-стрелков shooters. По замыслу, каждый стрелок
должен выводить свой номер:
function makeArmy() {
var shooters = [];
for (var i = 0; i < 10; i++) {
var shooter = function() { // функция-стрелок
alert( i ); // выводит свой номер
};
shooters.push(shooter);
}
return shooters;
}
var army = makeArmy();
army[0](); // стрелок выводит 10, а должен 0
army[5](); // стрелок выводит 10...
// .. все стрелки выводят 10 вместо 0,1,2...9
Почему все стрелки́ выводят одно и то же? Поправьте код, чтобы стрелки работали как
задумано. Предложите несколько вариантов исправления. (Вопрос автора задания)
Предложенные автором варианты решения, которые мне непонятны:
(1) Использовать дополнительную функцию для того, чтобы «поймать» текущее значение i:
function makeArmy() {
var shooters = [];
for (var i = 0; i < 10; i++) {
var shooter = (function(x) {
return function() {
alert( x );
};
})(i);
shooters.push(shooter);
}
return shooters;
}
var army = makeArmy();
army[0](); // 0
army[1](); // 1
Я так и не понял этот вариант решения задания. Что дают в этом варианте эти вторые
скобки с i и почему в JavaScript, в отличии от других нормальных языков типа Java x
= i хотя имена у параметров разные. Иными словами я не понимаю, как происходит отлавливание i.
(2) Обернуть весь цикл во временную функцию:
function makeArmy() {
var shooters = [];
for (var i = 0; i < 10; i++)(function(i) {
var shooter = function() {
alert( i );
};
shooters.push(shooter);
})(i);
return shooters;
}
var army = makeArmy();
army[0](); // 0
army[1](); // 1
Этот вариант решения задания еще больше вводит меня в ступор. Где вообще хранится
значения i каждого стрелка? Какую роль во всем этом играют вторые скобки с параметром (i)?
Ответы
Ответ 1
Проблема с переменной возникает из-за замыкания функции на контекст, то есть на внешнюю
переменную i. Это можно легко определить, если вывести доступ к этой переменной во
внешний код. Я буду использовать вывод на консоль вместо всплывающего сообщения.
function makeArmy() {
var shooters = [];
for (makeArmy.i = 0; makeArmy.i < 10; makeArmy.i++) {
var shooter = function() {
console.log( makeArmy.i );
};
shooters.push(shooter);
}
return shooters;
}
Теперь наглядна видна зависимость и что функция выводит переменную к которой привязана
во время вызова. А к моменту вызова цикл полностью прошел и переменная равна конечному
значению 10.
var shooters = makeArmy();
a[0](); //выведет 10
makeArmy.i = 20;
a[0](); //выведет 20
Соответственно, для нормального формирования функции надо эту связь разорвать и скопировать
текущий номер внутри цикла.
[Решение]
Это можно сделать через дополнительную функцию, которая скопирует значение, я упрощу
пример для этого:
function test(){
var i = 0;
result = function(){ console.log(i); };
i = 10;
return result;
}
test()(); //test() возвращает функцию, вторые скобки для мгновенного вызова вернувшейся
функции
Нам надо сделать так, что бы переменная не замыкалась, а копировалась. Одним из способов
является передать переменную в качестве параметра функции, в таком случае она скопируется,
а не замкнется. А внутри мы сформируем нужную нам функцию или объект на эту скопированную
переменную.
function test(){
var i = 0;
var makeResult = function(x){
return function(){ console.log(x); };
}
var result = makeResult(i); // вызываем функцию и передаем ей параметр который
скопируется внутри и из него сформируется нужная функция.
i = 10;
return result;
}
test()(); //выводит 0
Таким образом мы обошли замыкание на внешний контекст благодаря копированию при вызове
с параметром. На самом деле это можно упростить, сразу вызвав функцию makeResult.
function test(){
var i = 0;
var result = function(x){
return function(){ console.log(x); };
}(i); //сразу вызываем
i = 10;
return result;
}
test()(); //выводит 0
Тоже самое касается цикла с солдатами, мы сделали промежуточную функцию, которая
сформировала нужную и вернула ее по мере увеличения переменной i.
function test(){
var results = [];
for(var i = 0; i < 10; i++){
var result = function(x){
return function(){ console.log(x); };
}(i);
results.push(result);
}
return results;
}
test()[5]();
Что в первом, что во втором примерах делается одно и то же - вызывается промежуточная
функция с параметром для копирования и разрыва контекста.
Только в одном формируется просто целевая функция, а во втором весь объект целиком.
Ответ 2
Проблема возникает вот здесь:
for (var i = 0; i < 10; i++) {
var shooter = function() {
alert( i );
};
shooters.push(shooter);
}
А именно при вызовах:
army[0]();
army[5]();
Первое решение простое, ведь при вызове функция возвращала конечное значение цикла(10).
Оборачивая в функцию, мы делаем так, чтобы значение i записывалось и возвращалось каждый раз.
А обернув во временную функцию, функция будет выполнятся i раз.
Есть код на JavaScript:
"use strict";
var site = (function ()
{
var loader_html = '';
var loader_image_width = 100;
var loader_image_height = 100;
return
{
window_subroutines : {
get_scrollTop : function () { return (document.documentElement && document.documentElement.scrollTop)
|| (document.body && document.body.scrollTop); },
get_scrollLeft : function () { return (document.documentElement && document.documentElement.scrollLeft)
|| (document.body && document.body.scrollLeft); },
get_clientWidth : function () { return (document.documentElement && document.documentElement.clientWidth)
|| (document.body && document.body.clientWidth); },
get_clientHeight : function () { return (document.documentElement &&
document.documentElement.clientHeight) || (document.body && document.body.clientHeight); }
},
loader : {
show : function () {
var loader_elem = document.getElementById ("loader_container");
var left_coord = Math.round(site.window_subroutines.get_scrollLeft()
+ ( site.window_subroutines.get_clientWidth() / 2 ) - ( loader_image_width / 2 )) + "px";
var top_coord = Math.round(site.window_subroutines.get_scrollTop()
+ ( site.window_subroutines.get_clientHeight() / 2 ) - ( loader_image_height / 2 )) + "px";
loader_elem.innerHTML = loader_html;
loader_elem.style.top = top_coord;
loader_elem.style.left = left_coord;
loader_elem.style.display = "block";
},
hide : function () { setTimeout("document.getElementById('loader_container').style.display
= 'none';", 750); }
}
};
})();
Проблема в том, что при попытке его выполнить браузер выводит ошибку:
SyntaxError: function statement requires a name
get_scrollTop : function () { return (document.documentElement && document.docum...
Посмотреть живьём это можно на jsfiddle
Ответы
Ответ 1
Надо изменить
return
{
на
return {
JavaScript добавляет ; в конец строка автоматическим образом, так что код распарсится так:
return;
{ // блок кода
window_subroutines: // метка
{ // блок кода
get_scrollTop : // метка
function () { /* ... */ } // SyntaxError: декларация функции без имени
(Смотрите в спецификации о инструкциях с метками и автоматической подстановке точки
с запятой)
Первый раз столкнувшись с замыканиями и не зная, что это такое, пробовал синтаксически
понять, что происходит в коде вида: (function(){})(). Известно, что конструкция "()"
- это вызов функции. Возникал вопрос по поводу первых круглых скобок
Позже, проникнувшись темой, вопрос первых круглых скобок меня волновать не перестал.
Зачем они нужны, если без них всё работает?
При этом ни одного примера в статьях про замыкания без применения первых круглых
скобок я не видел.
Классический пример замыкания в JavaScript:
buttons[i].onclick = (function(a){
return function(){ console.log(a);}
})(i)
без первых скобок:
buttons[i].onclick = function(a){
return function(){ console.log(a);}
}(i)
Зачем во всех примерах эти, получается лишние, синтаксические элементы?
Ответы
Ответ 1
Ключевое слово function в js может употребляться в двух контекстах - это контекст
объявления функции и контекст выражения, где это слово создает анонимную функцию.
Если строка начинается со слова function - то это объявление функции. С такой функцией
нельзя ничего сразу же сделать (например, вызвать), кроме того, у нее должно быть имя.
function (a) { console.log(a) } (5) // Ошибка
Поэтому такую функцию во что-нибудь оборачивают:
+function (a) { console.log(a) } (5) // 5
!function (a) { console.log(a) } (5) // 5
~function (a) { console.log(a) } (5) // 5
(function (a) { console.log(a) }) (5) // 5
(function (a) { console.log(a) } (5)) // 5
Если же слово function стоит не в начале строки - то ничего делать и не надо, все
будет прекрасно работать:
var a = function (a) { return 5 } (5)
Ответ 2
Круглые скобки не обязательны, они лишь указывают что это не объявление функции,
а выражение с функцией, которое надо вычислить прямо на месте, поэтому вот это всё
работает:
(function(){alert(1);})()
(function(){alert(1);}())
!function(){alert(1);}()
+function(){alert(1);}()
В вашем примере роль круглых скобок выполняет оператор присвоения, поэтому они там
действительно не нужны. Обычно используются именно круглые скобки, потому что они не
заставляют программу выполнять лишние телодвижения (из примеров выше - приводить функцию
к числу или к булевому значению). Они нужны, когда вам не нужно с анонимной функцией
ничего делать, кроме её вызова
Ответ 3
Конструкции вида:
(function() {
...
})();
Это паттерн Immediately-invoked function expression. В JavaScript его принято использовать
для разделения области видимости переменных - что бы случайно не наплодить глобальных
переменных, которые впоследствии могут вызвать ошибки в других блоках кода.
Как видно из кода: при наступлении .onload имеет на входе e, как передать в callback функцию значение i, чтобы не заменить при этом необходимый e?
for (var i in many_i) { image[i].onload = (function (e) {
var important_val1 = e.target;
var important_val2 = index;
})(i); }
Спасибо.
Ответ
Здравствуйте.
Замыкания - очень мощный инструмент, но пользоваться им надо с некоторой настороженностью =)
Дело в том, что в вашем примере, все созданные функции имеют доступ к переменной i из scope функции родителя. К одной и той же переменной i и при обращении к ней получают ее значение на момент обращения.
Для того чтобы перебороть такое поведение все что вам нужно - зафиксировать значение i на момент создания функции callback'a. Этого можно добиться добавив в код временную функцию, которая сохранит в своем scope текущее значение i и возвратит функцию обработчик, которая, в свою очередь, будет иметь доступ к значению i зафиксированному в scope родительской временной функции
Как то так.
var i;
for(i in many_i) {
image[i].onload = (function(){
var closedI = i;
return function (e) {
console.log(closedI, e);
}
})()
}
Более доступно тема раскрыта здесь
почему в ниже лежащей функции конечное значение переменной равно первоначальному? Хотя во внутренней функции данной переменной присвоено другое значение посредством замыкания? Как сделать, что бы сохранялось новое значение? function s(){
var a = 1;
function d(c){
console.log(c);
c = 2;
console.log(c);
}
d(a)
console.log(a)
}
вывод в консоль:
s()
1
2
1
Ответ
Потому что примитивные значения (строки, числа, булевы значения, null/undefined) передаются в качестве аргументов по значению, а не по ссылке.
Т.е. грубо говоря ваш аргумент c - это не ссылка на переменную а, это новая переменная c, со значением которое вы туда передали.
Если сделать объект, то он будет передаваться по ссылке -
function s(){
var a = {b:1};
function d(c){
console.log(c.b);
c.b = 2;
console.log(c.b);
}
d(a);
console.log(a.b)
}
s();
Смысл замыкания как раз в том, что вы делаете одну функцию внутри другой, и из неё видно все локальные переменные родительской функции. вам не надо ничего никуда передавать чтоб с ними работать:
function s(){
var a = 1;
function d(){
console.log(a);
a = 2;
console.log(a);
}
d(a);
console.log(a)
}
s();
Замыкание — это особый вид функции. Она определена в теле другой
функции и создаётся каждый раз во время её выполнения. При этом
вложенная внутренняя функция содержит ссылки на локальные переменные
внешней функции.
(с) Википедия
Нужно сделать функцию, которая принимает две функции (простые мат.операции), объединяет их в одну и возвращает её. Одна из входящих функций может принимать некоторое количество аргументов. А результирующая функция должна иметь возможность принимать аргументы, чтобы направлять их на вход этой вложенной. function funcGen(func1, func2) {
return function(/*возможные аргументы*/) {
return func1(func2(/*возможные аргументы*/))
}}
Я знаю о псевдомассиве arguments, но не понимаю, как передать его элементы на вход func2. Я бы передал их массивом, но тогда func2 должна быть заранее к этому готова, а такого условия нет в задаче.
Ответ
Вы, вероятно, хотите что-то такого рода?:
function pow2(a) {
return a * a;
}
function sum() {
var sum = 0;
for (var i = 0; i < arguments.length; ++i) {
sum += arguments[i];
}
return sum;
}
function collapseFunctions(f1, f2) {
return function() {
return f1(f2.apply(this, arguments));
}
}
console.log(collapseFunctions(pow2, sum)(1, 2, 3));
Функция f1 принимает 1 параметр, я сделал функцию, которая принимает 1 аргумент и возводит его в квадрат - pow2. Функция f2 принимает любое количество параметров, в данном случае я сделал функцию, которая считает сумму всех переданных параметров. Функция collapseFunctions принимает f1 и f2, вызывает f2 и передает результат ее работы как аргумент для f1. Потом все, что насчитала f1, возвращается вам. Для анонимной функции внутри collapseFunction я не объявлял параметры, ибо они берутся из arguments. Чтобы пробросить псевдомассив arguments в функцию f2, я использую функцию apply. Как-то так. Надеюсь, что понял, что вы хотите. ДОПОЛНЕНИЕ Функция f2 в моем примере не обязательно должна работать с псевдомассивом arguments. Она может явно объявлять нужные ей параметры. Они будут доставлены ей в любом случае. Например, пусть берет лишь два параметра - a и b
function pow2(a) {
return a * a;
}
function sum(a, b) {
return a + b;
}
function collapseFunctions(f1, f2) {
return function() {
return f1(f2.apply(this, arguments));
}
}
console.log(collapseFunctions(pow2, sum)(1, 2));
Каррирование - способ предварительного заполнения первых нескольких аргументов функции (а также возврата новой функции). Т.е., я могу в коде предварительно заполнить несколько аргументов, а при вызове указать недостающие? Function.prototype.curry = function () {
// Запомнить функцию и предварительно заполнить ее аргументами переменные
var fn = this,
args = Array.prototype.slice.call(arguments);
// Эта функция должна получить на вход сохраненные аргументы и новые
return function() {
var arg = 0;
// Объединить заранее подготовленные аргументы сохраненные в замыкании с новыми аргументами
for (var i = 0; i < args.length && arg < arguments.length; i++) {
if (args[i] === undefined) {
args[i] = arguments[arg++];
}
}
// вызвать каррирующую функцию со всеми аргументами
return fn.apply(this, args);
};
};
Т.е., если каррировать данную функцию: function sum (a,b)
return a + b;
То можно вызвать ее следующим образом: var addTwo = sum.carry(2);
addTwo(5); // Увеличение на 2
// Но на выходе NaN
Правильно ли я все понял с каррированием, и где может быть ошибка?
Ответ
Этот код рабочий. Function.prototype.curry = function () {
// Запомнить функцию и предварительно заполнить ее аргументами переменные
var fn = this,
args = Array.prototype.slice.call(arguments);
// Эта функция должна получить на вход сохраненные аргументы и новые
return function() {
var arg = 0;
// Объединить заранее подготовленные аргументы сохраненные в замыкании с новыми аргументами
for (var i = 0; i < args.length || arg < arguments.length; i++) {
if (args[i] === undefined) {
args[i] = arguments[arg++];
}
}
// вызвать каррирующую функцию со всеми аргументами
return fn.apply(this, args);
};
};
function sum (a,b) {
return a + b;
}
var addTwo = sum.curry(2);
console.log(addTwo(5)); // Выводит 7
Ваши ошибки: надо в уcловии цикла, где объединяете массивы аргументов писать || а не &&, чтобы ориентироваться на самый длинный из массивов. Также вы допустили описку в var addTwo = sum.carry(2); - функция называется curry.
На данный вопрос уже ответили:
Как работают замыкания в JavaScript
6 ответов
Что такое замыкания мне понятно, но в ходе их исследования возникли нюансы: Есть код:
Не могу осознать благодаря чему переменная numberOfCalls в функции createCounter не обнуляется? Это фишка такая или этому есть разумное объяснение?
Ответ
"Вы вызываете один раз функцию createCounter".
"Замыкание" - создание объекта, содержащего экземпляры всех переменных из внешних областей видимости, используемых функцией. Вызовите функцию еще раз , а потом начинайте вызывать два результата в произвольном порядке:
$(document).ready(function() {
function createCounter() {
var numberOfCalls = 0;
return function() {
return ++numberOfCalls;
};
}
function appendOutput(aText) {
$("#output").append(aText + " ");
}
$("#btnRun").click(function() {
$("#output").html("");
var fn1 = createCounter();
var fn2 = createCounter();
appendOutput("fn1: " + fn1());
appendOutput("fn1: " + fn1());
appendOutput("fn1: " + fn1());
appendOutput("fn2: " + fn2());
appendOutput("fn2: " + fn2());
appendOutput("fn1: " + fn1());
});
});
Сегодня попробовал google closure.
Заметил, что цифры в условиях он ставит на первое место: Оригинал A.keyLis.blockCtrlAlt.fined(e.keyCode) !== -1 && e.ctrlKey
После closure -1 !== A.keyLis.blockCtrlAlt.fined(a.keyCode) && a.ctrlKey
Вопрос следующий- как это оптимизирует код?
Ответ
Нет, это не оптимизация. Это Йода стайл. Когда то он помогал избежать ошибок. Если вместо a == 1 написать a = 1, то будет немного не то, что ожидается, но многие старый компиляторы/интерпретаторы пропускали. А вот так 1 = a сразу ошибка. Сейчас многие компиляторы/интерпретаторы умеют "видеть" код вида a=1 в условиях и ругаются. Как по мне, то сейчас так уже писать не нужно.