[Day 23] 함수형 프로그래밍 + 객체 더 알아보기

함수형 프로그래밍

고차 함수 (Higher-order Function)

  • 함수를 인수로 받는 함수, 또는 함수를 반환하는 함수

    // 함수를 인수로 받는 함수
    function func2(f) {
      f();
    }
      
    // 함수를 반환하는 함수
    function func1() {
      return function() {};
    }
    
  • 다른 함수의 인수로 넘겨지는 함수를 콜백(callback) 이라고 부르기도 한다.

    [1, 2, 3].filter(item => item % 2 === 0)
    // item => item % 2 === 0
    // 이 부분이 콜백이다.
    

클로저 (Closure)

  • 일반적으로 안쪽 스코프에서 정의된 변수는 바깥 스코프에서 접근할 수 없다.

  • 만약 안쪽 스코프에서 만들어진 함수에서 바깥 스코프의 변수를 사용하고 있다면, 이 함수를 통해서 변수를 계속 사용할수 있다.

  • 바깥 스코프에 있는 변수를 가져다 사용하는 함수와, 변수가 저장되는 저장소클로저(closure)라고 부릅니다.

    function func1(x) {
      // 여기서 반환되는 함수는 바깥 스코프에 있는 변수 `x`를 사용하고 있습니다.
      return function () {	// 바깥 스코프에 있는 변수 x를 사용하는 이 함수가 클로저(closure)가 된다.
        return x;
      }
    }
      
    const func2 = func1(1);
      
    // `func1`의 실행은 끝났지만, `func2`를 통해서 변수 `x`를 사용할 수 있습니다.
    console.log(func2()); // 1
    
    const arr = [];
      
    for (let i = 0; i < 10; i++) {
      // 여기서 만들어진 함수는 바깥 스코프에 있는 변수 `i`를 사용하고 있습니다.
      arr.push(function() {	// 바깥 스코프의 변수 i를 사용하는 이 함수가 클로저(closure)가 된다.
        return i;
      });
    }
      
    // for 루프의 실행은 끝났지만, 루프 안에서 만들어진 함수가 배열에 저장되어 있습니다.
    // 배열 안에 들어있는 함수를 통해, 해당 함수가 만들어진 시점의 변수 `i`를 사용할 수 있습니다.
    console.log(arr[2]()); // 2
    console.log(arr[5]()); // 5
    
  • 클로저와 고차 함수

    // 특정한 방식으로 동작하는 함수를 만들어내는 고차 함수를 작성할 수 있습니다.
    function makeAdder(x) {
      return function (y) {
        return x + y;
      }
    }
      
    [1, 2, 3].map(makeAdder(2)); // [3, 4, 5]
    
  • 클로저의 성질을 이용해 데이터를 숨기고 정해진 방법을 통해서만 데이터에 접근할 수 있도록 제한을 두는 데 활용할 수 있다.

    function makeCounter(x = 1) {
      return function() {
        return x++;
      }
    }
    // `x`를 직접 변경할 수 있는 방법이 없습니다!
      
    const counter = makeCounter();
    console.log(counter()); // 1
    console.log(counter()); // 2
    
    function personFactory(initialAge) {
      let age = initialAge;
      return {
        getOlder() {
          age++;
        },
        getAge() {
          return age;
        }
      };
    }
    // `age`를 직접 변경할 수 있는 방법이 없습니다!
    

화살표 함수와 고차 함수

  • 화살표 함수 문법을 이용하면 코드량을 줄여서 고차 함수를 만들 수 있다.

    // 반환값이 화살표 함수인 화살표 함수
    const makeAdder = x => y => x + y;
      
    const add2 = makeAdder(2);
    add2(3); // 5
    
    const makeCounter = (x = 1) => () => x++;
      
    const counter = makeCounter();
    console.log(counter()); // 1
    console.log(counter()); // 2
    

