JS/[책] 모던 JS deep dive

26장. ES6 함수의 추가 기능

web_seul 2023. 1. 18. 12:54
반응형

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