Понимание функции каррирования из вопроса интервью

интервью внешний интерфейс JavaScript функциональное программирование

Недавно, разбирая ресурсы для интервью, я нашла интересную тему и записала ее.

тема

Как реализовать мульти(2)(3)(4)=24?

Во-первых, давайте проанализируем эту проблему, реализуем многофункциональную функцию и передадим параметры для выполнения по очереди, чтобы получить окончательный результат. Вывод, который легко сделать из вопроса, состоит в том, что перемножая поступающие параметры можно получить требуемый результат, то есть 2Х3Х4=24.

простая реализация

Итак, как реализовать многофункциональную функцию для вычисления значения результата? Первое решение, которое приходит на ум, — это замыкания.

function multi(a) {
    return function(b) {
        return function(c) {
            return a * b * c;
        }
    }
}

Используя принцип замыкания, когда мультифункция выполняется, она возвращает внутреннюю функцию в мультифункции. Когда она выполняется снова, она фактически выполняет внутреннюю функцию. Эта внутренняя функция затем вкладывается во внутреннюю функцию для вычисления окончательного результат и возврат.

闭包实现

Чисто из названия кажется, что желаемый результат достигнут, но если хорошенько подумать, то обнаружится, что есть проблемы.

Недостатки приведенной выше схемы реализации:

  • Код недостаточно элегантен, а этапы реализации требуют уровней вложенных функций.
  • Плохая масштабируемость.Если вы хотите реализовать такие функции, как multi(2)(3)(4)...(n), вам нужно вложить n уровней функций.

Итак, есть ли лучшее решение, ответ заключается в использовании каррирования функций в функциональном программировании.

каррирование функций

В функциональном программировании функции являются гражданами первого класса. Так как же выглядит каррирование функций?

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

Основными функциями и особенностями каррирования функций являются повторное использование параметров, ранний возврат и отложенное выполнение.

Например: инкапсулировать метод, который совместим с мониторингом событий современных браузеров и браузеров IE Обычно инкапсуляция выглядит так.

var addEvent = function(el, type, fn, capture) {
    if(window.addEventListener) {
        el.addEventListener(type, function(e) {
            fn.call(el, e);
        }, capture);
    }else {
        el.attachEvent('on' + type, function(e) {
            fn.call(el, e);
        })
    }
}

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

var addEvent = (function() {
    if(window.addEventListener) {
        return function(el, type, fn, capture) {
            el.addEventListener(type, function(e) {
                fn.call(el, e);
            }, capture);
        }
    }else {
        return function(ele, type, fn) {
            el.attachEvent('on' + type, function(e) {
                fn.call(el, e);
            })
        }
    }
})()

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

Другим типичным сценарием применения каррирования является реализация функции связывания. Используются две особенности каррирования функций: повторное использование параметров и ранний возврат.

Function.prototype.bind = function(){
	var fn = this;
	var args = Array.prototye.slice.call(arguments);
	var context = args.shift();

	return function(){
		return fn.apply(context, args.concat(Array.prototype.slice.call(arguments)));
	};
};

Реализация каррирования

Так как же реализовать функцию вопросов интервью через функцию каррирования?

Универсальная версия

function curry(fn) {
    var args = Array.prototype.slice.call(arguments, 1);
	return function() {
		var newArgs = args.concat(Array.prototype.slice.call(arguments));
        return fn.apply(this, newArgs);
    }
}

Первый аргумент функции curry — это функция, которая будет динамически каррироваться, а остальные аргументы хранятся в переменной args.

Функция, возвращаемая при выполнении функции каррирования, получает новые аргументы в сочетании с аргументами, хранящимися в переменной args, и передает объединенные аргументы функции каррирования.

function multiFn(a, b, c) {
    return a * b * c;
}
var multi = curry(multiFn);
multi(2,3,4);

результат:

image

Хотя результат тот же, легко обнаружить, что есть проблема, то есть код сложнее, чем предыдущая реализация замыкания, и метод выполнения не такой multi(2)(3)(4), как название требует. Тогда давайте улучшим эту версию кода.

Улучшенная версия

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

function curry(fn, args) {
    var length = fn.length;
    var args = args || [];
    return function(){
        newArgs = args.concat(Array.prototype.slice.call(arguments));
        if(newArgs.length < length){
            return curry.call(this,fn,newArgs);
        }else{
            return fn.apply(this,newArgs);
        }
    }
}
function multiFn(a, b, c) {
    return a * b * c;
}
var multi = curry(multiFn);
multi(2)(3)(4);
multi(2,3,4);
multi(2)(3,4);
multi(2,3)(4);

image

Видно, что благодаря улучшенной версии функции каррирования реализация темы расширилась до нескольких. Расширяемость кода этой схемы реализации относительно сильна, но все же немного недостаточна, то есть количество оцениваемых параметров должно быть известно заранее, поэтому код может быть более гибким и достигать эффекта передачи параметров при будет, например: мульти(2)(3)(4), мульти(5)(6)(7)(8)(9) и так далее.

Оптимизировано

function multi() {
    var args = Array.prototype.slice.call(arguments);
	var fn = function() {
		var newArgs = args.concat(Array.prototype.slice.call(arguments));
        return multi.apply(this, newArgs);
    }
    fn.toString = function() {
        return args.reduce(function(a, b) {
            return a * b;
        })
    }
    return fn;
}

image

Такое решение можно использовать гибко. Недостатком является то, что возвращаемое значение имеет тип Function.

image

Суммировать

  • Что касается самой темы, существует несколько реализаций, если вы понимаете и в полной мере используете возможности замыканий.
  • Может случиться так, что в практических прикладных сценариях решение каррирования функций используется редко, но все же полезно понимать и распознавать каррирование функций для самосовершенствования.
  • После понимания замыканий и каррирования функций, если вы столкнетесь с похожим типом вопроса на собеседовании, вы сможете его решить.

постскриптум

Технический результат написан в отношении обучения и обобщения, пожалуйста, укажите на любые ошибки и проблемы в тексте. Больше технических результатов можно посмотреть в моемблог на гитхабе.

Я разобрался с некоторыми внешними учебными ресурсами, надеясь помочь тем, кто в них нуждается, по адресу:Резюме учебных ресурсов.

Ссылаться на