[자바스크립트 패턴] 2~3주차 JS의 함수란?

이주영·2023년 9월 5일
0

Javascript

목록 보기
10/11
post-thumbnail

들어가기 앞서 🌱

LIVID 프론트엔드 스터디를 통해 웹 성능 최적화를 한 달간 진행했고 이제 자바스크립트 코딩 패턴 교제로 한 달간 스터디를 진행하기로 했다. 이 책은 JS의 ES5 문법을 기준으로 쓰였는데 여러모로 JS의 개념을 제대로 이해하는 데 좋다고 느꼈다. 이번 자바스크립트 코딩 패턴을 통해 closure와 this 그리고 module 그리고 prototype에 대해 깊이 알고 싶다. 그리고 회사에 있는 레거시 코드를 보고 리팩토링할 기회로 이어진다면 너무 재밌을 것 같다.

📘 4장 함수의 키워드

  • 함수 표현식
  • 함수 선언문
  • 지역 스코프
  • 호이스팅

📘 4.1 배경 지식

JS에서 함수의 특징은 두 가지가 있다. 가장 중요!!

첫 번째, 일급 객체 라는 것.

우선 일급이 무엇인가? 일급 시민, 일급 함수?

일급이란 다른 요소와 차이가 없다는 것이고 값처럼 쓸 수 있는 것을 의미한다.

특징으론

  • 런타임, 즉 프로그램 실행 중에 동적으로 생성한다.
  • 변수에 할당할 수 있고 다른 변수에 참조를 복사할 수 있고 확장 가능하고 삭제가 가능한다.
  • 다른 함수의 인자로 전달할 수 있고 return값이 될 수 있다.
  • 자기 자신의 프로퍼니와 메서드를 가질 수 있다.

함수가 일급 객체인 이유에 대해 스터디원과 논의해본 결과 한 문장으로 말하면

인자와 반환값으로 함수를 사용할 수 있기 때문이라는 결론이 낫다.

두 번째, 유효 범위(scope)를 제공한다.

  • 모듈을 만들기 위한 목적으로도 사용했다.

자바스크립트 함수는 객체인데 호출해서 실행시킬 수 있는 특별한 기능이 있는 것으로 알면 될 것 같다.

📕 선언문 vs 표현식의 차이

핵심적인 차이는 이름과 호이스팅이다. 모든 변수는 함수 본문 최상위로 끌어올려지는 특징이 있는데...

function foo() {
  return 'global foo'
}
function bar() {
  return 'global bar'
}

function hoistMe() {
console.log(foo()) // 'local foo;
console.log(bar()) 
//  var일 경우 TypeError: bar is not a function
//  let일 경우 ReferenceError: Cannot access 'bar' before initialization

  
  function foo() {
  return 'local foo'
}
  var bar = function () {
    return 'local bar'
  }
}

hoistMe()

hoistMe 함수 내부에 foo의 정의 부분이 호출되는 시점보다 늦어도 'local foo'가 잘 보이는데 bar은 호이스팅이 되지 않는 문제가 있다.

📕 선언문과 표현식 차이 정리표

함수선언문함수표현식
- 변수에 할당할 수 없다.
- 함수 이름을 생략할 수 없다.
- 런타임 이전에 자바스크립트 엔진에 의해 함수 객체가 먼저 생성된다
- 런타임에서 함수 선언문이 실행되기 이전에 함수를 참조할 수 있으며 호출할 수도 있다.
- 변수에 할당할 수 있다.
- 함수 이름을 생략할 수 있다.
- 함수 표현식은 변수에 할당되는 값이 함수 리터럴인 문이다.
- 함수 호이스팅이 발생하는 것이 아니라 변수 호이스팅이 발생한다.
- 함수 표현식으로 정의한 함수는 함수 표현식 이후에 참조, 호출해야 한다. TDZ와 연관이 있다.

📘 4.2 콜백 패턴

비동기 이벤트 리스너

이벤트가 발생했을떄 호출될 콜백 함수의 포인터를 전달하는 것이다.

document.addEventListener("click", console.log, false)
  • 이벤트는 사용자의 상호작용에 따라 발생할 수도, 발생하지 않을 수도 있다.
  • 함수를 이벤트가 발생하는 시점에 따라 실행시키기 위해 콜백인자로 전달한다.

타임 아웃

콜백 패턴의 대표적인 window 객체의 API는 setTimeout과 setInterval이다.

라이브러리에서의 콜백

  • 콜백은 라이브러리를 설계할 때 유용한 간단하고 강력한 패턴이다.
  • 소프트웨어 라이브러리에 들어갈 코드는 가능한 범용적이고 재사용할 수 있어야 한다.
  • 콜백은 이러한 일반화에 도움
  • 생각할 수 있는 모든 기능을 예측하고 구현할 필요는 없다.
  • 이는 라이브러리를 쓸데없이 부풀릴 뿐이고 대부분 사용자는 그런 커다란 기능들의 덩어리를 절대 필요로 하지 않는다.
  • 대신 핵심 기능에 집중하고 콜백의 형태로 "연결고리(hook)"를 제공하라.
  • 콜백함수를 활용하면 조금 더 쉽게 라이브러리 메서드를 만들고 확장하고 가다듬을 수 있다.

