2022. 12. 27. 19:41ㆍ기록/Modern JavaScript Deep Dive
- 목차
함수의 구분
ES6 이전의 모든 함수는 일반 함수 / 생성자 함수로 호출 가능 (callable 이면서 constructor 임)
ES6 이전에는 메서드(객체에 바인딩된 함수)도 callabel 이면서 constructor 임
ES6 이전의 모든 함수는 사용 목적에 따라 명확한 구분이 없기 때문에
호출 방식에 특별한 제약이 없고 생성자 함수로 호출되지 않아도 프로토타입 객체를 생성함
이것은 실수 유발 가능성이 있고 성능에 좋지 않음
ES6 에서는 함수를 사용 목적에 따라 세 가지로 구분함
1. 일반 함수
2. 메서드
3. 화살표 함수
ES6 함수 구분 | constructor | prototype | super | arguments |
일반 함수 | O | O | X | O |
메서드 | X | X | O | O |
화살표 함수 | X | X | X | X |
메서드
ES6 이전 사양에는 메서드에 대한 명확한 정의가 없었음
일반적으로 객체에 바인딩된 함수를 의미했었음
ES6 사양에서 정의한 메서드
메서드 축약 표현으로 정의된 함수만을 의미함
인스턴스를 생성할 수 없는 non-constructor
prototype 프로퍼티가 없고 프로토타입을 생성하지 않음
표준 빌트인 객체가 제공하는 프로토타입 / 정적 메서드는 모두 non-constructor
const base = {
name: 'Lee',
sayHi() {
return `Hi! ${this.name}`;
}
};
const derived = {
__proto__: base,
// sayHi는 ES6 메서드, [[HomeObject]]를 가짐
// sayHi의 [[HomeObject]]는 sayHi가 바인딩된 객체인 derived를 가리킴
// super는 sayHi의 [[HomeObject]]의 프로토타입인 base를 가리킴
sayHi() {
return `${super.sayHi()}. how are you doing?`;
}
};
console.log(derived.sayHi()); // Hi! Lee. how are you doing?
자신을 바인딩한 객체를 가리키는 내부 슬롯 [[HomeObject]] 를 가짐
super 참조는 내부 슬롯 [[HomeObject]] 를 사용하여 수퍼클래스의 메서드를 참조함
화살표 함수
function 키워드 대신 화살표를 사용하여 간략하게 함수 정의 가능
내부 동작도 기존의 함수보다 간략함
콜백 함수 내부에서 this가 전역 객체를 가리키는 문제를 해결하기 위한 대안으로 유용함
1. 화살표 함수 정의
함수 정의
const multiply = (x, y) => x * y;
multiply(2, 3); // 6
- 함수 선언문으로 정의할 수 없음
- 함수 표현식으로 정의해야 함
매개변수 선언
const arrow = (x, y) => { ... };
const arrow = x => { ... };
const arrow = () => { ... };
- 매개변수가 여러 개인 경우 소괄호 안에 매개변수를 선언함
- 매개변수가 한 개인 경우 소괄호 생략 가능
- 매개변수가 없는 경우 소괄호 생략 불가능
함수 몸체 정의
// block body
const power = x => { return x ** 2 };
// 위 표현은 다음과 동일함
// concise body
const power = x => x ** 2;
// SyntaxError: Unexpected token 'const'
const arrow = () => const x = 1;
- 함수 몸체가 하나의 문으로 구성될 경우 중괄호 생략 가능
- 함수 몸체 내부의 문이 값으로 평가될 수 있는 표현식인 문일 경우 암묵적으로 반환됨
- 함수 몸체 내부의 문이 표현식이 아닌 문일 경우 에러 발생 (반환 불가능)
const create = (id, content) => ({ id, content });
create(1, 'JavaScript'); // {id: 1, content: "JavaScript"}
// 위 표현은 다음과 동일함
const create = (id, content) => { return {id, content} };
- 객체 리터럴을 반환하는 경우 소괄호로 감싸 주어야 함
- 소괄호로 감싸지 않을 경우 객체 리터럴의 중괄호를 함수 몸체를 감싸는 중괄호로 잘못 해석함
const sum = (a, b) => {
const result = a + b;
return result;
}
- 함수 몸체가 여러 개의 문으로 구성될 경우 중괄호 생략 불가능
- 반환값이 있을 경우 명시적으로 반환해야 함
const person = (name => ({
sayHi() {
return `Hi? My name is ${name}.`;
}
}))('Lee');
console.log(person.sayHi()); // Hi? My name is Lee.
- 화살표 함수도 즉시 실행 함수로 사용 가능
[1, 2, 3].map(v => v * 2); // [ 2, 4, 6 ]
- 화살표 함수도 일급 객체이기 때문에 Array.prototype.map / filter / reduce 같은 고차 함수에 인수로 전달 가능
2. 화살표 함수와 일반 함수의 차이
(1) 화살표 함수는 인스턴스를 생성할 수 없는 non-constructor
prototype 프로퍼티가 없음
프로토타입을 생성하지 않음
(2) 중복된 매개변수 이름 선언 불가능
일반 함수는 중복된 매개변수 이름을 선언해도 에러가 발생하지 않음 (strict mode 에서는 에러 발생)
화살표 함수에서 중복된 매개변수 이름을 선언하면 에러 발생
(3) 화살표 함수는 함수 자체의 this, arguments, super, new.target 바인딩을 갖지 않음
화살표 함수 내부에서 this, arguments, super, new.target을 참조하면
스코프 체인을 통해 상위 스코프의 this, arguments, super, new.target를 참조함
화살표 함수가 중첩되어 있을 경우
스코프 체인 상에서 가장 가까운 상위 함수 중 화살표 함수가 아닌 함수의 this를 참조함
3. this
화살표 함수는 함수 자체의 this 바인딩을 갖지 않음
화살표 함수 내부에서 this를 참조하면 상위 스코프의 this를 그대로 참조함
이것이 lexical this
window.x = 1;
const normal = function () { return this.x; };
const arraw = () => this.x;
console.log(normal.call({ x: 10 })); // 10
console.log(arraw.call({ x: 10 })); // 1
Function.prototype.call / apply / bind 메서드를 사용해도 화살표 함수 내부의 this 교체 불가능
const add = (a, b) => a + b;
console.log(add.call(null, 1, 2)); // 3
console.log(add.apply(null, [1, 2])); // 3
console.log(add.bind(null, 1, 2)()); // 3
Function.prototype.call / apply / bind 메서드 호출은 가능
화살표 함수는 항상 상위 스코프의 this 바인딩을 참조함
const person = {
name: 'Lee',
sayHi() {
console.log(`Hi ${this.name}`);
}
};
person.sayHi(); // Hi Lee
메서드를 정의할 때는 ES6 축약 표현으로 정의한 ES6 메서드를 사용하는 것이 좋음
function Person(name) {
this.name = name;
}
Person.prototyle.sayHi = function () { console.log(`Hi ${this.name}`); };
const person = new Person('Lee');
person.sayHi(); // Hi Lee
프로퍼티를 동적 추가할 때는 ES6 메서드 정의를 사용할 수 없으므로 일반 함수를 할당함
function Person(name) {
this.name = name;
}
Person.prototype = {
// constructor 프로퍼티와 생성자 함수 간의 연결을 재설정함
constructor: Person,
sayHi() { console.log(`Hi ${this.name}`); }
};
const person = new Person('Lee');
person.sayHi(); // Hi Lee
일반 함수가 아닌 ES6 메서드를 동작 추가하고 싶은 경우
객체 리터럴을 바인딩하고 프로토타입의 constructor 프로퍼티와 생성자 함수 간의 연결을 재설정함
4. super
화살표 함수는 함수 자체의 super 바인딩을 갖지 않음
화살표 함수 내부에서 super를 참조하면 상위 스코프의 super를 참조함
5. arguments
화살표 함수는 함수 자체의 arguments 바인딩을 갖지 않음
화살표 함수 내부에서 arguments를 참조하면 상위 스코프의 arguments를 참조함
상위 스코프의 arguments 객체를 참조할 수는 있지만 화살표 함수 자신에게 전달된 인수 목록을 확인할 수 없음
화살표 함수로 가변 인자 함수를 구현해야 할 경우 Rest 파라미터를 사용해야 함
Rest 파라미터
Rest 파라미터 === 나머지 매개변수
매개변수 이름 앞에 세 개의 점을 붙여서 정의한 매개변수
함수에 전달된 인수들의 목록을 배열로 전달받음
1. 기본 문법
function foo(...rest) {
// 매개변수 rest는 인수들의 목록을 배열로 전달받는 Rest 파라미터임
console.log(rest); // [ 1, 2, 3, 4, 5 ]
}
foo(1, 2, 3, 4, 5);
function bar(param1, param2, ...rest) {
console.log(param1); // 1
console.log(param2); // 2
console.log(rest); // [ 3, 4, 5 ]
}
bar(1, 2, 3, 4, 5);
console.log(bar.length); // 2
일반 매개변수와 Rest 파라미터 함께 사용 가능
함수에 전달된 인수들은 매개변수와 Rest 파라미터에 순서대로 할당됨
Rest 파라미터는 먼저 선언된 매개변수에 할당된 인수를 제외한 나머지 인수들로 구성된 배열임
따라서 반드시 마지막 파라미터야 하고 하나만 선언 가능
함수 정의 시 선언한 매개변수 개수를 나타내는 함수 객체의 length 프로퍼티에 영향을 주지 않음
2. Rest 파라미터와 arguments 객체
arguments 객체
함수 호출 시 전달된 인수들의 정보를 담고 있음
순회 가능한 유사 배열 객체
함수 내부에서 지역 변수처럼 사용 가능
배열이 아닌 유사 배열 객체이기 때문에 배열 메서드를 사용하기 위해서는
Function.prototype.call / apply 메서드를 사용해 배열로 변환해야 함
Rest 파라미터
가변 인자 함수의 인수 목록을 배열로 직접 받을 수 있음
함수와 ES6 메서드는 Rest 파라미터와 arguments 객체 둘다 사용 가능
화살표 함수로 가변 인자 함수를 구현해야 할 경우 반드시 Rest 파라미터를 사용해야 함
매개변수 기본값
자바스크립트 엔진은 매개변수의 개수와 인수의 개수를 체크하지 않음
인수가 전달되지 않은 매개변수의 값은 undefined
매개변수에 인수가 전달되었는지 확인하여 전달되지 않은 경우 매개변수에 기본값을 할당해서 방어해야 함
매개변수에 인수를 전달하지 않은 경우 / undefined를 전달한 경우에만 유효함
Rest 파라미터에는 기본값 지정 불가능
함수 객체의 length 프로퍼티와 arguments 객체에 영향을 주지 않음
ES6에서 도입됨
function sum(x = 0, y = 0) {
return x + y;
}
console.log(sum(1, 2)); // 3
console.log(sum(1)); // 1