728x90
모던 자바스크립트 Deep Dive을 요약한 내용입니다.
12.1 함수란?
- 함수는 자바스크립트에서 가장 중요한 핵심 개념
- 자바스크립트의 핵심 개념인 스코프, 실행 컨텍스트, 클로저, 생성자 함수에 의한 객체 갱성, 메서드, this, 프로토타입, 모듈화 등이 모두 함수와 깊은 관련이 있다
- 함수는 자바스크립트를 정확히 이해하고 사용하기 위해 피해갈 수 없는 핵심 중의 핵심
- 수학의 함수는 “입력(input)”을 받아 “출력(output)”을 내보내는 일련의 과정을 정의한 것
- f(x, y) = x + y 라는 함수를 정의하고 이 함수에 입력 2, 5를 전달하면 함수는 정의된 일련의 과정, 즉 x + y를 싫애하여 7을 출력
- 프로그래밍 언어의 함수도 수학의 함수와 같은 개념
- 함수 f(x, y) = x + y를 자바스크립트의 함수로 표현
- // f(x, y) = x + y function add(x, y) { return x + y } add(2, 5) // 7
- 함수는 일련의 과정을 문으로 구현하고 코드 블록으로 감싸서 하나의 실행 단위로 정의한 것
- 함수 내부로 입력을 전달 받은 변수를 매개 변수, 입력을 인수, 출력을 반환값이라 한다.
12.2 함수를 사용하는 이유
- 함수는 필요할 때 여러번 호출할 수 있다.
- 몇 번이든 재사용이 가능
- 코드의 중복을 억제하고 재사용성을 높이는 함수는 유지보수의 편의성을 높이고 실수를 줄여 코드의 신뢰성을 높이는 효과가 있다.
- 함수는 객체 타입의 값이다.
- 이름(식별자)를 붙일 수 있다.
- 적절한 함수 이름은 함수의 내부 코드를 이해하지 않고도 함수의 역활을 파악할 수 있게 돕는다.(코드 가독성 향상)
12.3 함수 리터럴
- 자바스크립트의 함수는 객체 타입의 값이다.
- 함수도 함수 리터럴로 생성할 수 있다.
- 함수 리터럴은 function 키워드, 함수 이름, 매개 변수 목록, 함수 몸첼 구성된다.
- var f = function add(x, y) { return x + y }
- 함수 리터럴의 구성 요소
- 함수 이름
- 함수 이름은 식별자다. 따라서 식별자 네이밍 규칙을 준수해야 한다.
- 함수 이름은 함수 몸체 내애서만 참조할 수 있는 식별자다.
- 함수 일므은 생략할 수 있다. 이름이 있는 함수를 기명 함수, 이름이 없는 함수를 무명/익명 함수라 한다.
- 매개변수 목록
- 0개 이상의 매개변수를 소괄호로 감싸고 쉼표로 구분한다.
- 각 매개변수에는 함수를 호출할 때 지정한 인수가 순서대로 할당된다.
- 매개변수 목록은 순서에 의미가 있다.
- 매개변수는 함수 몸체 내에서 변수와 동일하게 취급된다.
- 매개변수도 변수와 마찬가지로 식별자 네이밍 규칙을 준수해야 한다.
- 함수 몸체
- 함수가 호출되었을 때 일괄적으로 실행될 문들을 하나의 실행 단위로 정의한 코드 블록이다.
- 함수 몸체는 함수 호출에 의해 실행된다.
- 함수 이름
- 함수는 객체지만 일반 객체와는 다르다
- 일반 객체는 호출할 수 없지만 함수는 호출할 수 있다.
- 일반 객체에는 없는 함수 객체만의 고유한 프로퍼티를 갖는다.
12.4 함수 정의
- 함수를 정의하는 방법에는 4가지가 있다.
- 함수 선언문
- function add(x, y) { return x + y }
- 함수 표현식
- var add = function(x, y) { return x + y }
- Function 생성자 함수
- var add = new Function('x', 'y', 'return x + y')
- 화살표 함수(ES6)
- var add = (x, y) => x + y
- 모든 함수 정의 방식은 함수를 정의한다는 면에서는 동일하다.
- 미묘하지만 중요한 차이가 있다.
<aside> 💡 변수는 ‘선언'한다고 했지만 함수는 ‘정의'한다고 표현했다. 함수 선언문이 편가되면 식별자가 암묵적으로 생성되고 함수 객체가 할단된다. 따라서 ECMAScript 사양에서도 변수에는 선언. 함수에는 정의라고 표현한다.
</aside>
12.4.1 함수 선언문
- 함수 선언문을 사용해 함수를 정의하는 방식
- function add(x, y) { return x + y } console.dir(add) // ƒ add(x, y) console.log(add(2, 5)) // 7
- 함수 리터럴은 함수 이름을 생략할 수 있으나 함수 선언문은 함수 이름을 생략할 수 없다.
- function (x, y) { return x + y } // Uncaught SyntaxError: Function statements require a function name
- 함수 선언문은 표현식이 아닌 문이다.
- 함수 선언문도 표현식이 아닌 문이므로 변수에 할당할 수 없다.
- 예제를 실행해보면 함수 선언문이 변수에 할당되는 것처럼 보인다.
var add = function add(x, y) { return x + y } console.log(add(2, 5))
- 자바스크립트 엔진이 코드의 문맥에 따라 동일한 함수 리터럴을 표현식이 아닌 문인 함수 선언문으로 해석하는 경우와 표현식인 문인 함수 리터럴 표현식으로 해석하는 경우가 있기 때문
- 함수 선언문은 함수 이름을 생략할 수 없다는 점을 제외하면 함수 리터럴과 형태가 동일하다.
- 함수 이름이 있는 기명 함수 리터럴은 함수 선언문 또는 함수 리터럴 표현식으로 해석될 가능성이 있다는 의미
- ??
- 자바스크립트 엔진은 생성된 함수를 호출하기 위해 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하고, 거기에 함수 객체를 할당한다.
- 함수는 함수 이름으로 호출하는 것이 아니라 함수 객체를 가르키는 식별자로 호출한다.
12.4.2 함수 표현식
- 함수는 값처럼 변수에 할당할 수 있고 프로퍼티 값이 될 수도 있으며 배열의 요소가 될 수도 있다.
- 자바스크립트의 함수는 일급 객체이다.
- 함수 선언문으로 정의한 add 함수를 함수 표현식으로 바꿔서 정의
- var add = function(x, y) { return x + y } console.log(add(2, 5))
- 함수 리터럴의 함수 이름은 생략할 수 있다.
- 이러한 함수를 익명 함수라 한다.
- 함수를 호출할 때는 함수 이름이 아니라 함수 객체를 가르키는 식별자를 사용해야한다.
- 함수 이름은 함수 몸체 내부에서만 유효한 식별자이므로 함수 이름으로 함수를 호출할 수 없다.
var add = function foo(x, y) { return x + y } console.log(add(2, 5)) // 7 console.log(foo(2, 5)) // Uncaught ReferenceError: foo is not defined
- 함수 선언문은 “표현식이 아닌 문"이고 함수 표현식은 “표현식인 문"이다.
- 미묘하지만 중요한 차이가 있다.
12.4.3 함수 생성 시점과 함수 호이스팅
console.log(add) // ƒ add(x, y)
console.log(sub) // undefined
console.log(add(2, 5)) // 7
console.log(sub(2, 5)) // Uncaught TypeError: sub is not a function
// 함수 선언문
function add(x, y) {
return x + y
}
// 함수 표현식
var sub = function (x, y) {
return x + y
}
- 함수 선언문으로 정의한 함수는 함수 선언문 이전에 호출 할 수 있다.
- 함수 표현식으로 정의한 함수는 함수 표현식 이전에 호출 할 수 없다.
- 함수 선언문으로 정의한 함수와 함수 표현식으로 정의한 함수의 생성 시점이 다르기 때문이다.
- 함수 선언문으로 함수를 정의하면 런타임 이전에 함수 객체가 먼저 생성된다.
- 자바스크립트 엔진은 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하고 생성된 함수 객체를 할당한다.
- 함수 선언문이 코드의 선두로 끌어 올려진 것처럼 동작하는 자바스크립트 고유의 특징을 함수 호이스팅이라 한다.
- var 키워드로 선언된 변수는 undefined로 초기화되고, 함수 선언문을 통해 암묵적으로 생성된 식별자는 함수 객체로 초기화된다.
- var 키워드를 사용한 변수 선언문 이전에 변수를 참조하면 변수 호이스팅에 의해 undefined로 평가되지만 함수 선언문으로 정의한 함수를 함수 선언문 이전에 호추랗면 함수 호이스팅에 의해 호출이 가능하다.
- 변수 할당문의 값은 할당문이 실행되는 시점, 즉 런타임에 평가되므로 함수 표현식의 함수 리터럴도 할당문이 실행되는 시점에 평가되어 함수 객체가 된다.
- 함수 표현식으로 함수를 정의하면 함수 호이스팅이 발생하는 것이 아니라 변수 호이스팅이 발생한다.
- 함수 호이스팅은 함수를 호출하기전에 반드시 함수를 선언해야 한다는 당연한 규칙을 무시한다.
- 더글라스 크락포드는 함수 선언문 대신 함수 표현식을 사용할 것을 권장
12.4.4 Function생성자 함수
- 자바스크립트가 기본 제공하는 빌트인 함수인 Function 생성자 함수에 매개변수 목록과 함수 몸체를 문자열로 전달하면서 new 연산자와 함께 호출하면 함수 객체를 생서애서 반환한다.<aside> ⛔ Uncaught EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src chrome://resources chrome://test chrome://webui-test 'self'".
- </aside>
- var add = new Function('x', 'y', 'return x + y') console.log(add(2, 5))
- Function 생성자 함수로 함수를 생성하는 방식은 일반적이지 않으며 바람직하지도 않다.
- Function 생성자 함수로 생성한 함수는 클로저를 생성하지 않는 등. 함수 선언문이나 함수 표현식으로 생성한 함수와 다르게 동작한다.
- var add1 = (function () { var a = 10 return function (x, y) { return x + y + a } }()) console.log(add1(1, 2)) // 13 var add2 = (function () { var a = 10 return new Function('x', 'y', 'return x + y + a') }()) console.log(add2(1, 2)) // Uncaught EvalError: Refused to evaluate a string as JavaScript because
12.4.5 화살표 함수
- ES6에서 도입된 화살표 함수는 function 키워드 대신 화살표 ⇒를 사용해 좀더 간략한 방법으로 함수를 선언할 수 있다.
- 화살표 함수는 항상 익명 함수로 정의한다.
- const add = (x, y) => x + y console.log(add(2, 5)) // 7
- 화살표 함수는 기존의 함수 선언문 또는 함수 표현식을 완전히 대체하기 위해 디자인된 것은 아니다.
- 화살표 함수는 기존 함수보다 표현만 간단한 것이 아니라 내부 동작 또한 간략화되어 있다.
- 화살표 함수는 생성자 함수로 사용할 수 없음
- 기존 함수와 this 바인딩 방식이 다름
- prototype 프로퍼티가 없음
- arguments 객체를 생성하지 않음
12.5 함수 호출
12.5.1 매개변수와 인수
- 함수를 실행하기 위해 필요한 값을 함수 외부에서 함수 내부로 전달할 필요가 있는 경우, 매개변수를 통해 인수를 전달한다.
- 인수는 값으로 평가될 수 있는 표현식이여야한다.
- 인수는 함수를 호출할 때 지정하며, 개수와 타입에 제한이 없다.
- function add(x, y) { return x + y } var result = add(1, 2)
- 매개변수는 함수 몸체 내부에서만 참조할 수 있고 함수 몸체 외부에서는 참조할 수 없다.
- function add(x, y) { console.log(x, y) // 2 5 return x + y } add(2, 5) console.log(x, y) // Uncaught ReferenceError: x is not defined
- 함수는 매개변수의 개수와 인수의 개수가 일치하는지 체크하지 않는다.
- 인수가 부족해서 인수가 할당되지 않은 매개변수의 값은 undefined 이다.
function add(x, y) { return x + y } console.log(add(2)) // NaN
- 매개변수보다 인수가 더 많은 경우 초과된 인수는 무시된다.
- 초과된 인수는 그냥 버려지는 것은 아님
- 모든 인수는 암묵적으로 arguments 객체의 프로퍼티로 보관된다.
function add(x, y) { console.log(arguments) // Arguments(3) [2, 5, 10, callee: ƒ, Symbol(Symbol.iterator): ƒ] return x + y } console.log(add(2, 5, 10)) // 7
12.5.2 인수 확인
- 자바스크립트의 경우 함수를 정의할 떄 적절한 인수가 전달되었는지 확인할 필요가 있다.
- function add(x, y) { if (typeof x !== 'number' || typeof y !== 'number') { throw new TypeError('인수는 모든 숫자 값이어야 합니다.') } return x + y } console.log(add(2)) // Uncaught TypeError: 인수는 모든 숫자 값이어야 합니다. console.log(add('a', 'b')) // Uncaught TypeError: 인수는 모든 숫자 값이어야 합니다.
- 단축 평가를 사용해 매개변수에 기본값을 할당하는 방법도 있다.
- function add(a, b, c) { a = a || 0 b = b || 0 c = c || 0 return a + b + c } console.log(add(1, 2, 3)) // 6 console.log(add(1, 2)) // 3 console.log(add(1)) // 1 console.log(add()) // 0
- ES6에서 도입된 매개변수 기본값을 사용하면 함수 내에서 수행하던 인수 체크 및 초기화를 간소화할 수 있다.
- function add(a = 0, b = 0, c = 0) { return a + b + c } console.log(add(1, 2, 3)) // 6 console.log(add(1, 2)) // 3 console.log(add(1)) // 1 console.log(add()) // 0
12.5.3 매개변수의 최대 개수
- ECMAScript 사양에서는 매개변수의 최대 개수에 대한 명시적으로 제한하고 있지 않다.
- 물리적 한계는 있으므로 자바스크립트 엔진마다 매개변수의 최대 개수에 대한 제한이 있겠지만 충분히 많은 매개변수를 지정할 수 있다.
- 매개변수는 최대 몇개까지 사요앟는 것이 좋을까?
- 이상적인 매개변수 개수는 0개이며 적을 수록 좋다
- 매개변수는 최대 3개 이상을 넘지 않는 것을 권장
- 그 이상의 매개변수가 필요하다면 하나의 매개변수를 선언하고 객체를 인수로 전달하는 것이 유리하다.
- 이상적인 함수는 한가지 일만 해야하며 가급적 작게 만들어야 한다.
12.5.4 반환문
- 함수는 return 키워드와 표현식으로 이뤄진 반환문을 사용해 실행 결과를 함수 외부로 반환할 수 있다.
- function multiply(x, y) { return x * y } var result = multiply(3, 5) console.log(result) // 15
- 반환문은 두가지 역활을 한다.
- 반환문은 함수의 실행을 중단하고 함수 몸체를 빠져나간다.
- function multiply(x, y) { return x * y console.log('실행되지 않는다.') } console.log(multiply(3, 5)) // 15
- 반환문은 return 키워드 뒤에 오는 표현식을 평가해 반환한다.
- return 키워드 뒤에 반환값으로 사용할 표현식을 명시적으로 지정하지 않으면 undefined가 반환된다.
function foo() { return } console.log(foo()) // undefined
- 반환문은 생략할 수 있다.
- 함수 몸체의 마지막까지 실행한 후 암묵적으로 undefined를 반환한다.
function foo() { } console.log(foo()) // undefined
- return 키워드와 반환값으로 사용할 표현식 사이에 줄바꿈이 있으면 세미콜론 자동 삽입 기능에 의해 의도치 않은 결과가 발생할 수 있다.
- function multiply(x, y) { return x * y } console.log(multiply(3, 5)) // undefined
12.6 참조에 의한 전달과 외부 상태의 변경
- 원시 값은 값에 의한 전달, 객체는 참조에 의한 전달 방식으로 동작
- changeVal 함수는 매개변수를 통해 전달받은 원시 타입 인수와 객체 타입 인수를 함수 몸체에서 변경한다.
- primitive의 경우 원시 값은 변경 불가능한 값이므로 직접 변경할 수 없기 때문에 재할당을 통해 할당된 원시값을 새로운 원시 값으로 교체
- obj의 경우 객체는 변경 가능한 값이므로 직접변경할 수 있기 때문에 재할당 없이 직접 할당된 객체를 변경했다.
function changeVal(primitive, obj) {
primitive += 100
obj.name = 'Kim'
}
var num = 100
var person = { name: 'Lee' }
console.log(num) // 100
console.log(person) // {name: 'Lee'}
changeVal(num, person)
console.log(num) // 100
console.log(person) // {name: 'Kim'}
12.7 다양한 함수의 형태
12.7.1 즉시 실행 함수
- 함수 정의와 동시에 즉시 호출되는 함수를 즉시 실행 함수라 한다.
- 즉시 실행 함수는 단 한번만 호출되면 다시 호출할 수 없다.
(function () { var a = 3 var b = 5 return a * b }()) // 15
- 즉시 실행 함수는 함수 이름이 없는 익명 함수를 사용하는 것이 일반적이다.
- 함수 이름이 있는 기명 즉시 실행 함수도 사용할 수 있다.
- 그룹 연산자 (…) 내의 기명 함수는 함수 선언문이 아니라 함수 리터럴로 평가되며 함수 이름은 함수 몸체에서만 참조할 수 있는 식별자이므로 즉시 실행 함수를 다시 호출할 수 는 없다.
(function foo() { var a = 3 var b = 5 return a * b }()) // 15 foo() // Uncaught ReferenceError: foo is not defined
- 즉시 실행 함수는 반드시 그룹 연산자(…)로 감싸야 한다.
- 그룹 연산자가 없으면 오류 발생
function () { var a = 3 var b = 5 return a * b }() // Uncaught SyntaxError: Function statements require a function name
- 즉시 실행 함수도 일반 함수처럼 값을 반환할 수 있고 인수를 전달할 수도 있다.
- var res = (function () { var a = 3 var b = 5 return a * b }()) console.log(res) // 15 res = (function (a, b) { return a * b }(3, 5)) console.log(res) // 15
12.7.2 재귀 함수
- 함수가 자기 자신을 호출하는 것을 재귀 호출이라 한다.
- 재귀 함수는 반복되는 처리를 위해 사용한다.
- function countdown(n) { for (var i = n; i >= 0; i--) { console.log(i) } } countdown(10)
- 재귀 함수를 사용하여 만들어보기
- function countdown(n) { if (n < 0) return console.log(n) countdown(n - 1) } countdown(10)
- 재귀 함수를 사용하면 반복되는 처리를 반복문 없이 구현 할 수 있다.
- 팩토리얼은 재귀 함수로 간단히 구현할 수 있다.
- function factorial(n) { if (n <= 1) { return 1 } return n * factorial(n - 1) } console.log(factorial(0)) // 1 console.log(factorial(1)) // 1 console.log(factorial(2)) // 2 console.log(factorial(3)) // 6 console.log(factorial(4)) // 24 console.log(factorial(5)) // 120 console.log(factorial(6)) // 720 console.log(factorial(7)) // 5040
- 재귀 함수는 반복되는 처리를 반복문 없이 구현할 수 있는 장점이 있지만 무한 반복에 빠질 위험이 있고 이로 인해 스택 오버플로 에어를 발생시킬 수 있으므로 주의해서 사용해야한다
12.7.3 중첩 함수
- 함수 내부에 정의된 함수를 중첩 함수 또는 내부 함수라 한다.
- 중첩 함수를 포함하는 함수는 외부 함수라 부른다.
- 중첩 함수는 외부 함수 내부에서만 호출 할 수 있다.
- 일반적으로 중첩 함수는 자신을 초함하는 외부 함수를 돕는 헬퍼 함수의 역활을 한다.
- function outer() { var x = 1 function inner() { var y = 2 console.log(x + y) // 3 } inner() } outer()
- ES6부터 함수 정의는 문이 위치할 수 있는 문맥이라면 어디든지 가능하다.
- ES6이전에는 코드의 최상위 또는 다른 함수 내부에서만 정의할 수 있어으나 ES6부터는 if 문이나 for문 등의 코드 블록내에서도 정의할 수 있다.
- 호이스팅으로 인해 혼란이 발생할 수 있으므로 if 문이나 for 문 등의 코드 블록에서 함수 선언문을 통해 함수를 정의하는 것은 바람직하지 않다.
12.7.4 콜백 함수
- 어떤 일을 반복 수행하는 repeat 함수를 정의해 보자
- function repeat(n) { for (var i=0; i<n; i++) { console.log(i) } } repeat(5)
- repeat 함수의 반복문 내부에서 다른 일을 하고 싶다면 함수를 새롭게 정의해야 한다.
- 함수의 변하지 않는 공통 로직은 미리 정의해 두고, 경우에 따라 변경되는 로직은 추상화해서 함수외부에서 함수 내부로 전달
- 함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수를 콜백 함수라고 하며, 매개 변수를 토해 함수의 외부에서 콜백 함수를 전달받은 함수를 고차 함수라고 한다.
function repeat(n, f) { for (var i=0; i<n; i++) { f(i) } } var logAll = function(i) { console.log(i); } repeat(5, logAll) var logOdds = function(i) { if (i % 2) { console.log(i) } } repeat(5, logOdds)
- 고차 함수는 콜백 함수를 자신의 일부분으로 합성한다.
- 고차 함수는 매개변수를 통해 전달받은 콜백 함수의 호출 시점을 결정해서 호출 한다.
- 콜백 함수는 고차함수에 의해 호출되며 고차함수는 필요에 따라 콜백 함수에 인수를 전달할 수 있다.
// 익명 함수 리터럴을 콜백 함수로 고차 함수에 전달 repeat(5, function(i) { if (i % 2) { console.log(i) } })
- 콜백 함수는 함수평 프로그래밍 패러다임뿐만 아니라 비동기 처리(이벤트 처리, Ajax 통신, 타이머 함수 등)에 활용되는 중요한 패턴이다.
- document.getElementById('myButton').addEventListener('click', function() { console.log('button clicked!') }) setTimeout(function () { console.log('1초 경과') }, 1000);
- 콜백 함수는 비동기 처리 뿐만 아니라 배열 고차 함수에서도 사용된다.
- var res = [1, 2, 3].map(function (item) { return item * 2 }) console.log(res) // [2, 4, 6] res = [1, 2, 3].filter(function (item) { return item % 2 }) console.log(res) // [1, 3] res = [1, 2, 3].reduce(function (acc, cur) { return acc + cur }) console.log(res) // 6
12.7.5 순수 함수와 비순수 함수
- 함수형 프로그래밍에서는 어떤 외부 상태에 의존하지도 않고 변경하지도 않는 부수 효과가 없는 함수를 순수 함수라고 한다
- 순수 함수는 동일한 인수가 전달되면 언제나 동일한 값을 반환하는 함수다
var count = 0 function increase(n) { return ++n } count = increase(count) console.log(count) count = increase(count) console.log(count)
- 외부 상태에 의존하거나 외부 상태를 변경하는 부수효과가 있는 함수를 비 수순수 함수라고 한다.
- 함수가 외부 상태를 변경하면 상태 변화를 추적하기 어려워 진다.
- 함수 외부 상태의 변경을 지양하는 순수 함수를 사용하는 것이 좋다
var count = 0 function increase() { return ++count } increase(count) console.log(count) increase(count) console.log(count)
728x90
'자바스크립트 > 모던 자바스크립트 Deep Dive' 카테고리의 다른 글
전역 변수의 문제점 (1) | 2023.11.15 |
---|---|
스코프 (0) | 2023.11.15 |
원시 값과 객체의 비교 (0) | 2023.11.15 |
객체 리터럴 (0) | 2023.11.15 |
타입 변환과 단축 평가 (0) | 2023.11.15 |