재귀 함수 (Recursive Function)

  • 함수 내부에서 자기 자신을 호출하는 함수

  • 루프와 재귀 함수

    // 1부터 N까지 더하는 재귀 함수
    function sumByRec(n) {
      if(n === 1) {	// 재귀 함수의 종료 조건
        return 1	// return은 호출된 해당 함수만을 빠져나간다.
      } else {
        return sumByRec(n - 1) + n	// sumByRec(n-1)의 값이 무엇인지 알지 못하기 때문에 함수를 일시중지 시켜놓고 다시 함수를 돈다.
      }
    }
      
    sumByRec(5)
    
    • 재귀 함수는 문제를 같은 형태의 부분 문제로 쪼갤 수 있을 때 활용할 수 있다.
    // 재귀 함수를 이용한 피보나치 수
    function fiboRec(n) {
      if(n === 0) {			// 재귀 함수의 종료 조건
        return 0
      } else if(n === 1) {	// 재귀 함수의 종료 조건
        return 1
      } else {
        return fiboRec(n - 2) + fiboRec(n - 1)
      }
    }
      
    fiboRec(5)
    
    • 재귀 함수를 잘 사용하면 루프를 사용할 때 보다 코드의 의미가 명확해지고 코드의 길이가 줄어든다.
  • 주의할 점

    • 재귀 함수가 실행되는 동안에는 종료되지 않은 함수가 많이 생기게 되므로, 코드의 실행 속도가 느려지거나 메모리에 부담을 줄 수 있다.
    • Javascript 구동 환경에선느 특정 깊이 이상의 재귀 호출이 일어날 수 없도록 제한을 두고 있다. (Chrome 브라우저는 대략 10000번 정도 발생하면 에러)

객체 더 알아보기

객체 자신의 속성 (Own Property)

  • 속성 접근자 또는 in 연산자를 통해 객체의 속성에 접근할 때 객체 자신의 속성이 반환될 수도 있고, 프로토타입으로부터 상속받은 속성이 반환될 수도 있다. (구분 불가)
  • 객체 자신이 특정 속성을 가지고 있는지를 확인하기 위해서는 Object.prototype.hasOwnProperty 메소드를 사용해야 한다.

데이터 속성(Data Property)의 부수 속성(Property Attribute)

  • 각 속성마다의 동작 방식 같은 정보는 속성의 부수 속성에 숨겨져 있다.
  • 객체의 부수 속성을 알아보려면 Object.getOwnPropertyDescriptor 라는 정적 메소드를 사용해 부수 속성을 나타내는 객체를 얻을 수 있다. 이 객체를 속성 기술자(property descriptor) 라고 부른다.
const obj = {prop: 1};

Object.getOwnPropertyDescriptor(obj, 'prop');
// { value: 1, writable: true, enumerable: true, configurable: true }

Object.getOwnPropertyDescriptor(Math, 'PI');
// { value: 3.141592653589793, writable: false, enumerable: false, configurable: false }
  • 데이터 속성(data property)에 대한 속성 기술자는 네 가지 속성을 갖는다.
    • value : 속성에 어떤 값이 저장되어 있는지
    • writable : 변경할 수 있는 속성인지
    • enumerable : 열거 가능한 속성인지 (내용 출력 가능한지)
    • configurable : 부수 속성을 변경하거나 속성을 삭제할 수 있는지

속성 기술자를 통해 객체의 속성 정의하기

  • Object.create 정적 메소드의 두 번째 인수에 속성 기술자 객체를 입력해서 객체의 속성을 정의할 수 있다.
  • 이미 존재하는 객체의 속성 정의하려면 Object.defineProperty 또는 Object.defineProperties 정적 메소드를 이용해야 한다.

