[JS] ES6 함수의 추가 기능

돗개·2022년 11월 19일
0

crack JavaScript

목록 보기
18/18

함수의 구분

  • ES6 이전 - 사용 목적에 따라 함수가 명확히 구분되지 않음. (일반 함수 & 생성자 함수로서 호출 가능. 즉, callable 이면서 constructor)

  • 함수가 명확히 구분되지 않다는 것은 성능 상 문제가 있음. 객체에 바인딩된 함수가 constructor이라는 것은 불필요한 prototype객체를 생성하기 때문.

  • ES6 - 사용 목적에 따라 함수를 세 가지 종류로 명확히 구분.

    • 일반 함수 (Normal) - 함수 선언문이나 함수 표현식으로 정의한 함수. (ES6 이전의 함수와 차이가 없음) (constructor, prototype 프로퍼티 존재, arguments가짐.)
    • 메서드 (Method) - 메서드 축약 표현으로 정의된 함수. (ES6 이전 - 객체에 바인딩된 함수) (non-constructor)
    • 화살표 함수 (Arrow) - function 키워드 대신 화살표(=>)를 사용하여 정의된 함수. (non-constructor)
// ES6 이전
var foo = function () {
  return 1;
}
// 일반 함수로서 호출
foo();  // 1
// 생성자 함수로서 호출
new foo();  // foo {}
// 메서드로서 호출
var obj = { foo: foo }
obj.foo;  // 1

Method

  • ES6 이전 - 객체에 바인딩된 함수
  • ES6 이후
    • 메서드 축약 표현으로 정의된 함수
    • 인스턴스를 생성할 수 없는 non-constructor. (prototype 프로퍼티 없고, 프로토타입도 생성하지 않음)
    • 생성자 함수로서 호출할 수 없다
    • 표준 빌드인 객체가 제공하는 프로토타입 메서드와 정적 메서드는 모두 non-constructor
    • 자신을 바인딩한 객체를 가리키는 내부 슬롯 [[HomeObject]]를 가짐
    • super 키워드를 사용할 수 있음
    • 본연의 기능 super를 추가하고 의미적으로 맞지 않는 constructor를 제거
    • 즉, 메서드 정의 시 프로퍼티 값으로 익명함수 표현식을 전달하지 않도록 하자.
const obj = {
  x: 1,
  foo() { return this.x; },  // foo는 메서드 (ES6)
  bar: function() { return this.x; }  // 메서드가 아닌 일반 함수
};
obj.foo();  // 1
obj.bar();  // 1

new obj.foo();  // TypeError: obj.foo is not a constructor (생성자 함수로서 호출 불가)
new obj.bar();  // bar {}

obj.foo.hasOwnProperty('prototype');  // false (constructor가 아닌 메서드라 prototype 프로퍼티가 없음)
obj.bar.hasOwnProperty('prototype');  // true

// 표준 빌드인 객체
String.prototype.toUpperCase.prototype;  // undefined
Array.prototype.map.prototype;  // undefined

