2022. 2. 5. 08:49ㆍ책/자바스크립트 딥다이브
1. 함수 구분
ES6 이전에 모든 함수(메서드 포함)는 callable 이면서 동시에 constructor였다. 즉, 일반 함수도 되고 생성자 함수도 될 수 있었다.
이렇게 되면 객체에 바인딩된 함수도 constructor가 되고,
이때문에 객체에 바인딩된 함수가 prototype 프로퍼티를 가지며,
프로토타입 객체 또한 생성하게 된다.
심지어 콜백함수 또한 constructor가 되기때문에 쓸대없이 프로토타입 객체를 생성하게 된다
이때문에 ES6에선 함수를 사용 목적에 따라 아래와 같이 세 종류로 구분하기 시작.
ES6 함수 구분 | constructor | prototype | super | arguments |
일반 함수(Normal) | O | O | X | O |
메서드(Method) | X | X | O | O |
화살표 함수(Arrow) | X | X | X | X |
2. 메서드
ES6 이전엔 통상 객체에 바인딩된 함수를 지칭하는 말이었으나, ES6 사양에선 메서드 축약 표현으로 정의된 함수만을 의미하게 되었다.
ㅇ특징
- ES6 메서드는 non-constructor ( = 인스턴스 생성 불가)
- 인스턴스 생성 불가이므로 프로토타입 생성X
* 참고로 표준 빌트인 객체의 메서드들도 모두 non-constructor임
* ex : Number.isNaN.prototype // undefined
- 내부 슬롯 [[HomeObject]]를 갖는다 ( = super 참조 가능)
정리하면 ES6 메서드는 본연의 기능(super)을 추가하고 의미적으로 맞지 않는 기능(constructor)은 제거했다. 즉, 앞으로 메서드를 정의할 땐 ES6 이전 방식을 사용하지 않도록 하여라.
3. 화살표 함수
1) 정의하기
- 함수 몸체 내부의 문이 값으로 평가될 수 있는 표현식이면 암묵적으로 반환된다
const power = x => x ** 2 ;
const power = x => { return x ** 2; };
* 표현식이 아니면 에러발생
(X) const arrow = ( ) => const x = 1;
// SyntaxError: Unexpected token 'const'
- 객체리터럴 반환시 소괄호로 감싸줘야 한다
(O) const create = (id, content) => ( { id, content } );
(O) const create = (id, content) => { return { id, content } };
(X) const create = (id, content) => { id, content };
// { }가 객체를 뜻하는게 아니라 함수 몸체를 가리키는걸로 잘못 해석함
2) 즉시 실행 함수로 사용 가능
const person = ( name => ( {
// 왜 ( { } ) 냐고? 하아...
// { } 자체가 걍 메서드 하나 달아놓은 객체리터럴이잖아요
// 바로 위에 객체리터럴 반환시 소괄호로 감싸야한다고 했잖슴 ㅡㅡ
sayHi() { return `My name is ${name}` } )
)('존잘남');
console.log(person.sayHi( )) // My name is 존잘남
3) 고차함수에 인수 전달 가능 (화살표 함수도 일급객체 이므로)
[1,2,3,4].map( v => v * 2);
3.2 화살표 함수 vs 일반 함수
- 화살표함수는 non-constructor (인스턴스생성X, 프로토타입 생성X)
- 화살표함수엔 중복된 매개변수이름 사용 불가
- 화살표함수는 함수 자체의 this, arguments, super, new.target 바인딩을 갖지 않음
* 화살표 함수안에서 얘들 참조시 상위 스코프에서 참조해옴
3.3 this
22장에서 말했지만 this는 함수의 호출 방식에 따라 동적으로 결정된다.
- 일반함수 : 전역 객체 (단, strict mode에선 undefined)
- 생성자함수 : 인스턴스
- 메서드 : 메서드를 호출한 객체 ( MyClass.sayHi( ) 면 MyClass )
이게 문제가 되는 경우가 콜백 함수를 쓰는 경우인데,
class 접두사붙이기 {
constructor (접두사) {
this.접두사 = 접두사;
}
// 프로토타입 메서드
add(어레이) {
return 어레이.map(function (item) {
return this.접두사 + item;
});
}
}
const 인스턴스 = new 접두사붙이기('멍충이')
console.log(인스턴스.add(['너', '나', '우리']));
위 코드가 안돌아가는 이유는 아래 과정을 거치기 때문이다
1) 인스턴스가 add 메서드를 호출한다 ... this = 인스턴스
2) add 안에서 Array.prototype.map의 인수로 콜백 함수가 전달된다
3) map 메서드는 그 콜백 함수를 일반 함수로서 호출한다 ... this = 전역객체 (X)
* 땡!!! 클래스 내부는 strict mode가 암묵적으로 적용된다
* 따라서 3)에서 this엔 전역객체가 아니라 undefined가 바인딩됨
화살표함수 이전엔 이걸 해결하려면
1) map 메서드 두번째 인수로 this를 바인딩할 객체를 전달하거나
add(어레이) {
return 어레이.map(function (item) {
return this.접두사 + item;
}, this);
}
2) Function.prototype.bind 메서드로 this를 새로 바인딩한 함수를 만들어 쓰거나
add(어레이) {
return 어레이.map(function (item) {
return this.접두사 + item;
}.bind(this));
}
ㅇ갓갓 화살표 함수 (lexical this)
하지만 화살표 함수는 함수 자체의 this 바인딩을 갖지 않고,
따라서 화살표 함수 내부에서 this를 참조하면 사위 스코프의 this를 그대로 참조하게 된다
(이를 lexical this라고 한다)
이걸 이용하면 아래처럼 간단하게 처리가 가능하다
add(어레이) {
// 음.. 화살표 함수는 this 따위 없고 바로 상위 스코프의 this를 갖다쓰는데
// add는 메서드고, 메서드의 this는 메서드를 호출한 객체를 가리키게 될테니
// this에 인스턴스가 알아서 바인딩되겠군 개꿀
return 어레이.map(item => this.접두사 + item);
}
화살표 함수를 의사코드로 표현하자면
( ) => this.아무거나 ;
( function( ) { return this.아무거나; } ).bind(this)
ㅇ특징
- this 바인딩이 없어서 Function.prototype.call/apply/bind 메서드를 못쓴다
(애초에 this를 바꿔주는 애들인데 화살표 함수 자체가 this 바인딩이 없는걸?)
- 메서드 정의하는데 갖다쓰면 매우 이상해진다
// 객체의 프로퍼티에 화살표 함수를 달면?
// 응 ㄲㅈ 상위스코프로 갈거야
// this = 전역객체 = window
const person = {
name: '노예야',
sayHi : () => console.log(`ㅎㅇ ${this.name}`)
}
person.sayHi(); // ㅎㅇ
// 프로토타입 메서드에 쓰면?
// 응 상관없어 무조건 상위스코프로 갈거야
// this = 전역객체 = window
function Person(name) {
this. name = name;
}
Person.prototype.sayHi = () => console.log(`ㅎㅇ ${this.name}`)
const person = new Person('노예야')
person.sayHi(); // ㅎㅇ
* 따라서 메서드를 동적 추가하고 싶다면 일반 함수나 ES6 메서드를 쓰자
function Person(name) {
this.name = name;
}
// ES6 메서드 쓰기
Person.prototype = {
constructor: Person, //Person.prototype 객체의 주인님이 Person임을 알리고
sayHi() { console.log(`ㅎㅇ ${this.name}`)} // ES6 메서드로 정의해버리기
}
ㅇ화살표함수와 클래스필드
클래스필드가 뭔지 고새 까먹었을까봐 적자면
클래스 기반 객체지향 언어에서 클래스가 생성할 인스턴스의 프로퍼티를 가리키는 용어
걍 인스턴스 프로퍼티라고 생각하면 됨 ㅇㅅㅇ
// 클래스 필드
class Person {
name = "노예";
sayHi = () => console.log(`ㅎㅇ ${this.name}야`)
}
// 위랑 아래랑 똑같은 코드임
class Person {
constructor( name ) {
this.name = "노예";
this.sayHi = () => console.log(`ㅎㅇ ${this.name}야`)
}
}
여기서 화살표함수 내부의 this는 원래 클래스 외부다.
하지만 이 경우엔 클래스가 생성할 인스턴스를 참조하게 된다.
즉, constructor 내부의 this 바인딩과 같다.
3.4 super
화살표 함수는 함수 자체의 super 바인딩을 갖지 않는다.
따라서 화살표 함수 내부에서 super 참조시 상위 스코프의 super를 참조한다
class Base {
constructor (name) {
this.name = name;
}
sayHi() {
return `ㅎㅇ ${this.name}`
}
}
class Derived extends Base {
// 클래스 필드 정의
// 인스턴스 메서드임 (프로토타입 메서드 아님!!)
// 화살표 함수의 super는 상위 스코프인 (서브클래스)constructor의 super를 가리킴
//
sayHi = () => `${super.sayHi()}`
}
3.5 arguments
화살표 함수는 함수 자체의 arguments 바인딩을 갖지 않는다.
따라서 화살표 함수 내부에서 arguments를 참조하면 this와 마찬가지로 상위 스코프의 arguments를 참조한다.
(function() {
// Arguments { 0: 1, 1: 2 }
const foo = () => console.log(arguments);
foo(3,4);
}(1,2))
화살표 함수 자신에게 전달된 인수 목록은 확인 못하고
상위 함수에게 전달된 인수 목록을 참조하는 병맛같은 일이 발생한다.
따라서 화살표 함수로 가변 인자 함수 구현시 Rest 파라미터를 쓰도록 하여라
4. Rest 파라미터
함수 정의시 매개변수가 몇 개인지 정확히 정하기 힘들 경우 주는대로 다 받아쓰겠다는 의미.
즉, 함수에 전달된 인수들의 목록을 배열로 전달받는다.
function 함수( ...rest ) {
console.log( rest ) // [ 1, 2, 3, 4 ]
}
함수(1,2,3,4);
- Rest 파라미터는 단 한번만 선언할 수 있다.
- Rest 파라미터는 일반 매개변수와 사용 가능 ( 변수1, 변수2, ...rest )
- 함수 객체의 length 프로퍼티에 영향을 주지 않는다
* length 프로퍼티 : 선언한 매개변수의 개수를 나타낸다
* 함수( ...rest ) { } 에서 함수.length = 0 으로 취급안해줌
4.2 Rest 파라미터와 arguments 객체
ES6 이전엔 가변인자 함수의 경우 매개변수로 인수를 전달받는 것이 불가능했다 (실화냐;;)
그래서 arguments 객체를 통해 간접적으로 불러오는 방법을 사용했었는데
rest 파라미터 덕분에 이 문제가 해결됐다.
ㅇ고대의 문법 (알 필요는 없지만)
function sum() {
// 고대 문법이라 var 키워드
// arguments는 유사 배열 객체라 배열메서드를 못썼다
// 그래서 Function.prototpye.call/apply로
// 배열로 변환한 뒤 써야했음;;
var array = Array.prototype.slice.call(arguments);
return array.reduce(function(pre, cur) {
return pre + cur;
}, 0);
}
특히 화살표 함수의 경우엔 함수 자체가 arguments 객체를 갖지 않다보니
화살표 함수로 가변인자 함수를 구현하려면 rest 파라미터를 쓸 수 밖에 없다
5. 매개변수 기본값
함수 호출시 매개변수 개수만큼 인수를 전달하는게 너무 당연해보인다.
하지만 그렇지 않은 경우에도 에러가 발생하지 않는데
이는 자바스크립트 엔진이 매개변수의 개수와 인수를 체크하지 않기 때문이다
function sum( x, y ) {
return x + y;
}
console.log( 1 ); // NaN (에러는 아님)
개발자로서 의도치않은 사용을 막기 위해 방어 코드가 필요하다.
즉, 매개변수에 인수 전달을 체크하고, 인수 전달이 안됐으면
매개변수에 기본값을 할당할 필요가 있다.
이걸 쉽게 해주는게 ES6 매개변수 기본값인데
인수를 전달하지 않거나 undefined를 전달한 경우에 유효하게 대처해준다
그리고 함수 객체의 length 프로퍼티와 arguments 객체에 아무런 영향을 주지 않는다
function sum ( x, y=0 ) {
console.log(arguments)
return x + y;
}
sum(1); // Arguments { '0' : 1 } <-- 음.. 인수 2개처럼 동작은 한다만 실제 받은 인수는 1개
console.log( sum.length ); // 1 <-- 매개변수가 1개로 찍혀나온다 (y=0 부분 무시)
'책 > 자바스크립트 딥다이브' 카테고리의 다른 글
[27장] 배열(2) (0) | 2022.02.06 |
---|---|
[27장] 배열 (0) | 2022.02.06 |
[25장] 클래스(3) (0) | 2022.02.04 |
[25장] 클래스(2) (0) | 2022.02.04 |
[25장] 클래스 (0) | 2022.02.03 |