접근자 속성(Accessor Property)과 그 부수 속성

  • getter와 setter가 정의된 속성을 접근자 속성이라고 한다.

    const obj = {
      
      // 메소드 이름 앞에 `get`을 써주면, 이 메소드는 getter 메소드가 됩니다.
      get prop() {
        console.log('getter가 호출되었습니다.');
        return this._hidden;
      },
      
      // 메소드 이름 앞에 `set`을 써주면, 이 메소드는 setter 메소드가 됩니다.
      set prop(arg) {
        console.log('setter가 호출되었습니다.');
        this._hidden = arg;
      }
    }
      
    // `set prop` 메소드가 `1`을 인수로 해서 호출됩니다.
    obj.prop = 1;
      
    // `get prop` 메소드가 호출되고 해당 메소드의 반환값을 읽어옵니다.
    obj.prop; // 1
    
    • 접근자 속성에 대한 속성 기술자는 4 가지 속성을 갖는다.
      • get : getter 함수 (속성을 읽어올 때 호출)
      • set : setter 함수 (속성을 변경할 때 호출)
      • enumerable : 열거 가능한 속성인지
      • configurable : 부수 속성을 변경하거나 속성을 삭제할 수 있는지
  • 접근자 속성도 속성 기술자를 통해 정의할 수 있다.

    function Money(won) {
      this._won = won;
    }
      
    // `Object.defineProperties` 정적 메소드를 사용해서
    // 속성 기술자를 통해 특정 객체의 여러 속성을 한꺼번에 정의할 수 있습니다.
    Object.defineProperties(Money.prototype, {
      // `Money.prototype` 객체에 `won` 이라는 속성을 정의합니다.
      won: {
        get: function() {
          return this._won;
        },
        set: function(arg) {
          this._won = arg;
        }
      },
      // `Money.prototype` 객체에 `dollar` 라는 속성을 정의합니다.
      dollar: {
        get: function() {
          return this._won / 1086;
        },
        set: function(arg) {
          this._won = arg * 1086;
        }
      }
    });
      
    const w = new Money(1086);
      
    w.won += 1086;
    console.log(w.dollar); // 2
      
    w.dollar += 1;
    console.log(w.won); // 3258
    

객체의 속성 열거하기

  • 객체의 속성을 열거할 때 사용할 수 있는 방법들

    • Object.keys : 객체 자신의 속성열거 가능한(enumerable) 속성의 이름을 배열로 반환한다.
    • Object.values : 객체 자신의 속성열거 가능한(enumerable) 속성의 속성 값을 배열로 반환한다.
    • Object.entries : 객체 자신의 속성열거 가능한(enumerable) 속성의 이름과 값을 배열로 반환한다.
    • Object.getOwnPropertyNames : 객체 자신의 모든 속성의 이름을 배열로 반환한다. (열거 불가능한 속성도 포함)
    • for...in 구문 : 객체 자신의 속성 및 상속받은 속성열거 가능한(enumerable) 속성의 이름을 배열로 반환한다.
      • 꼭 필요한 경우가 아니라면 쓰지 말자!! 불편하다 ㅠㅠ
    const obj = {
      a: 1,
      b: 2
    };
      
    Object.keys(obj); // ['a', 'b']
    

얕은 복사(Shallow Copy) & 깊은 복사(Deep Copy)

  • 객체 복사

    • Object.assign 정적 메소드는 인수로 받은 객체들의 모든 열거 가능한 속성대상 객체에 복사한다.

      const obj = {};
      Object.assign(obj, {a: 1}, {b: 2});
          
      console.log(obj); // { a: 1, b: 2 }
      
    • 객체를 복제하는 수단으로도 사용된다.

      const obj = {
        a: 1,
        b: 2
      };
          
      // 빈 객체를 대상으로 `Object.assign`을 사용하면, 객체를 간단히 복제할 수 있습니다.
      const obj2 = Object.assign({}, obj);
      console.log(obj2); // { a: 1, b: 2 }
      
    • 주의할 점

      • 객체가 중첩되어 있다면, 내부에 있는 객체는 복제되지 않는다. (얕은 복사)
      • Object.assign을 통해 속성 값이 복사될 때, 실제로 복사되는 것은 중첩된 객체가 아니라 그에 대한 참조이다.
        • 복사본의 중첩된 객체의 속성을 변경하면 원본의 값도 바뀌게 된다.
      • Array.prototype.slice() 도 마찬가지로 얕은 복사다.
    • 깊은 복사

      • 중첩된 자료구조까지 모두 복사하는 것
      • 깊은 복사를 자바스크립트에서 구현하는 것은 고려할 것이 많아서 어렵다. 그래서 관련 라이브러리를 사용하는 것이 더 낫다.

Object.preventExtensions

  • Object.preventExtension 정적 메소드는 `[[Extensible]] 속성을 false로 바꿔주는 역할을 한다. 즉, 객체에 속성을 추가하는 것이 불가능해진다.
  • Object.seal : 객체에 속성을 추가하거나, 이미 존재하는 속성을 삭제하는 것이 불가능해진다.
  • Object.freeze : 객체에 속성을 추가하거나, 이미 존재하는 속성을 변경/삭제하는 것이 불가능해진다.
메소드 속성 추가 속성 읽기 속성 변경 속성 삭제 및 재정의
Object.preventExtensions X O O O
Object.seal X O O X
Object.freeze X O X X

댓글남기기