const base = {
  name: 'Lee',
  sayHi() { return `hi, i'm ${this.name}.` }
};
const derived = {
  __proto__: base,
  // super는 sayHi의 [[HomeObject]]의 프로토타입인 base.prototype을 가리킴
  sayHi() { return `${super.sayHi()}. how u doing? }  // hi i'm Lee. how u doing?
};
derived.sayHi(); // hi i'm Lee. how u doing?

Arrow function

function 키워드 대신 화살표(=>)를 사용하여 기존의 함수 정의 방식보다 간략하게 정의한 함수. 콜백 함수 내부에서 this가 전역 객체를 가리키는 문제를 해결하기 위한 대안으로 유용함.

// 1) 함수 정의 - 함수 선언문으로 정의할 수 없고, 함수 표현식으로 정의해야 함
const multiply = (x, y) => x * y;
multiply(2, 3);  // 6

// 2) 매개변수 선언
const arrow = (x, y) => {};
const arrow = x => {};
const arrow = () => {};

// 3) 함수 몸체 정의
const power = x => { return x ** 2; };
const arrow = () => { return const x = 1; };
const create = (id, create) => { return { id, content }; };

// 함수 몸체가 하나로 구성되는 표현식인 문일 때, {} 생략 가능하며 자동 반환됨
const power = x => x ** 2;
const arrow = () => const x = 1;  // SyntaxError: Unexpected token 'const'
const create = (id, content) => ({ id, content });  // 객체 반환하는 경우는 소괄호로 감싸주기
create(1, 'js');  // { id: 1, content: 'js' }

// 즉시 실행 함수로 사용
const person = (name => ({
  sayHi() { return `hi. my name is ${name}.`; }
}))('Lee');
console.log(person.sayHi());  // hi. my name is Lee.

// 고차함수에 인수로 전달 가능
[1, 2, 3].map(function (v) {  // ES5
  return v * 2;
});
[1, 2, 3].map((v) => v * 2);  // ES6

화살표 함수와 일반 함수의 차이

// 1) 화살표 함수는 인스턴스를 생성할 수 없는 non-constructor.
const foo = () => {};
// 생성자 함수로서 호출 불가
new Foo();  // TypeError: Foo is not a constructor
// prototype 프로퍼티가 없고 프로토타입도 생성하지 않음
Foo.hasOwnProperty('prototype');  // false
-------------------------------------------------------------------------------
// 2) 함수 자체의 this, arguments, super, new.target 바인딩을 갖지 않음.
// 화살표 함수 내에서 위의 키워드들을 참조한다면, 상위 스코프의 것을 참조.
-------------------------------------------------------------------------------
// 3) this
// 함수를 호출할 때, 함수가 어떻게 호출되었는지에 따라 this에 바인딩할 객체가 동적으로 결정됨.
class Prefixer {
  constructor(prefix) {
    this.prefix = prefix;
  }
  add(arr) {
    return arr.map(function (item) {
      // 여기서 콜백함수를 일반함수로서 호출하는데, map은 strict mode가 암묵적으로 적용되어 this는 undefined가 바인딩됨. 
      return this.prefix + item;  // TypeError: cannot read prroperty 'prefix' of undefined
    });
  }
}
const prefixer = new Prefixer('-');
console.log(prefixer.add(['first', 'second']));

// ES6 이전 해결책
add(arr) {
  const that = this;  // this를 일단 회피시킴
  return arr.map(function (item) {
    return that.prefix + item;  // this 대신 that을 참조
  })
}
// ES5 해결책
add(arr) {
  return arr.map(function (item) {
    return this.prefix + item;
  }, this);  // this에 바인딩된 값이 콜백 함수 내부의 this에 바인딩됨
}
// Function.prototype.bind 메서드 사용
add(arr) {
  return arr.map(function (item) {
    return this.prefix + item;
  }.bind(this));  // this에 바인딩된 값이 콜백함수 내부의 this에 바인딩됨
}
// * 화살표 함수로 해결 (함수 자체의 this 바인딩을 갖지 않음. 따라서 상위 스코프의 this를 그대로 참조 = lexical this)
add(arr) {
  return arr.map((item) => this.prefix + item);
}
// 화살표 함수를 제외한 모든 함수에는 this 바인딩이 반드시 존재.
// 만약 화살표 함수가 전역 함수라면, 화살표 함수의 this는 전역 객체를 가리킴.
const foo = () => console.log(this);
foo();  // window
// 화살표 함수가 this 바인딩을 갖지 않기 때문에 call, apply, bind를 호출할 수 없다는 의미가 아니라 이를 사용해 this를 교체할 수 없고, 언제나 상위 스코프의 this 바인딩을 참조.
const person {
  name: 'Lee',
  // 따라서 메서드를 화살표 함수로 정의하는 것은 피해야 한다.
  sayHi: () => console.log(`Hi ${this.name}`)
  // 축약 표현 메서드를 활용하는 것이 좋다. (클래스 필드에서도)
  sayHi() { console.log(`Hi ${this.name}`) }
};
person.sayHi();  // Hi
-------------------------------------------------------------------------------
// 4) super
// 화살표 함수는 함수 자체의 super 바인딩을 갖지 않음 (화살표 함수 내부에서 super를 참조하면 상위 스코프의 super를 참조)
-------------------------------------------------------------------------------
// 5) arguments
// 화살표 함수는 함수 자체의 arguments 바인딩을 갖지 않음 (상위 스코프의 arguments를 참조)
// 상위 스코프의 arguments 객체를 참조할 수는 있지만 상위 함수에게 전달된 인수 목록을 참조하므로 그닥 도움되지 않음. (=> Rest 파라미터를 사용하기)

Rest parameter

함수에 전달된 인수들의 목록을 배열로 전달받는다.
매겨변수 이름 앞에 세개의 점 ...을 붙여서 정의한 매개변수.

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

// 일반 매개변수와 함께 사용
function foo(param, ...rest) {  // 선언된 매개변수에 할당된 인수를 제외한 나머지 인수들로 배열된 구성이므로, 반드시 마지막 파라미터여야 하며, 단 하나만 선언 가능
  console.log(param, rest);
}
foo(1, 2, 3);  // 1, [2, 3]
foo.length;  // 1 (함수 객체의 length 프로퍼티에 영향을 주지 않음)

// ES6에서는 rest 파라미터를 이용해 가변 인자 함수의 인수 목록을 배열로 직접 전달 받을 수 있다.
function sum(...args) {
  return args.reduce((pre, cur) => pre + cur, 0);
}
console.log(sum(1, 2, 3));  // 6

매개변수 기본값

기존에 함수 내에서 수행하던 인수 체크 및 초기화를 간소화할 수 있다.
매개변수 기본값은 매개변수에 인수를 전달하지 않은 경우와 undefined를 전달한 경우에만 유효.

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

// ES6
function sum(x = 0, y = 0) {
   return x + y;
}
profile
울보 개발자(멍.. 하고 울어요)

0개의 댓글