Javascript ES6문법 정리

fgStudy·2022년 4월 24일
0

자바스크립트

목록 보기
12/26
post-thumbnail

자바스크립트 함수는 ES6이후 많은 변화가 있었다. 해당 포스팅은 ES6 함수와 이전 함수를 비교하고, ES6 함수를 사용해야 하는 이유에 대해 서술하고자 한다.


함수의 구분 변화

ES6 이전까지 모든 함수는 일반 함수로 호출할 수 있는 것은 물론 생성자 함수로서도 호출할 수 있었다. 즉 ES6 이전 함수는 callable이면서 constructor이었다.

var foo = function () {
	return 1;
}

// 일반적인 함수로서 호출
foo(); // 1

// 생성자 함수로서 호출
new foo(); // -> foo {}

// 메서드로서 호출
var obj = { foo: foo };
obj.foo(); // -> 1

또한 ES6 이전의 메서드(객체에 바인딩된 함수)도 callable이며 constructor이다.
따라서 객체에 바인딩된 함수도 일반 함수로서 호출할 수 있는 것은 물론 생성자 함수로서 호출할 수도 있다.

// 프로퍼티 f에 바인딩된 함수는 callable이며 constructor이다.
var obj = {
  x: 10,
  f: function() { return this.x; }
}

// 프로퍼티 f에 바인딩된 함수를 메서드로서 호출
console.log(obj.f()); // 10

// 프로퍼티 f에 바인딩된 함수를 일반 함수로서 호출
var bar = obj.f;
console.log(bar()); // undefined

// 프로퍼티 f에 바인딩된 함수를 생성자 함수로서 호출
console.log(new obj.f()); // f{}

콜백 함수 또한 constructor이기 때문에 불필요한 객체를 생성한다.

[1, 2, 3].map(function (item) {
	return item * 2;
}); // -> [2, 4, 6]

이처럼 ES6 이전의 모든 함수는 사용 목적에 따라 명확한 구분이 없으므로 호출 방식에 특별한 제약이 없었다. 이는 정의한 함수의 목적에 맞지 않게끔 잘못 사용할 유발할 가능성이 높다. 이러한 문제를 해결하기 위해 ES6에서는 함수를 사용 목적에 따라 세 종류로 명확히 구분했다.

ES6 함수의 구분constructorprototypesuperarguments
일반 함수OOXO
메서드XXOO
화살표 함수XXXX

일반 함수는 함수 선언문이나 함수 표현식으로 정의한 함수로 이전과 동일한 의미이다. 반면 메서드와 화살표 함수는 이전과 차이점이 있다.
또한 일반 함수는 constructor이나 ES6 메서드와 화살표 함수는 non-constructor이다.


1. 메서드

1-1. constructor인 ES6 메서드, non-constructor인 ES6 이전 메서드

ES6 사양에서 메서드는 메서드 축약 표현으로 정의된 함수만을 의미한다.

const obj = {
	x: 1,
  	// ES6 메서드
  	foo() { return this.x; },
	// ES6 이전 메서드로 ES6이후 일반 함수로 취급된다.
  	bar: function() { return this.x; }
}

new obj.foo(); // TypeError: obj.foo is not a constructor
new obj.bar(); // bar {}

ES6 메서드는 인스턴스를 생성할 수 없는 non-constructor이다. 따라서 prototype 프로퍼티가 없고 프로토타입도 생성할 수 없다.
반면 ES6 이전 메서드는 일반 함수이므로 인스턴스를 생성할 수 있는 constructor이다.

1-2. super 키워드를 쓸 수 있는 ES6 메서드, 쓸 수 없는 ES6 이전 메서드

ES6 메서드는 자신을 바인딩하는 객체를 가리키는 내부 슬롯 [[HomeObject]]를 갖는다. super 참조는 내부 슬롯 [[HomeObject]]를 사용하여 super 클래스의 메서드를 참조하므로 내부 슬롯 [[HomeObject]]를 갖는다. 따라서 ES6 메서드는 super 키워드를 갖는다.


const base = {
	name: 'Lee',
  	sayHi() {
    	return `Hi! ${this.name}`;
    }
};

const derived = {
	__proto__: base,
  	// sayHi는 ES6 메서드이다. ES6 메서드는 [[HomeObject]]를 갖는다.
  	// sayHi의 [[HomeObject]]는 derived.prototype(자기 자신을 바인딩)을 가리킨다.
  	// super은 sayHi의 [[HomeObject]]의 프로토타입인 base.prototype을 가리킨다.
  	sayHi() {
    	return `${super.sayHi()}. how are you doing?`;
    }
}

console.log(derived.sayHi()); // Hi! Lee. how are you doing?

반면 ES6 이전 함수는 일반 함수이므로 내부 슬롯 [[HomeObject]]을 갖지 않는다. 따라서 super 키워드를 사용할 수 없다.

const derived = {
	__proto__: base,
  	sayHi: function () {
    	return `${super.sayHi()}. how are you doing?`;
    }
}

console.log(derived.sayHi()); // SyntaxError: 'super' keyword unexpected here

2. 화살표 함수

화살표함수는 ES6에 새롭게 나온 함수로 표현을 간략하게 표현할 수 있다. 화살표 ㅎ마수는 일반 함수의 기능을 간략화했으며 this도 편리하게 설계되었다.

