Fun With Higher Order Functions In JavaScript - Lee-hyuna/33-js-concepts-kr GitHub Wiki
์๋ฌธ: https://derickbailey.com/2015/10/21/fun-with-higher-order-functions-in-javascript/
๊ณ ์ฐจํจ์๋ ์ฌ๋ฐ..๋ค?!
JavaScript๋ "huge order functions"์ผ๋ก ํจ์ํ ํ๋ก๊ทธ๋๋ฐ์ด ๊ฐ๋ฅํด์ก๋ค. ์ด ์ธ์ด, ๋จ์ด ์์ฒด๋ ๋๋ฌด ๋ง์ฐํ ์๊ฐ์ด ๋ค๊ธด ํ๋ค..
์ ๋ฌผ์ ์ด์ด๋ณด๊ณ ์์์ ๋ ๋ค๋ฅธ ์ ๋ฌผ์ ์ฐพ๋ ๊ฒ ๊ฐ๋ค, ์ด ์ข์ ์์ด๋์ด๋ก ์ผํ๋ ๋ฐฉ๋ฒ์ ํฐ๋ํ๋ค๋ฉด ์ฌ๋ฏธ์๊ณ ํฅ๋ฏธ๋กญ๊ฒ ์ผ์ ํ ์ ์์ ๊ฒ์ด๋ค.
Common Examples
underscore
, lodash
๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์๋ "debounce
" ๋๋ "throttle
"๊ณผ ๊ฐ์ ๋ง์ ์ผ๋ฐ์ ์ธ ๊ธฐ๋ฅ์ ํฌํจํ์ฌ ๊ณ ์ฐจํจ์๋ ์ ๋ง ์ ์ฉํ๊ฒ ๋ง์ด ์ฐ์ธ๋ค
window.onResize = _.throttle(function(){
// do stuff when the window resizes, here
// but only do it once every 500 milliseconds
// and no more than that
}, 500);
์ด ์์ ๋ throttle ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ์๋์ฐ ๋ธ๋ผ์ฐ์ ๋ฆฌ์ฌ์ด์ฆ ์ด๋ฒคํธ๋ฅผ ์ง์์ ์ผ๋ก ์ฌ์ฉํ๋ ๊ฒ์ ๋ฐฉ์งํ๋ ์ญํ ์ ํ๋ค.
JavaScript์ ๋ด์ฅ๋ ๋ค๋ฅธ ์๋ ๋ณผ ์ ์๋ค. ํน์, ํจ์์ "bind
" ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์ด ์๋์?
var fn = someFunction.bind(someObject);
fn(someParam, 2, "...");
์ด๊ฒ์ ๊ณ ์ฐจํจ์๋ฅผ ๋ง๋๋ ๋ฐฉ๋ฒ์ด๋ค. ์ผ๋ฐ์ ์ผ๋ก JavaScript ์ฝ๋์๋ ๋ค์ํ ์๊ฐ ์๋ค. this๋ฅผ ์ฐ๊ธฐ๋ ์ฝ๋ค.
Build Your Own Bind Function
๋ธ๋ผ์ฐ์ ์์ ES5 ๊ธฐ๋ฐ JavaScript๊ฐ ๋ณดํธํ๋๊ธฐ ์ ์๋ "bind
" ํจ์๋ฅผ ์ง์ ์์ฑํด์ผ ํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์๋ค. ๋ฌผ๋ก , ํ ๋น๋๋ฅผ ์ฌ์ฉํ์ฌ underscore
, lodash
๋ฅผ ํ ์ ์์ง๋ง, ๋ชจ๋๊ฐ ๊ทธ๋ ๊ฒ ํ๊ธฐ๋ฅผ ์ํ์ง๋ ์๋๋ค.
๊ธฐ๋ณธ ์์ด๋์ด๋ ํจ์ ๋ฐ ์ปจํ
์คํธ ๊ฐ์ฒด๋ฅผ ์ ๋ฌํ์ฌ ํด๋น ํจ์ ๋ด์์ "this
"๋ฅผ ๋ํ๋ด๋ ๊ฒ์ด๋ค.
function bind(fn, ctx){
return function(){
var args = Array.prototype.slice.apply(arguments);
return fn.apply(ctx, args);
}
}
"bind" ํจ์ ์์ฒด๋ ๋ค๋ฅธ ํจ์๋ฅผ ๋ฐํํ๋ ๊ฒ ์ด์์ ์ญํ ์ ํ์ง ์๋๋ค. ์ผ๋ถ ๊ณ ์ฐจํจ์๋ ๋ด๋ถ ํจ์๋ฅผ ๋ฐํํ๊ธฐ ์ ์ ์ผ๋ถ ๊ณ์ฐ์ ์ํํ๊ณ ๋ค๋ฅธ ์ฝ๋๋ฅผ ์ค์ ํ๋ค. ์ค์ํ ๊ฒ์ ๋ฐ์ธ๋ฉ ํจ์๊ฐ ๋ค๋ฅธ ํจ์๋ฅผ ๋ฐํํ๋ค๋ ๊ฒ์ด๋ค.
์ด ๊ฒฝ์ฐ ์ ๋ง ์ค์ํ ๊ฒ์ด ๋ฐ๋ก ๋ด๋ถ ํจ์์ด๋ค. ํจ์๋ฅผ ๋ฐํํ๊ฒ๋๋ฉด ๋ณ์์ ํ ๋น๋๋ค. ์ด ๋ณ์๋ ์ด์ ๋ค์๊ณผ ๊ฐ์ ํจ์๋ฅผ ๊ฐ๋ฆฌํจ๋ค.
- ํ์ฌ ์ธ์ ๊ฐ์ฒด๋ฅผ ์ฌ๋ฐ๋ฅธ ๋ฐฐ์ด๋ก ๋ถํ ํ๋ค.
- ์๋ ํจ์๋ฅผ ํธ์ถํ๋ค ("
fn
" ๋งค๊ฐ ๋ณ์๋ก ์ ๋ฌ๋จ). - "
ctx
" ๋ณ์๋ฅผ ์๋ ๊ธฐ๋ฅ์ ๋ํ ์ปจํ ์คํธ("this
")๋ก ์ ์ฉํ๋ค.
์๋ฅผ ๋ค์ด ๋ค์๊ณผ ๊ฐ๋ค.
function foo(){
console.log(this.bar);
}
var fn = bind(foo, {bar: "baz"});
fn();
์ด ๊ฐ๋จํ ์์ ์์๋ "this.bar
"๋ฅผ ๊ธฐ๋กํ๋ "foo
" function๋ฅผ ์์ฑํ๋ค. ๋ฐ์ธ๋ฉ ํจ์๋ ์ปจํ
์คํธ๋ก ์ง์ ๋ ๊ฐ์ฒด ๋ฌธ์ ๊ทธ๋๋ก ํจ์๋ฅผ ์์ฑํ๋ ๋ฐ ์ฌ์ฉ๋๋ค. ๊ฒฐ๊ณผ ๊ธฐ๋ฅ์ ํธ์ถํ๋ฉด ์์๋ "this is a test
" ์ฝ์ ๋ฉ์์ง๊ฐ ์์ฑ๋๋ค.
๊ฐ์ฒด ๋ฉ์๋๋ฅผ ๋์ ์ผ๋ก ๊ตฌ์ฑํ๋ ๋ฐฉ๋ฒ
JavaScript ๊ฐ๋ฐ์๋ ๋ฉ์๋์์ ์ผ๋ฐ์ ์ธ ๋์์ ์ํ ๋ ์์ฑํด์ผ ํ๋ ์ฝ๋ ์์ ์ค์ด๋ ๋ฑ ๋ ๋์ ์์ ๊ธฐ๋ฅ์ ํตํด ๋ง์ ์์ ์ ์ํํ ์ ์๋ค.
์๋ฅผ ๋ค์ด, SPA์์ ๋๊ตฐ๊ฐ๊ฐ ๋ฉ๋ด์์ ๋งํฌ๋ ๋ฒํผ์ ํด๋ฆญํ ๋ ํ๋ฉด์ ํน์ ๋ณด๊ธฐ๋ฅผ ๋ ๋๋งํ๋ ์ฝ๋๊ฐ ์์ ์ ์๋ค. ๊ฐ ๋ฉ๋ด ํญ๋ชฉ์ ๋ค๋ฅธ ๋ณด๊ธฐ๋ฅผ ์์ฑํ์ง๋ง ๋ชจ๋ ๋ณด๊ธฐ๋ฅผ ๋์ผํ ๊ธฐ๋ณธ ๋ ์ด์์์ ํ์ํด์ผ ํ๋ค.
์ฑ์ด ์ด๋ฏธ ์คํ๋๊ณ ์์ผ๋ฉด ์ ๋ณด๊ธฐ๋ฅผ ๋ ์ด์์์ ์ ์ ํ ๋ถ๋ถ์ผ๋ก ๋ ๋๋งํ๋ค. ๊ทธ๋ฌ๋ ์ฌ์ฉ์๊ฐ ๋ธ๋ผ์ฐ์ ์์ ์๋ก ๊ณ ์นจ ๋จ์ถ๋ฅผ ๋๋ฅด๋ฉด ๋ณด๊ธฐ๋ฅผ ๋ฃ๊ธฐ ์ ์ ๋ ์ด์์์ด ์ ๋๋ก ๋์ด ์๋์ง ํ์ธํด์ผ ํ๋ค.
์ด๋ฅผ ๊ด๋ฆฌํ๊ธฐ ์ํด ์ฝ์์ ๋ฐํํ๋ "ShowLayout" ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์๋ค. ํน์ ๋ณด๊ธฐ๋ฅผ ํ์ํ๋ ๊ฐ ๋ฐฉ๋ฒ ๋ด์์ ์ด ๋ฐฉ๋ฒ์ ํธ์ถํ๊ณ ์ฝ์์ด ํด๊ฒฐ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆด ์ ์๋ค.
var MyApp = {
showLayout(){
// only show the layout once
if (this._layoutPromise){ return this._layoutPromise; }
// no layout shown yet, so set it up now
this._layoutPromise = new Promise(function(resolve){
var myLayout = new MyLayout();
myLayout.render(function(contents){
$("#container").html(contents);
resolve(myLayout);
});
});
// return the promise after we set it up
return this._layoutPromise;
},
home: function(){
// show the layout, then the homepage
this.showLayout().then(function(layout){
layout.showContent(new HomeContent());
});
},
about: function(){
// show the layout, load some data, then show
// the about page
this.showLayout().then(function(layout){
loadData("about", function(data){
var about = new AboutContent(data);
layout.showContent(about);
});
});
},
newThing: function(){
// show the layout, create the add thing view
// and then show it
this.showLayout().then(function(layout){
var thing = new Thing();
var addThingForm = new AddThingForm(thing);
layout.showContent(addThingForm);
});
}
};
showLayout ๋ฉ์๋๋ฅผ ํธ์ถํ๋ ๋ฐฉ๋ฒ์ ๋ณ๊ฒฝํด์ผ ํ๋ ๊ฒฝ์ฐ ์ด๋ฌํ ๋ชจ๋ ๋ฉ์๋์์ ๋ณ๊ฒฝํด์ผ ํ๋ค.
๊ณ ์ฐจํจ์๋ฅผ ์ฌ์ฉํ๋ฉด ์ด ์ฝ๋๋ฅผ ๋จ์ํํ ์ ์๋ค. showLayout()
์ฝ๋๋ฅผ ๋ชจ๋ ๊ธฐ๋ฅ์ ๋ฐฐ์นํ๋ ๋์ ๊ณ ์ฐจํจ์๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐํ์์ ํด๋น ๋์์ ๊ตฌ์ฑํ ์ ์๋ค.
// create a higher order function
// to use the layout and then run
// the next chunk of code
function useLayout(fn){
// return a function that knows how to use the layout
return function(){
this.showLayout().then((layout) => {
// make sure the original function still
// has "this" set appropriately
fn.call(this, layout);
});
}
}
// compose the app out of
// higher order functions
var MyApp = {
showLayout(){
// ... same implementation as before
},
// show the layout, then the homepage
home: useLayout(function(layout){
layout.showContent(new HomeContent());
}),
// show the layout, load some data, show about
about: useLayout(function(layout){
loadData("about", (data) => {
var about = new AboutContent(data);
layout.showContent(about);
});
}),
// show the layout, create add form, show it
newThing: useLayout(function(layout){
var thing = new Thing();
var addThingForm = new AddThingForm(thing);
layout.showContent(addThingForm);
})
};
์ฌ๊ธฐ์ showLayout
๋ "useLayout
" ํจ์์ธ ๋จ์ผ ์์น์ ์บก์ํ๋๋ค. ์ด ๊ธฐ๋ฅ์ MyApp
๊ฐ์ฒด์ ๋ํด function์ returnํ๊ณ ํด๋น ๊ฐ์ฒด์ ์ฐ๊ฒฐ๋ ๊ฒ์ผ๋ก ๊ฐ์ ํ๋ค.
useLayout
ํจ์๋ MyApp
๊ฐ์ฒด ์ธ๋ถ์์ ์ฌ์ฌ์ฉํ ์ ์๋ ๋ฒ์๊ฐ ์ ์ง๋ง, ๋ชฉ์ ์ ShowLayout
ํธ์ถ์ ์บก์ํํ์ฌ ํ์์ ๋ฐ๋ผ ๋น ๋ฅด๊ณ ์ฝ๊ฒ ๋ณ๊ฒฝํ ์ ์๋๋ก ํ๊ธฐ ์ํ ๊ฒ์ด๋ค. ์ฝ๋ ์ค๋ณต์ ์์ ์ค์ด๋ ๊ฒ์ ์ข์ ์ผ์ด๋ฉฐ, ๊ฐ ๋ฐฉ๋ฒ์ ๋ํ ์ฝ๋ ์์ ์ค์ผ ์๋ ์๋ค. ์ฝ๊ธฐ ์ฝ๊ณ ์์ ํ๊ธฐ ์ฝ๋ค.
๊ทธ๋ฌ๋ ๋ฐํ์์ MyApp์ ๋ฉ์๋์ ์ถ๊ฐ ๋งค๊ฐ ๋ณ์๋ฅผ ์ ๋ฌํด์ผ ํ ๊ฒฝ์ฐ ์ด๋ป๊ฒ ๋๋ ๊ฒ์ธ๊ฐ?
Passing Args From The Function Call
์ฑ์ ID๋ ๋ผ์ฐํฐ์์ ์ ๋ฌ๋ ๋ค๋ฅธ ๋ฐ์ดํฐ๊ฐ ํ์ํ ๊ฒฝ์ฐ useLayout
ํจ์๊ฐ ์ด๋ฅผ ๊ณ ๋ คํด์ผ ํ๋ค. ๋ ์ด์์์ ๋์ ๊ธฐ๋ฅ๊ณผ ํจ๊ป ์ ๋ฌํด์ผ ํ์ง๋ง ID ๋๋ ๋ค๋ฅธ ๋งค๊ฐ ๋ณ์๋ค๋ ์ ๋ฌํด์ผ ํ๋ค.
์ด๋ ๊ฒ ํ๋ ค๋ฉด useLayout
์์ ๋ฐํ๋๋ ๊ธฐ๋ฅ์ ์์ ํ๊ณ ES6์ ...rest
์ฐ์ฐ์๋ฅผ ์ฌ์ฉํ์ฌ ์ธ์๋ฅผ ์ถ์ ํ๋๋ก ํ๋ค. ๊ทธ๋ฐ ๋ค์ fn.apply
๋ฅผ ํธ์ถํ๊ธฐ ์ง์ ์ ๋ ์ด์์ ๊ฐ์ฒด๋ฅผ Args
Array์ ์ถ๊ฐํ๋ค.
// create a higher order function
// to use the layout and then run
// the next chunk of code
function useLayout(fn){
// return a function that knows how to use the layout
return function(...args){
this.showLayout().then((layout) => {
// prepend the layout to the args
args = [layout, ...args];
fn.apply(this, args);
});
}
}
์ด์ MyApp์ ๋ฉ์๋๊ฐ ๋งค๊ฐ ๋ณ์๋ฅผ ํ์ฉํด๋ ํธ์ถ์ ์ง์ ๋ ๋ ์ด์์๊ณผ ๋งค๊ฐ ๋ณ์๊ฐ ๊ณ์ ํ์๋๋ค.
// compose the app out of
// higher order functions
var MyApp = {
// ... other methods
// load a thing by it's id, then show it
editThing: useLayout(function(layout, id){
loadThing(id, (thing) => {
var editThingForm = new EditThingForm(thing);
layout.showContent(editThingForm);
});
})
};
// call the edit function with an id
MyApp.editThing(123);
๋ ์ด์์์ด ์ฒซ ๋ฒ์งธ ์ธ์๋ก ๋ค์ด๊ฐ๊ฒ ๋๋ค.
id๋ editThing
์ ํธ์ถํ ๋ ์ฌ์ฉํ๋ค. ์ฆ, ID๋ง ์ง์ ํด์ผ ๋๋จธ์ง ๋ถ๋ถ์ useLayout์ ๊ตฌํํ์ฌ ์ฒ๋ฆฌํ ์ ์๋ค.