JavaScript 공부 8주차

  • 람다식
    • 함수 표현식
    • 함수 선언문 vs 함수 표현식
    • 화살표 함수
  • 익명 메서드
  • 자유 변수와 묶인 변수
  • 캡쳐
    • Variable capturing
    • Lambda capturing
  • 클로저 (== upvalue)
    • 람다식과의 차이점
  • 콜백 함수
    • 콜백 지옥을 벗어나는 법 (선택)

람다식

람다식이란 식별자 없이 실행 가능한 함수를 말한다.

함수 표현식

JavaScript는 객체 지향 프로그래밍 언어로, 함수 또한 객체의 한 타입이다. 그러므로 함수는 값처럼 변수에 할당 할 수 있고, 프로퍼티 값이 될 수도 있으며 배열의 요소가 될 수도 있다.
즉 함수 리터럴로 생성한 함수 객체를 변수에 할당하여 정의할 수 있는데, 이를 함수 표현식이라고 한다.

// 함수 표현식
var add = function (x,y) {
return x + y
};
console.log(add(2,5)); // 7

함수 선언문과의 차이

함수 선언문은 코드 실행 시 함수 선언문이 코드의 가장 위쪽으로 끌어올려지는 호이스팅이 일어난다. 그러므로 함수 선언문은 정의되기 이전에도 호출이 가능하다.
하지만 var 키워드로 표현된 변수는 호이스팅이 일어나긴 하지만 undefinded로 평가된다.
그러므로 함수 선언문으로 정의한 함수는 선언 이전에 호출하여도 함수가 호출되지만 함수 표현식으로 정의된 함수는 선언 이전에 호출하면 함수가 호출되지 않는다.

화살표 함수

ES6에서 도입된 화살표 함수는 function 키워드 대신 =>를 통해 간략한 방법으로 함수를 선언할 수 있게 한다.

const multiply = (x,y) => {x * y};
mulityply(2,3); // 6

화살표 함수는 익명 함수로 사용되고 매개변수가 한 개인 경우 소괄호를 생략할 수 있다. 다만 매개변수가 없다면 소괄호를 생략할 수 없다.
또한 함수 내부의 문이 표현식이라면 중괄호를 생략할 수 있다.

const multiply = () => x ** 2;
mulityply(2); // 4

만약 객체 리터럴을 반환하는 경우 객체 리터럴을 소괄호로 바꿔 주어야 한다. 소괄호로 감싸지 않는다면 객체 리터럴의 중괄호를 함수 몸체를 감싸는 중괄호로 잘못 해석한다.

const create = (id, content) => ({id, content});
const create(1, 'JavaScript') // {id: 1, content: "JavaScript"};

함수 몸체가 여러 개의 문으로 구성된다면 함수 몸체를 감싸는 중괄호를 생략할 수 없으며, 반환값이 있다면 명시적으로 반환해야 한다.

const sum = (a, b) => {
const result = a + b;
return result;
}

화살표 함수는 일반 함수와 다른 특징을 가진다.
화살표 함수는 인스턴스를 생성할 수 없다.

const Foo = () => {};
new Foo(); // TypeError: Foo is not a constructor

중복된 매개변수를 가질 수 없다.

function normal(a,a) (return a + a);
console.log(normal(1,2)); // 4
const arrow = (a,a) => a + a; // SyntaxError: Duplicate parameter name not allowed in this context

화살표 함수는 this, argument, super 바인딩을 갖지 않는다. 만약 화살표 함수 내에서 this, argument, super을 참조하면 상위 스코프의 this, argument, super를 참조한다.

익명 메소드

위의 몇몇 코드처럼 함수 리터럴의 함수 이름을 생략할 수도 있는데, 이를 익명 함수라고 한다. 함수 이름은 함수 내부에서만 유효한 식별자이므로 함수 이름으로는 함수를 호출할 수 없다.

var add = function foo (x,y) {
return x + y
};
console.log(foo(2,5)); // ReferenceError: foo is not defind

자유 변수와 묶인 변수

function adder(a) {
	return function(b) {
	return a + b; }
}
var add5 = adder(5);
add5(10); // 15

