📝 용어 정리
| 설명 | |
|---|---|
| 순수 함수 | 동일한 입력에 항상 동일한 출력을 반환하며, 외부 상태를 변경하지 않는 함수 |
| 콜백 함수 | 다른 함수의 인자로 전달되어 특정 시점에 호출되는 함수 |
| 고차 함수 | 함수를 인자로 받거나 함수를 반환하는 함수 |
| 매개변수 | 함수 정의 시 입력값을 받기 위해 선언하는 변수 |
| 인자 | 함수 호출 시 전달하는 실제 값 |
🧬 프로토타입(Prototype)
자바스크립트는 프로토타입 기반 언어로, 모든 객체는 다른 객체를 상속받을 수 있습니다.
이때 상속의 기준이 되는 객체를 프로토타입이라 하며, 객체는 내부적으로 [[Prototype]]이라는 숨겨진 프로퍼티를 통해 자신의 프로토타입을 참조합니다. 프로토타입에 정의된 프로퍼티나 메서드는 해당 프로토타입을 상속받은 모든 객체에서 공유되어 사용할 수 있습니다.
즉, 프로토타입은 객체 간 상속과 재사용을 가능하게 하는 핵심 메커니즘입니다.
⚖️ 함수 선언문 vs 함수 표현식 비교
| 함수 선언문 | 함수 표현식 | |
|---|---|---|
| 형태 | function func() { ... } | const func = function() { ... } |
| 호이스팅 | 전체가 호이스팅되어 선언 전에도 호출 가능 | 변수만 호이스팅되므로 선언 이후에만 호출 가능 |
| 생성 시점 | 코드 실행 전에 메모리에 로드됨 | 코드 실행 시 평가되어 메모리에 할당됨 |
일급 객체 (First-class Object)
자바스크립트에서 함수는 일급 객체로 취급됩니다. 즉, 함수는 값처럼 변수에 할당, 인자로 전달, 반환값으로 사용될 수 있습니다. 따라서 함수 리터럴로 생성한 함수를 변수에 할당할 수 있으며, 이러한 방식을 함수 표현식이라고 합니다.
🎈 함수 호이스팅
자바스크립트 엔진은 함수 선언문을 호이스팅하여 코드 실행 전에 메모리에 먼저 올립니다. 따라서 함수 선언 이전에도 호출이 가능합니다.
반면, 함수 표현식은 변수에 할당되는 형태이므로, 선언 이전에 호출하면 ReferenceError가 발생합니다. 함수 표현식은 호이스팅되더라도 변수 선언만 호이스팅되기 때문에, 값이 할당되기 전에는 사용할 수 없습니다.
🏗️ 생성자 함수
// 생성자 함수 정의
function Person(name, age) {
this.name = name
this.age = age
// 메서드 정의
this.sayHello = function () {
console.log(`안녕하세요, 제 이름은 ${this.name}이고, 나이는 ${this.age}살이에요.`)
}
}
// new 연산자를 사용해 인스턴스 생성
const person1 = new Person('지민', 28)
const person2 = new Person('현수', 32)
person1.sayHello() // 안녕하세요, 제 이름은 지민이고, 나이는 28살이에요.
person2.sayHello() // 안녕하세요, 제 이름은 현수이고, 나이는 32살이에요.new 키워드를 사용해 생성자 함수를 호출하면, 다음과 같은 단계로 인스턴스가 생성되고 반환됩니다.
1. 새로운 빈 객체가 생성된다.
2. 생성된 객체가 this에 바인딩된다.
3. 생성자 함수의 코드가 실행되어 프로퍼티와 메서드가 this에 추가된다.
4. 생성자 함수가 명시적으로 다른 객체를 반환하지 않으면, this가 자동으로 반환된다.
🔗 This
this는 객체 자신의 프로퍼티나 메서드를 참조하기 위한 자기 참조 변수입니다. this가 가리키는 값, 즉 this 바인딩은 함수 호출 방식에 따라 동적으로 결정됩니다.
1. 일반 함수 호출
일반 함수로 호출하면 전역 객체(브라우저에서는 window, Node.js에서는 global)를 가리킵니다. 엄격 모드(use strict)에서는 undefined가 됩니다.
function showThis() {
console.log(this)
}
showThis() // 브라우저: window, 엄격모드: undefined2. 메서드 호출
객체의 메서드로 호출하면, this는 해당 메서드를 호출한 객체를 가리킵니다.
const obj = {
name: 'Alice',
greet() {
console.log(this.name)
},
}
obj.greet() // Alice3. 생성자 함수 호출
생성자 함수 내부의 this에는 생성자 함수가 생성할 인스턴스가 바인딩됩니다.
function Person(name) {
this.name = name
}
const person = new Person('Bob')
console.log(person.name) // Bob4. Function.prototype.apply/call/bind 메서드에 의한 간접 호출
call과apply는 첫 번째 인자로 전달된 객체로this를 강제 바인딩합니다.bind는 새로운 함수를 반환하며,this가 항상 바인딩된 상태를 유지합니다.
function sayHello(greeting) {
console.log(`${greeting}, ${this.name}`)
}
const user = { name: 'Carol' }
sayHello.call(user, 'Hi') // Hi, Carol
sayHello.apply(user, ['Hello']) // Hello, Carol
const boundFunc = sayHello.bind(user)
boundFunc('Hey') // Hey, Carol➡️ 화살표 함수
ES6의 화살표 함수는 기존 함수 표현식을 더 간결하게 작성하는 새로운 문법입니다. function 키워드 대신 화살표(=>)를 사용하고, 함수의 내용이 한 줄인 경우 중괄호와 return 키워드를 생략할 수 있습니다. 또한, this 바인딩 방식이 기존 함수와 달라(lexical this) 중첩 함수에서 this를 사용할 때 유용합니다.
const obj = {
name: 'Alice',
regularFunc: function () {
console.log(this.name)
},
arrowFunc: () => {
console.log(this.name)
},
}
obj.regularFunc() // Alice
obj.arrowFunc() // undefined (상위 스코프의 this)화살표 함수의 this
화살표 함수는 자신만의 this를 가지지 않기 때문에, 내부에서 this를 참조하면 함수가 정의된 상위 스코프의 this를 그대로 사용합니다(lexical this). 따라서 call, apply, bind로도 this를 변경할 수 없으며, 이러한 특성 때문에 화살표 함수로 객체의 메서드를 정의하는 것은 바람직하지 않습니다.
화살표 함수의 arguments
화살표 함수는 자신만의 arguments 객체를 가지지 않기 때문에, 내부에서 arguments를 참조하면 상위 스코프에서 정의된 함수의 arguments를 그대로 사용합니다. 따라서 화살표 함수에서 전달된 인자를 개별적으로 처리하려면 나머지 매개변수(Rest Parameter, ...args) 를 사용하는 것이 권장됩니다.
function regular() {
const arrow = () => {
console.log(arguments) // 상위 함수의 arguments 참조
}
arrow(3, 4)
}
regular(1, 2) // [1, 2]
// 나머지 매개변수를 사용하는 안전한 방법
const arrowSafe = (...args) => {
console.log(args)
}
arrowSafe(5, 6) // [5, 6]🧠 클로저(Closure)
클로저는 중첩 함수가 외부 함수보다 오래 살아남아, 외부 함수의 변수를 계속 참조할 수 있는 구조를 의미합니다.
자바스크립트의 모든 함수는 상위 스코프를 기억하므로 이론적으로 모든 함수는 클로저입니다. 하지만 상위 스코프의 식별자를 참조하지 않는 경우, 대부분의 모던 브라우저는 메모리 최적화를 위해 상위 스코프를 유지하지 않습니다. 이는 불필요한 메모리 사용을 방지하기 위함입니다.
클로저는 상태를 은닉하고, 안전하게 유지·변경할 수 있도록 특정 함수에만 권한을 부여하기 위해 사용됩니다.
function makeCounter() {
let count = 0 // 외부 함수 변수
return function () {
count++
return count
}
}
const counter = makeCounter()
console.log(counter()) // 1
console.log(counter()) // 2
console.log(counter()) // 3🚂 배열(Array)
일반적인 배열은 인덱스를 통해 요소에 빠르게 접근할 수 있습니다. 하지만 요소를 삽입하거나 삭제하는 경우에는 효율적이지 않습니다. 반면, 자바스크립트의 배열은 해시 테이블 기반 객체로 구현되어 있기 때문에, 인덱스로 접근할 때는 일반 배열보다 성능이 다소 느릴 수 있지만, 요소를 삽입하거나 삭제할 때는 상대적으로 더 빠른 성능을 기대할 수 있습니다.
👉 배열은 인덱스를 나타내는 문자열을 프로퍼티 키로 갖는 객체입니다. 따라서 배열에서 존재하지 않는 인덱스로 요소에 접근하면, 객체에서 존재하지 않는 프로퍼티에 접근했을 때와 마찬가지로 undefined를 반환합니다.
배열 요소 추가
push 메서드는 성능 면에서 좋지 않고 원본 배열을 직접 변경하는 부수 효과가 있습니다. 따라서 ES6의 스프레드 문법을 사용하는 것이 더 바람직합니다.
// ES6 스프레드 문법
const arr = [1, 2]
const newArr = [...arr, 3]참고 자료
『모던 자바스크립트 Deep Dive』, 이웅모 저, 위키북스