26.1 함수의 구분
ES6 이전의 모든 함수는 일반 함수 호출, 생성자 함수 호출이 가능함
var foo = function () {
return 1;
};
//ES6 이전의 모든함수는 callable이자 constructor
foo(); //일반함수 호출, 1
new foo(); //생성자 함수 호출, foo {}
var obj = {foo: foo};
obj.foo(); //메서드로서 호출, 1
ES6 함수 구분 | constructor | prototype | super | arguments |
일반 함수 | O | O | X | O |
메서드 | X | X | O | O |
화살표 함수 | X | X | X | X |
26.2 메서드 : 축약표현으로 정의된 함수
const obj = {
x: 1,
foo(){ return this.x; }, //메서드 foo
bar: function() {return this.x;} //메서드가 아닌 일반함수 바인딩
}
console.log(obj.foo()); //1
console.log(obj.bar()); //1
new obj.foo(); //TypeError, ES6메서드는 생성자 함수로 호출할수 없음
new obj.bar(); //bar{}
obj.foo.hasOwnProperty('prototype'); //false, obj.foo는 constructor가 아닌 ES6메서드이므로 prototype 프로퍼티가 없음
obj.bar.hasOwnProperty('prototype'); //true, obj.bar는 constructor인 일반함수, prototype 프로퍼티가 있음
//표준 빌트인 객체가 제공하는 프로토타입 메서드, 정적메서드는 non-constructor
ES6메서드는 자신을 바인딩한 객체를 가리키는 내부 슬롯 [[HomeObject]]를 가짐, super 참조는 내부 슬롯 [[HomeObject]]를 사용하여 수퍼클래스의 메서드를 참조하므로 내부 슬롯 [[HomeObject]]을 갖는 ES6메서드는 super 키워드 사용 가능
const base = {
name: 'Lee',
sayHi(){
return `Hi! ${this.name}`;
}
};
const derived = {
__proto__: base,
//sayHi는 ES6메서드, 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?
ES6가 아닌 함수는 내부슬롯 [[HomeObject]]을 갖지않으므로 super 키워드 사용x
const derived = {
__proto__:base,
//sayHi는 ES6메서드가 아님
//sayHi는 [[HomeObject]]를 갖지않으므로 super키워드 사용x
sayHi: function(){
//SyntaxError
return `${super.sayHi()}. how are you doing?`;
}
};
ES6메서드는 본연의 기능(super)를 추가하고 의미적으로 맞지않는 기능(constructor)는 제거했으므로 메서드를 정의시 프로퍼티 값으로 익명 함수 표현식을 할당하는 ES6이전 방식 사용x
26.3 화살표 함수 : function 키워드 대신 화살표(=>)를 사용하여 기존 함수의정의 방식보다 간략히 함수 정의, 내부 동작도 간략해짐, 콜백함수 내부 this가 전역객체를 가리키는 문제해결의 대한으로 유용
26.3.1 화살표 함수 정의
함수 정의 : 함수 선언문으로 정의x, 함수 표현식으로 정의
const multiply - (x, y) => x*y;
multiply(2,3); //6
매개변수 선언 : 매개변수가 여러개인 경우 () 안에 매개변수 선언 / 한개인 경우 소괄호 생략 / 없는 경우 () 생략x
const arrow = (x, y) => { ... };
const arrow = x => { ... };
const arrow = () => { ... };
함수 몸체 정의 : 함수몸체가 하나의 문으로 구성시 {} 생략 가능, 함수내부 문이 값으로 평가될 수 있는 표현식일 경우 암묵적으로 반환함
//concise body
const power = x => x ** 2;
power(2) //4
//block body
const power = x => {return x ** 2;};
함수 몸체를 감싸는 {}를 생략한 경우 내부의 문이 표현시깅 아닌 문일 경우는 반환할 수 없으므로 에러 발생
const arrow = () => const x = 1; //SyntaxError
//위 표현은 다음과 같이 해석됨
const arrow = () => { return const x = 1; };
const arrw = () => { const x = 1; }; //{}생략하지 말것
객체 리터럴을 반환하는 경우 ()로 감싸기
const create = (id, ,content) => ({id, content});
create(1, 'Javascript'); //{id:1, content:'Javascript'}
//위 표현은 다음과 동일
const create = (id, content) => {return {id, content}; };
()를 생략하는 경우 객체 리터럴의 중괄호 {}를 함수 몸체를 감싸는 {}로 해석함
//{id, content}를 함수 몸체내의 쉼표 연산자문으로 해석
const create = (id, content) => {id, content};
create(1, 'Javascript'); //undefined
함수 몸체가 여러개의 문으로 구성될 때 {}생략x, 반환값이 있으면 명시적으로 반환
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.
화살표 함수도 일급객체이므로 map, filter, reduce와 같은 고차 함수에 인수로 전달 가능, 일반적인 함수 표현식보다 표현이 간결하여 가독성이 좋음
//ES5
[1, 2, 3].map(function(v){
return v*2;
});
//ES6
[1, 2, 3].map(v => v*2); //[2, 4, 6]
26.3.2 화살표 함수와 일반함수의 차이
01. 화살표 함수는 인스턴스를 생성할 수 없는 non-constructor -> prototype프로퍼티가 없고 프로토타입 생성x
const Foo = () => {};
//화살표 함수는 생성자 함수로 호출x
new Foo(); //TypeError
const Foo = () => {};
//화살표 함수는 prototype프로퍼티가 없음
Foo.hasOwnProperty('prototype'); //false
02. 중복된 매개변수 이름 선언시 에러
//일반함수는 중복된 매개변수 이름을 선언해도 에러x
function normal(a, a) {return a+a;}
console.log(normal(1, 2)); //4
//strict mode는 에러
'use strict';
function normal(a, a) {return a+a;} //SyntaxError
//화살표함수는 에러
const arrow =(a, a) => a+a; //SyntaxError
03. 화살표 함수는 함수자체의 this, arguments, super, new.target 바인딩을 갖지 않음
스코프 체인을 통해 상위 스코프 중 화살표가 아닌 함수의 this, arguments, super, new.target 를 참조함
26.3.3 this
this바인딩은 함수의 호출방식에 따라 동적으로 결정
class Prefixer {
constructor(prefix){
this.prefix = prefix;
}
add(arr){
//add 메서드는 인수로 전달된 배열 arr을 순회하며 배열의 모든 요소에 prefix를 추가
//1
return arr.map(function (item){
return this.prefix + item; //2
//TypeError
});
}
}
const prefixer = new Prefixer('-webkit-');
console.log(prefixer.add(['transition', 'user-select']));
//프로토타입 메서드 내부 1에서 this는 메서드를 호출한 객체(prefixer 객체)
//map메서드가 콜백함수를 일반함수로 호출하므로
//map의 인수로 전달한 콜백함수 내부 2에서 this는 undefined
//1과 2에서의 this가 다른값을 가리키므로 TypeError
//해결방법
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']));
//['-webkit-transition', '-webkit-user-select']
화살표 함수는 함수자체의 this를 갖지 않으므로 화살표 함수 내부에서 this를 참조하면 일반적인 식별자처럼 상위스코프의 this를 참조함(lexical this)
//화살표 함수는 상위 스코프의 this참조
() => this.x;
//익명 하뭇에 상위 스코프의 this 주입
//위 화살표 함수와 동일하게 동작
(function () {return this.x;}).bind(this);
화살표함수는 this바인딩을 갖지않으므로 call, apply, bind 메서드를 사용해도 화살표 함수 내부의 this를 바꿀수 없다
window.x=1;
const normal = function () {return this.x;};
const arrow = () => this.x;
console.log(normal.call({ x:10 }); //10
console.log(arrow.call({ x:10 }); //1
화살표함수에서 this는 전역객체를 가리키므로 화살표함수로 메서드를 정의x, 프로퍼티에 화살표 함수 할당x
//메서드 정의
//Bad
const person = {
name:'Lee',
sayHi: () => console.log(`Hi ${this.name}`)
};
person.sayHi(); //Hi
//Good
const person = {
name:'Lee',
sayHi() {
console.log(`Hi ${this.name}`);
}
};
person.sayHi(); //Hi Lee
//프로퍼티에 화살표함수 할당
//Bad
function Person(name){
this.name = name;
}
Person.prototype.sayHi = () => console.log(`Hi ${this.name}`);
const person = new Person('Lee');
person.sayHi(); //Hi
//Good
function Person(name){
this.name = name;
}
Person.prototype.sayHi = function () {console.log(`Hi ${this.name}`)};
const person = new Person('Lee');
person.sayHi(); //Hi Lee
26.3.4 super
화살표함수는 함수자체의 super바인딩을 갖지않으므로 super참조시 상위스코프의 super를 참조함, super는 내부슬롯 [[HomeObject]]를 갖는 ES6메서드내에서만 사용가능
class Base {
constructor(name){
this.name = name;
}
sayHi(){
return `Hi! ${this.name}`;
}
}
class Derived extends Base {
//화살표함수의 super는 상위스코프인 constructor의 super를 가리킴
sayHi = () => `${super.sayHi()} how are you doing?`;
}
const derived = new Derived('Lee');
console.log(derived.sayHi()); //Hi! Lee how are you doing?
26.3.5 arguments
화살표함수는 함수자체의 arguments바인딩을 갖지않으므로 arguments참조시 상위스코프의 arguments를 참조함
(function (){
//화살표함수 foo의 arguments는 상위 스코프인 즉시 실행함수의 arguments를 가리킴
const foo =() => console.log(arguments); //[Arguments] {'0':1, '1':2}
foo(3,4);
}(1,2));
//화살표 함수 foo의 arguments는 상위 스코프인 전역의 arguments를 가리킴
//전역에는 arguments객체가 존재하지않으므로 error
conso foo = () => console.log(arguments);
foo(1,2); //ReferenceError
arguments객체는 함수정의시 매개변수의 개수를 확정할 수 없는 가변 인자 함수를 구현할때 유용하나 화살표함수에서는 arguments를 사용할 수없으므로 Rest파라미터를 사용
26.4 Rest 파라미터
26.4.1 기본문법 : 매개변수 이름앞에 ...를 붙여 정의한 매개변수, 함수에 전달된 인수들의 목록을 배열로 전달받음
function foo(...rest){
//매개변수 rest는 인수들의 목록을 배열로 전달받는 Rest 파라미터
console.log(rest); //[[1,2,3,4,5]
}
foo(1,2,3,4,5);
//순차적 할당
function foo(param1, param2, ...rest){
console.log(param1); //1
console.log(param2); //2
console.log(rest); //[[1,2,3,4,5]
}
foo(1,2,3,4,5);
//Rest 파라미터는 마지막에
function foo(...rest, param1, param2){}
foo(1,2,3,4,5); //SyntaxError
//Rest 파라미터는 하나만 가능
function foo(...rest1, ...rest2){}
foo(1,2,3,4,5); //SyntaxError
//Rest 파라미터는 함수 정의시 선언산 매개변수의 length에 영향x
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
26.4.2 Rest파라미터와 arguments 객체 : arguments객체는 함수호출시 전달된 인수들의 정보를 담은 순회가능한 유사배열 객체이며 함수내부에서 지역변수처럼 사용가능
//매개변수의 개수를 사전에 알수없는 가변 인자함수
function sum(){
//가변 인자함수는 arguments객체를 통해 인수를 전달받음
console.log(arguments);
}
sum(1,2); //{length:2, '0':1, '1':2}
하지만 유사배열객체이므로 배열메서드 사용시 call, apply메서드로 배열로 변환해야함
function sum(){
//유사배열객체인 arguments 객체를 배열로 변환
var array = Array.prototype.slice.call(arguments);
return array.reduce(function(pre, cur){
return pre + cur;
}, 0);
}
console.log(sum(1,2,3,4,5)); //15
ES6에서는 rest 파라미터를 사용해서 가변인자 함수의 인수 목록을 배열로 전달, 화살표함수는 arguments객체를 갖지않으므로 가변인자함수를 구현할때는 Rest파라미터 사용
function sum(...args){
//Rest 파라미터 args에는 배열 [1,2,3,4,5]가 할당됨
var array = Array.prototype.slice.call(arguments);
return args.reduce((pre, cur) => pre + cur, 0);
}
console.log(sum(1,2,3,4,5)); //15
26.5 매개변수 기본값
함수호출시 매개변수의 개수와 인수의 개수가 동일해아하나 JS엔진은 이를 체크하지않으므로 의도치 않은 결과 발생
function sum(x,y){
return x+y;
}
console.log(sum(1)); //NaN
//개수가 다를경우를 위한 방어코드
function sum(x, y){
//인수가 전달되지않아 매개변수의 값이 undefined일 경우 기본값 할당
x = x || 0;
y = y || 0;
return x+y;
}
console.log(sum(1,2)); //3
console.log(sum(1)); //1
ES6의 매개변수 기본값을 사용하여 인수체크 및 초기화를 간소화 가능
function sum(x=0, y=0){
return x+y;
}
console.log(sum(1,2)); //3
console.log(sum(1)); //1
//인수를 전달하지 않은경우와 undefined를 전달한 경우에만 유효
function lagName(name = 'Lee'){
console.log(name);
}
logName(); //Lee
logName(undefined); //Lee
logName(null); null
Rest 파라미터는 기본값 지정x
function foo(...rest = []){
console.log(rest);
}
//SyntaxError
'JS > [책] 모던 JS deep dive' 카테고리의 다른 글
36장. 디스트럭처링 할당 (0) | 2023.02.21 |
---|---|
35장. 스프레드 문법 (임시저장) (0) | 2023.02.21 |
22장. this (0) | 2023.01.11 |
21장. 빌트인 객체 (0) | 2023.01.10 |
20장. strict mode (0) | 2022.12.30 |