add5의 입장에서 봤을 때, 자신의 스코프 내에 있는 b라는 변수는 인자로 받은 변수이고 해당 스코프 내에 갖혀있지만, a라는 변수는 어디서 와서 사용되는지 알 수 없다.
이때의 a를 자유 변수, b를 묶인 변수라고 한다.

캡쳐

Variable capturing

Lambda capturing

클로저

JavaScript 엔진은 함수를 어디서 호출했는지가 아니라 함수를 어디서 호출했는지에 따라 상위 스코프를 결정하는데 이를 렉시컬 스코프(정적 스코프)라고 한다.
함수가 정의되는 환경과 실행되는 환경이 다른 경우가 있으므로 함수는 자기가 실행되는 환경과는 상관없이 자신이 정의될 때의 상위 스코프를 기억해야 한다. 이를 위해 함수는 자신의 내부 슬롯 Environment에 상위 스코프의 참조를 기억한다.

const x = 1;
function outer() {
const x = 10;
const inner = function () { console.log(x);};
return inner;
}
// outer 함수를 호출하면 중첩 함수 inner을 반환한다.
// 그리고 outer 함수의 실행 컨텍스트는 제거된다.
const innerFunc = outer();
innerFunc(); // 10

outer 함수를 호출하면 inner 함수를 반환하고 생명 주기를 마감한다. 즉 outer 함수의 실행이 종료되면 실행 컨텍스트 스택에서 제거된다. 이때 outer 함수의 x값 10을 저장하고 있던 실행 컨텍스트가 제거되었으므로, outer 함수의 지역 변수 x 또한 생명 주기를 마감한다. 하지만 실행 결과는 outer 함수의 지역 변수 x의 값인 10을 나타낸다.
이처럼 외부 함수보다 중첩 함수가 더 오래 유지되는 경우 중첩 함수는 이미 생명 주기가 종료된 외부 함수의 변수를 참조할 수 있다. 이러한 중첩 함수를 closure(클로저) 라고 부른다.
JavaScript의 모든 함수는 상위 스코프를 기억하므로 이론적으로 모든 함수는 클로저지만, 몇 가지 예외가 있다.
먼저 중첩 함수가 상위 스코프에서 어떠한 식별자도 참조하지 않는 경우 최적화를 통해 상위 클로저를 기억하지 않는다. 또한 중첩 함수가 외부 함수보다 생명 주기가 더 짧은 경우 생명 주기가 종료된 외부 함수를 참조할 수 있다는 클로저의 의미에 부합하지 않아 클로저라고 불리지 않는다.
클로저는 스코프가 소멸된 상태에서도 그에 대한 접근은 내부의 독립된 복사본을 통해 이루어지므로 캡슐화와 은닉화를 구현하는데 사용할 수 있다.

람다식과의 차이

콜백 함수

콜백 함수란 함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수이다. 이때 콜백 함수를 전달받은 함수를 고차 함수라고 한다.

function repeat(n,f) {
	for (var i = 0, i<n;, i++) { // 고차 함수
    f(i);
    }
}
var logAll = funtion(i) {
	console.log(i);      // 콜백 함수
    }
repeat(5,logAll); // 0 1 2 3 4

만약 콜백 함수가 고차 함수 내부에서만 사용된다면 익명 함수 리터럴로 정의함과 동시에 고차 함수 내부로 전달하는 것이 일반적이다.

repeat(5, function (i) {
	if (1 % 2) console.log(i);
}); // 1, 3

하지만 콜백 함수가 자주 호출된다면 함수 외부에서 콜백 함수를 따로 정의하는게 효율적이다. 콜백 함수를 익명 리터럴로 정의하면서 곧바로 고차 함수에 전달하면 고차 함수가 호출될 때마다 콜백 함수가 새로 생성되기 때문이다.

콜백 지옥을 벗어나는 법

콜백 지옥이란 비동기 호출이 필요한 프로그램을 작성할 때 자주 벌어지는 현상인데, 콜백 함수 호출이 중첩되어 가독성이 떨어지고 디버깅이 어려워진다는 단점이 있다.
콜백 지옥을 해결하기 위해서는 ES6에서 도입된 promise 생성자 함수를 사용한다.
promise는 비동기 연산의 처리 상태와 처리 결과를 관리하는 객체로써 비동기 처리가 성공하면 resolve 함수를, 실패하면 reject 함수를 호출한다.

출처

좋은 웹페이지 즐겨찾기