2-1. this가 정적으로 묶이는 화살표 함수

ES6에서는 화살표 함수를 사용하여 콜백 함수 내부의 this 문제를 해결할 수 있다.

class Prefixer {
	constructor(prefix) {
    	this.prefix = prefix;
    }
  	add(arr) {
    	return arr.map(item => this.prefix + item);
    }
}

const prefixer = new Prefixer('-webkit-');
console.log(prefixer.add(['transition', 'user-select'])); // (2) ['-webkit-transition', '-webkit-user-select']

화살표 함수는 함수 자체의 this 바인딩을 갖지 않는다. 따라서 화살표 함수 내부에서 this를 참조하면 상위 스코프의 this를 그대로 참조한다. 이를 lexical this라 한다.

화살표 함수로 메서드(ES6가 아닌 일반적인 의미의 메서드)를 정의하는 것을 피해야 한다.

const person = {
	name: 'Lee',
  	sayHi: () => {}
}

// sayHi 프로퍼티에 할당된 화살표 함수 내부의 this는 
// 메서드를 호출한 객체인 person을 가리키지 않고 
// 상위 스코프인 전역의 this가 가리키는 전역 객체를 가리킨다.
// 따라서 이 예제의 this.name은 빈 문자열을 갖는 window.name과 같다.
person.sayHi();

2-2. arguments 바인딩을 갖지 않는 화살표 함수

화살표 함수는 함수 자체의 arguments 바인딩을 갖지 않는다. 따라서 화살표 함수 내부의 arguments를 참조하면 this와 마찬가지로 상위 스코프의 arguments를 참조한다.

(function () {
  	// 화살표 함수 foo의 arguments는 상위 스코프인 즉시 실행함수의 arguments를 가리킨다.
	const foo = () => {console.log(arguments)} // Arguments(2) [1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]
    foo(3,4);
}(1,2));

// 화살표 함수 foo의 arguments는 상위 스코프인 전역의 arguments를 가리킨다.
// 하지만 전역에는 arguments 객체가 존재하지 않으므로 참조 에러가 발생한다.
// arguments는 함수 내부에서만 유효하다.
const foo = () => console.log(arguments);
foo(1,2); // ReferenceError: arguments is not defined

화살표 함수는 상위 스코프의 arguments를 참조하므로 arguments 객체를 사용할 수 없다. 중첩 함수일 시 상위 스코프의 arguments(outer 함수)를 참조할 수는 있겠지만 화살표 함수 자신에게 전달된 인수 목록을 확인할 수 없다.

따라서 가변 인자 함수를 구현할 때는 ES6에 도입된 Rest 파라미터를 이용하도록 하자.


3. Rest 파라미터

Rest 파라미터는 함수에 전달된 인수들의 목록을 배열로 전달받는다.

function foo(...rest) {
  	// 매개변수 rest는 인수들의 목록을 배열로 전달받는 Rest 파라미터이다.
	console.log(rest); // [1,2,3,4,5]
}

foo(1,2,3,4,5);

일반 매개변수와 Rest 파라미터는 함께 사용할 수 있다. 이 때 함수에 전달된 인수들은 매개변수와 Rest 파라미터에 순차적으로 할당한다.

Rest 파라미터는 먼저 선언된 매개변수에 할당된 인수를 제외한 나머지 인수들로 구성된 배열이 할당된다. 따라서 Rest 파라미터는 반드시 마지막 파라미터이어야 한다.

function foo(p1, p2, rest) {
	console.log(p1); // 1
  	console.log(p2); // 2
  	console.log(rest); // [3,4,5]
}

foo(1, 2, 3, 4, 5);

Rest 파라미터는 함수 정의 시 선언한 매개변수 개수를 나타내는 함수 객체의 length 프로퍼티에 영향을 주지 않는다.

function foo(...rest) {}
console.log(foo.length); // 0

function bar(x, ...rest) {}
console.log(bar.length); // 1

function baz(x, y, ...rest) {}
console.log(baz.length); // 2

4. 매개변수 기본값

인수가 전달되지 않은 매개변수 값은 undefined이다. 따라서 인수가 전달되지 않은 경우 매개변수에 default값을 할당해줄 필요가 있다.

ES6 이전에는 아래와 같이 단축평가를 이용해 기본값을 할당해주었다.

function sum(x,y) {
  	// 인수가 전달되지 않아 매개변수 값이 undefined일 경우 기본값을 할당한다.
	x = x || 0;
  	y = y || 0;
  	
  	return x + y;
}

console.log(sum(1,2)); // 3
console.log(sum(2)); // 2

ES6에서 도입된 매개변수 기본값을 이용하면 함수 내에서 수행하던 인수 체크 및 초기화를 간소화할 수 있다.

function sum(x=0, y=0) {
	return x+y;
}

console.log(sum(1,2)); // 3
console.log(sum(2)); // 2

매개변수 기본값은 매개변수에 인수를 전달하지 않은 경우(undefined)undefined를 전달한 경우에만 유효하다.

function logName(name="익명") {
	console.log(name);
}

logName(); // 익명
logName(undefined); // 익명
logName(null); // null
logName('Lee'); // Lee

(docs) 모던 자바스크립트 딥다이브 26강 ES6 함수 추가 기능

profile
지식은 누가 origin인지 중요하지 않다.

0개의 댓글