📘 4.3 함수 반환하기

함수는 객체이기 떄문에 반환 값으로 사용될 수 있다. 리턴 값으로 데이터의 값이나 배열을 반환할 필요가 없다는 것. 함수를 리턴할 수도 있다고 입력 값에 따라 필요한 함수를 새로 만들 수도 있다.

var setup = function () {
  console.log(1)
  return function () {
      console.log(2)
  }
}

var my = setup()
my()

setup()은 반환된 함수를 감싸고 있어서 클로저를 생성한다. 클로저는 반환되는 함수에서는 접근할 수 있지만 코드 외부에서는 접근할 수 없어서 비공개 데이터 저장을 위해 사용할 수 있다.

예제

아래 예제로 클로저를 정확히 이해할 수 있다.


var setup = function () {
  var count = 0 
  return function () {
      return(count += 1)
  }
}

var next = setup()
next() // 1
next() // 2
next() // 3 

주의!! 클로저를 만들기 위해 반환값을 함수로 해야만 만들 수 있는 것은 아니라는 것을 기억하자

Q. useState을 클로저 관점으로 생각해보면 아래와 같은 코드가 맞는 코드인가? 라는 궁금증이 생겼다.

const useState = (initValue) => {
	let value = initValue;
	const setValue = (newValue) => {
	value = newValue
	}
	return [value, setValue]
}

위에서 정리한대로 반환값이 함수가 아니어도 해당 value를 수정하기 위해선 setValue라는 함수를 통해서만 value를 수정할 수 있다는 점에서 클로저를 만든 것이 맞다고 스터디원들과 결론을 맺었다.

📘 4.4 자기 자신을 정의하는 함수

  • 함수가 어떤 초기화 준비 작업을 단 한 번만 수행할 때 유용하는데? 음
  • 단점으론 자기 자신을 재정의한 이후에는 이전에 원본 함수에 추가했던 프로퍼티들을 모두 찾을 수 없게 된다.

예를 들어

let scareMe = function () {
    console.log("once!");
    scareMe = function () {
        console.log("after once");
    }
}

scareMe(); // once!
scareMe(); // after once!
scareMe(); // after once!
scareMe(); // after once!

📘 4.5 즉시 실행 함수

함수가 선언되자마자 실행되도록 하는 문법이다.

(function () {
    console.log('hey')
}()) //hey

(function () {
    console.log('hey')
})()// hey
  • 함수 표현식으로만 동작한다. (함수 선언문으로는 동작하지 않는다.)
  • 함수가 즉시 실행될 수 있도록 마지막에 괄호쌍을 추가한다.
  • 전체 함수를 괄호로 감싼다. (함수를 변수에 할당하지 않을 경우에만)

초기화 단계가 완료될 때 까지만 필요한 임시변수들을 담아놓기 위한 방법이 즉시 실행 함수 패턴이다!! 블록 스코프를 임의로 만드는 것.

즉시 실행 함수의 반환값

var getResult = (function(){
  var res = 2 + 2;
  return function () {
      return res
  }
}())

getResult()

이 코드를 보고 알게 된 것은 res 값에 접근이 불가하다는 것. 왜 즉시 실행함수 패턴을 사용하는가 찾아보니 전역 스코프가 아닌 독자적인 스코프에 값을 저장하고 싶을 때 사용한다는 것을 알게 됐다.

📘 4.6 즉시 객체 초기화

Init 메소드를 사용한다.

({
  maxWidth : 600,
  maxHeight: 400,
  gimmeMax : function (){
    return this.maxWidth + "x" + this.maxHeight
  },  
  init : function (){
    console.log(this.gimmeMax())
}
}).init() // '600x400'

즉시 객체 초기화 장점

즉시 실행 함수 패턴과 마찬가지로 전역 네임스페이스를 보호하는 특징이 있다.
정리해보면 4.5와 4.6은 전역 스코프의 namespace를 침범하지 않으려는 방법임을 기억하자.

📘 4.7 초기화 시점의 분기

최적화 패턴의 일환으로 브라우저 탐지가 전형적인 예라고 한다. 음??! 아직 잘 모르겠다.

📘 4.8 설정 객체 패턴

특징

  1. 매개 변수와 순서를 기억할 필요가 없다.
  2. 선택적인 매개변수를 안전하게 생략할 수 있다.
  3. 읽기 쉽고 유지보수하기 편하다.
  4. 매개 변수를 추가하거나 제거하기가 편하다.
  5. 객체 구조 분해 할당

예시를 살펴보면

