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" ํ•จ์ˆ˜ ์ž์ฒด๋Š” ๋‹ค๋ฅธ ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ ์ด์ƒ์˜ ์—ญํ• ์„ ํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ผ๋ถ€ ๊ณ ์ฐจํ•จ์ˆ˜๋Š” ๋‚ด๋ถ€ ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ์ „์— ์ผ๋ถ€ ๊ณ„์‚ฐ์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ๋‹ค๋ฅธ ์ฝ”๋“œ๋ฅผ ์„ค์ •ํ•œ๋‹ค. ์ค‘์š”ํ•œ ๊ฒƒ์€ ๋ฐ”์ธ๋”ฉ ํ•จ์ˆ˜๊ฐ€ ๋‹ค๋ฅธ ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

์ด ๊ฒฝ์šฐ ์ •๋ง ์ค‘์š”ํ•œ ๊ฒƒ์ด ๋ฐ”๋กœ ๋‚ด๋ถ€ ํ•จ์ˆ˜์ด๋‹ค. ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ๋˜๋ฉด ๋ณ€์ˆ˜์— ํ• ๋‹น๋œ๋‹ค. ์ด ๋ณ€์ˆ˜๋Š” ์ด์ œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ•จ์ˆ˜๋ฅผ ๊ฐ€๋ฆฌํ‚จ๋‹ค.

  1. ํ˜„์žฌ ์ธ์ˆ˜ ๊ฐœ์ฒด๋ฅผ ์˜ฌ๋ฐ”๋ฅธ ๋ฐฐ์—ด๋กœ ๋ถ„ํ• ํ•œ๋‹ค.
  2. ์›๋ž˜ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค ("fn" ๋งค๊ฐœ ๋ณ€์ˆ˜๋กœ ์ „๋‹ฌ๋จ).
  3. "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์„ ๊ตฌํ˜„ํ•˜์—ฌ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.