[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
: 부수 속성을 변경하거나 속성을 삭제할 수 있는지
- 접근자 속성에 대한 속성 기술자는 4 가지 속성을 갖는다.
-
접근자 속성도 속성 기술자를 통해 정의할 수 있다.
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 |
댓글남기기