-function createCar(name, brand, color, type) {
+function createCar({ name, brand, color, type }) {
    return {
        name,
        brand,
        color,
        type
    }
}

-createCar('아이오닉5', '현대', 'red', '전기')
+createCar({
+    name : '아이오닉5', 
+    brand : '현대', 
+    color : 'red', 
+    type : '전기' 
+})

현재 구조 분해 할당의 개념과 동일하다는 것을 알 수 있었다.

📘 4.9 커리(Curry)

함수 적용

  • 순수한 함수형 프로그래밍 언어에서, 함수는 불리거나 호출된다고 표현하기보다 적용(apply)된다고 표현된다.
  • 함수 호출이라는 것은 사실상 함수 적용을 가리키는 syntatic sugar이나 다름없다고 한다. 새로운 관점이라 신선하다고 느꼈다.
  • 함수를 적용하는 것과 === 호출하는 것 모두 경과는 같다고 한다.
  • apply는 두 개의 매개변수를 받는다.
    1. 이 함수 내에 this와 바인딩할 객체
  • null이면 전역 객체를 가리킨다.
    1. 배열 또는 인자로 함수 내부에서 배열과 비슷한 형태의 argument 객체로 사용
var sayHi = function (who) {
  return "Hello" + (who ?"," + who : '') + "!";
}

sayHi() // Hello!
sayHi('world') // Hello, world!
sayHi.apply(null,['hello']) //Hello,hello!
]

위에서 호출과 적용 모두 결과가 동일하다?! 신기하다..

apply 매소드 사용법

두 개의 매개변수를 받고 첫 번째 인자는 함수 내에 this와 바인딩할 객체, 두 번째는 배열 또는 arguments가 필요하다.

var alien = {
  sayHi : function (who) {
     return "Hello" + (who ?"," + who : '') + "!";
  }
}

alien.sayHi('world') // Hello,world
sayHi.apply(alien,["humans"]) // Hello, humans!

call 매소드 사용법

apply와 동일한데 arguments의 매개변수가 하나 일때 배열을 만들지 않아도 되도록 한다.

var alien = {
  sayHi : function (who) {
     return "Hello" + (who ?"," + who : '') + "!";
  }
}

alien.sayHi('world') // Hello,world
sayHi.call(alien,"humans") // Hello, humans!

부분적인 적용을 위한 커링

커링은 함수를 변형하는 과정이다.

function add(x,y) {
  var oldx = x, oldy = y;
  if(typeof oldy === 'undefined'){
    return function (newy){
      return oldx + newy
    }
  }
  return x + y
}

typeof add(5)
add(3)(4)

add() 적용이 아닌, 호출할 때, add가 반환하는 내부 함수에 클로저를 만든다. 클로저 (반환 함수 기준 외부 환경)는 x,y 인자를 비공개 변수인 oldx ,oldy로 저장한다. 장황해서 더 간단한 예제로 책에서 소개해준다.

oldx와 oldy가 없고 암묵적으로 x를 클로저에 저장시킨 코드

function add(x,y) {
  if(typeof y === 'undefined'){
    return function (y){
      return x + y
    }
  }
  return x + y
}

typeof add(5)
add(3)(4)

커링을 활용하여 어떤 함수에도 적용되는 방식으로 구현한 로직

function currying (fn) {
  var slice = Array.prototype.slice,
      stored_args = slice.call(arguments,1)
  return function () {
    var new_args = slice.call(arguments),
        args = stored_args.concat(new_args)
    return fn.apply(null,args)
  }
}

커링 사용 이유

대부분의 매개 변수가 항상 비슷할 때 사용할 수 있다. 부분적으로 적용해놓고 바뀌는 값만 매개변수로 받을 수 있게 하려고 사용된다.

📘 4.10 요약

  1. 함수는 일급 객체이다.
    1. 값으로 전달 될 수 있고
    2. 프로퍼티와 메서드를 확장할 수 있다.
  2. 함수는 지역 스코프를 제공한다.
  3. 패턴 학습
    1. API 패턴
      1. 콜백 패턴
      2. 설정 객체
      3. 함수 반환
      4. 커링 : 원분 함수와 매개변수 일부를 물려받는 새로운 함수를 생성하는 것.
    2. 초기화 패턴
      1. 즉시 실행 함수
      2. 즉시 객체 초기화
      3. 초기화 시점의 분기
    3. 성능 패턴
      1. 메모이제이션 패턴
      2. 자기선언 함수

마치며 🌱

자바스크립트의 함수에 대해 정리해보는 시간을 가졌다. 자바스크립트에 함수는 일급 객체이기에 다른 데이터 타입과 마찬가지로 값으로 사용할 수 있다. 그 말인 즉 슨 함수의 인자 혹은 반환 값으로 사용할 수 있다는 것이다. 또 다른 특징은 스코프를 제공해준다는 것이다. 이 특징으로 말미암아 과거에는 함수를 선언하여 지역 스코프를 만들어 전역 네임 스페이스를 안전하게 관리하던 시절이 있다는 것을 알게 됐다.

profile
https://danny-blog.vercel.app/ 문제 해결 과정을 정리하는 블로그입니다.

0개의 댓글