[러닝 자바스크립트] - 함수

이수빈·2022년 7월 31일
0

자바스크립트

목록 보기
5/7
post-thumbnail

러닝 자바스크립트 6장에 해당되는 부분이며, 읽으면서 자바스크립트에 대해 새롭게 알게된 것과 기록하고 싶은 부분을 정리한 내용입니다.

함수

함수하나의 단위로 실행되는 문의 집합이다.
모든 함수에는 함수를 구성하는 문의 모음인 바디가 있으며 {로 시작해서 }로 끝난다.
함수를 선언하고 호출해야 함수의 바디가 실행이 되고, 호출 할 때는 함수 이름 다음에 ()를 쓴다. 함수 호출은 표현식이다.

반환 값

함수 호출의 값은 반환 값으로 함수 바디 안에 return 키워드를 사용해 함수를 즉시 종료 시키고 값을 반환시킨다. return을 명시하지 않으면 반환 값은 undefined가 된다.

function getGreeting() {
  return "Hello World!";
}

function getGreetings() {
  let a = "Hello World!";
}

getGreeting(); // "Hello world!"
getGreetings(); // undefined

호출과 참조

자바스크립트에서 함수는 객체이기 때문에 할당하거나 넘기는 것이 가능하다.

함수 참조

함수 식별자 뒤에 괄호를 쓰지 않으면 다른 값과 마찬가지로 함수를 참조하는 것이며 함수는 실행되지 않는다. 함수를 다른 값에 할당하면 그 값의 이름으로 함수를 호출 할 수 있다.

function getGreeting() {
  return "Hello World!";
}

//변수에 할당
const f = getGreeting;
f(); // "Hello World!"

//객체 프로퍼티에 할당
const o = {};
o.f = getGreeting;
o.f(); // "Hello World!"

//배열 요소에 할당
const arr [1, 2, 3];
arr[1] = getGreeting;
arr[1](); // "Hello World!"

값 뒤에 괄호를 붙이면 자바스크립트는 그 값을 함수로 간주하고 호출한다. 함수가 아닌 값 뒤에 괄호를 붙이면 에러가 일어난다.

함수와 매개변수

함수를 호출하면서 정보를 전달할 때 함수 매개변수를 이용한다. 매개변수는 함수가 호출되기 전에는 존재하지 않는다는 점을 제외하면 일반적인 변수와 같다.

function avg(a, b) {
  return (a + b) / 2;
}

함수 선언에서 정의된 a와 b는 정해진 매개변수라고 한다. 이 정해진 매개변수는 함수가 호출되고 값을 받아 실제 매개변수가 된다. 실제 매개변수는 함수 바디 안에서만 존재한다.

함수에서 원시 값과 객체의 차이

함수를 호출하면 함수 매개변수는 변수 자체가 아니라 그 값을 전달받기 때문에, 함수 내부에서 함수 외부의 변수 값을 변경하더라도 아무 영향도 끼치지 않게 된다.

function f(x) {
  console.log(`f 내부 : x=${x}`);
  x = 5;
  console.log(`f 내부 : x=${x} (할당 후)`);
}

let x = 3;
console.log(`f를 호출하기 전 : x=${x}`);
f(x);
console.log(`f를 호출한 다음 : x=${x}`);

//실행 결과
//f 를 호출하기 전 : x=3
//f 내부 x=3
//f 내부 x=5 (할당 후)
//f를 호출한 다음 : x=3

함수 내부의 변수 x와 함수 외부의 변수 x는 이름은 같지만 서로 다른 개체이다.

하지만 함수 안에서 객체 자체를 변경하게 되면 얘기가 달라진다.

function f(o) {
  o.message = `f 안에서 수정함 (이전 값: '${o.message}')`;
}
let o = {
  message: "초기 값"
};
console.log(`f를 호출하기 전: o.message="${o.message}"`);
f(o);
console.log(`f를 호출한 다음: o.message="${o.message}"`);

//실행 결과
//f를 호출하기 전: o.message="초기 값"
//f를 호출한 다음: o.message="f 안에서 수정함 (이전 값: "초기 값")"

함수 내부에서 객체를 수정했고, 함수 외부의 객체에도 그대로 반영된다. 함수 내부의 o와 함수 외부의 o는 서로 다른 개체이지만, 둘은 같은 객체를 가리키고 있다. 이것이 원시 값과 객체의 핵심적인 차이이다.

function f(o) {
  o.message = "f에서 수정함";
  ✔ o = {
    message = "새로운 객체!"
  };
  console.log(`f내부: o.message="${o.message}" (할당 후`);
}let o = {
  message: "초기 값"
};
console.log(`f를 호출하기 전: o.message="${o.message}"`);
f(o);
console.log(`f를 호출한 다음: o.message="${o.message}"`);
//실행 결과
//f를 호출하기 전: o.message="초기 값"
//f내부: o.message="새로운 객체!" (할당 후)
//f를 호출한 다음: o.message="f에서 수정함"

✔ 표시된 함수 내부의 변수 o와 함수 외부의 변수 o는 서로 다르다. f를 호출하면 둘은 같은 객체를 가리키지만, f 내부에서 o에 할당한 객체는 새로운, 전혀 다른 객체이다.

매개변수 해체

매개변수도 해체가 가능하다. 해체 할당의 규칙과 마찬가지로 프로퍼티 이름은 반드시 유효한 식별자여야 하고, 들어오는 객체에 해당 프로퍼티가 없는 변수는 undefined를 할당받는다.

//객체 해체
function getSentence({ subject, verb, object }){
  return `${subject} ${verb} ${object}`;
}

const o = {
  subject : "I"
  verb : "love",
  object : "JavaScript",
};

getSentence(o); // "I love JavaScript"
//배열 해체
function getSentence([ subject, verb, object ]) {
  return `${subject} ${verb} ${object}`;
}

const arr = ["I", "love", "Javascript"];
getSentence(arr); // "I love JavaScript"

배열 해체에서 확산 연산자를 사용하면 남는 매개변수를 이용할 수 있다.

function addPrefix(prefix, ...words) { // words[] = ["verse", "vex"]
  const prefixedWords = [];
  for(let i=0; i<words.length; i++) {
    prefixedWords[i] = prefix + words[i];
  }
  return prefixedWords;
}

addPrefix("con", "verse", "vex"); // ["converse", "convex"]

확산 연산자는 반드시 마지막 매개변수여야하는데 확산 연산자 뒤에 다른 매개변수가 있으면 자바스크립트는 전달된 값 중 어디까지를 확산 매개변수에 할당해야 하는지 판단할 수 없기 때문이다.

매개변수 기본값

함수를 호출할 때 일반적으로 매개변수에 값을 제공하지 않으면 undefined가 값으로 할당되지만, ES6에서 매개변수에 기본값을 지정하는 기능이 추가되었다.

function f(a, b = "default", c = 3) {
  return `${a} - ${b} - ${c}`;
}

f(5, 6, 7); // "5 - 6 - 7"
f(5, 6); // "5 - 6 - 3"
f(5); // "5 - default - 3"
f(); // "undefined - default - 3"

객체의 프로퍼티인 함수

객체의 프로퍼티인 함수는 메서드라고 부르고 일반적인 함수와 구별된다.

const o = {
  name : "Wallace", // 원시 값 프로퍼티
  bark : function() {return 'Woof!'; }, // 함수 프로퍼티(메서드)
  bark() { return 'Woof!'; }, // ES6 문법
}

this

함수 바디에는 특별한 읽기 전용 값인 this가 존재한다. 일반적으로 this는 메서드에서 의미가 있는데, 메서드를 호출하면 this는 호출한 메서드를 소유하는 객체가 된다.

const o = {
  name : "Wallace",
  speak() { return `My Name is ${this.name}!`; },
}

o.speak(); // "My Name is Wallace!", this는 o에 묶인다

const speak = o.speak;
speak(); // "My Name is undefined!"

this는 함수를 어떻게 선언했느냐가 아닌, 어떻게 호출했느냐에 따라 달라진다. 위 코드에서 this가 o에 묶인 이유는 speak가 o의 프로퍼티여서가 아니라, o에서 speak를 호출했기 때문이다.
speak()에서는 호출하는 부분에서 함수가 어디에 속하는지 알 수 없기 때문에 this는 undefined에 묶인다.

그렇다면 중첩된 함수 안에서 this를 사용하면 어떻게 될까

const o = {
  name : 'Julie',
  greetBackwards : function () {
    function getReverseName() {
      let nameBackwards = '';
      for(let i=this.name.length-1; i>=0; i--) {
        nameBackwards += this.name[i];
      }
      return nameBackwards;
    }
    return `${getReverseName()} si eman ym ,olleH`;
  },
};
o.greetBackwards();

위 코드에서 greetBackwards안에서 getReverseName를 호출하면 this는 o가 아닌 다른 것에 묶인다. 이런 문제를 해결하기 위해 보통 사용하는 방법은 변수에 this를 할당해 주는 것이다.

const o = {
  name : 'Julie',
  greetBackwards : function () {const self = this;
    function getReverseName() {
      let nameBackwards = '';
      for(let i=self.name.length-1; i>=0; i--) {
        nameBackwards += self.name[i];
      }
      return nameBackwards;
    }
    return `${getReverseName()} si eman ym ,olleH`;
  },
};
o.greetBackwards();

저자는 this가 어디에 묶이는지 명확히 알 수 없도록 호출됐을 때 this를 결정하는 방법은 상황에 따라 매우 다르기 때문에 그러한 상황은 피하는 것이 최선이므로 자세하게 설명하지 않았다.

함수 표현식과 익명 함수

자바스크립트에서 함수를 선언하면 함수에 식별자와 바디가 주어진다. 익명 함수로 선언하게 되면 함수에 식별자가 주어지지 않아 함수 표현식으로 함수를 선언해야 한다.

const f = function() {
  // ...
};

일반적인 함수 선언과 차이점은 익명 함수를 먼저 만들고 그 함수를 변수에 할당했다는 것 뿐이다.

함수 표현식에서는 함수 이름을 생략할 수 있는데 함수에 이름을 정하고 다시 변수에 할당하면 어떻게 될까

const g = function f(stop) {
  if(stop) console.log('f stopped');
  f(true);
};
g(false);

우선 이런식으로 함수를 만들면 g에 우선순위가 있다. 함수 밖에서 함수에 접근할 때는 g를 써야 하며, f로 접근하려 하면 변수가 정의되지 않았다는 에러가 생긴다.
이런 방식이 필요할 때는 함수 안에서 자신을 호출할 때(재귀)이다.

저자는 함수 선언과 함수 표현식의 차이점을 함수 선언은 나중에 호출할 목적으로 함수를 만들 때, 함수 표현식은 함수를 다른 곳에 할당하거나 다른 함수에 넘길 목적으로 함수를 만들 때라고 한다.

화살표 표기법

화살표 표기법은 ES6에서 만들어진 문법으로 function이라는 단어와 중괄호 숫자를 줄이려고 고안된 단축 문법이다. 하지만 저자는 단순히 단축 문법에 그치지 않으며 최근 주목받고 있는 함수형 프로그래밍을 자바스크립트에 도입하는 중요한 열쇠라고 한다.

화살표 함수에는 세 가지 단축 문법이 있다.

  • function 생략 가능
  • 함수에 매개변수가 하나 뿐이라면 괄호 생략 가능
  • 함수 바디가 표현식 하나라면 중괄호와 return 생략 가능
const f1 = function() { return "hello!"; }
👇
const f1 = () => "hello!";

const f2 = function(name) { return `Hello ${name}!`; }
👇
const f2 = name => `Hello ${name}!`;

const f3 = function(a, b) { return a + b; }
👇
const f3 = (a,b) => a + b;

화살표 함수는 익명 함수를 만들어 다른 곳에 전달하려 할 때 가장 유용하며 일반적인 함수와의 차이는 this가 정적으로 묶인다는 점과 객체 생성자로 사용할 수 없다는 점이다.

위에서 다룬 greetBackWards 함수 코드를 아래와 같이 고칠 수 있다.

const o = {
  name : 'Julie',
  greetBackwards : function () {const getReverseName = () => {
      let nameBackwards = '';
      for(let i=this.name.length-1; i>=0; i--) {
        nameBackwards += this.name[i];
      }
      return nameBackwards;
    }
    return `${getReverseName()} si eman ym ,olleH`;
  },
};
o.greetBackwards();

call, apply, bind

자바스크립트에서는 함수를 어디서 어떻게 호출했느냐와 관계없이 this가 무엇인지 지정할 수도 있다.

call

call 메서드는 모든 함수에서 사용이 가능하며 this를 특정 값으로 지정할 수 있다.

const bruce = { name: "Burce" };
const madeline = { name: "Madeline" };

function greet() {
  return `Hello ${this.name}!`;
}

greet(); // "Hello undefined!"
greet.call(bruce); // "Hello bruce!"
greet.call(madeline); // "Hello madeline!"

함수를 호출할 때 call을 사용하고 this로 사용할 객체를 매개변수로 넘기면 해당 함수가 주어진 객체의 메서드인 것처럼 사용이 가능하다. call의 첫 번째 매개변수는 this로 사용할 값이고, 매개변수가 더 있으면 그 매개변수는 호출하는 함수로 전달된다.

function update(birthYear, occupation) {
  this.birthYear = birthYear;
  this.occupation = occupation;
}

update.call(bruce, 1949, 'singer'); // bruce = {name: 'Burce', birthYear: 1949, occupation: 'singer'}
update.call(madeline, 1942, 'actress'); // madeline = {name: 'Madeline', birthYear: 1942, occupation: 'actress'}

apply

apply 메서드는 매개변수를 배열로 받는다는 차이점을 제외하면 call 메서드와 동일하다.
apply는 배열 요소를 함수 매개변수로 사용해야 할 때 유용하다.

const arr = [2, 3, -5, 15, 7];
Math.min.apply(null, arr); // -5
Math.max.apply(null, arr); // 15

👇

Math.min(...arr); // -5
Math.max(...arr); // 15

위 코드에서 Math.max와 Math.min은 this와 관계없이 동작하기 때문에 null을 사용했고, this 값이 무엇이든 상관 없다면 확산 연산자를 사용할 수 있다.

bind

마지막으로 bind 메서드는 this 값을 영구히 바꿀 수 있다.
call이나 apply, 다른 bind가 호출되더라도 this의 값을 고정시키려면 bind를 사용한다.

const updateBruce = update.bind(bruce);

updateBruce(1904, "actor"); // bruce = {name: 'Burce', birthYear: 1904, occupation: 'actor'}
updateBruce(madeline, 1274, "king"); // bruce = {name: 'Burce', birthYear: 1274, occupation: 'king'}

저자는 bind가 매우 유용하지만 bind를 사용한 함수는 call이나 apply, 다른 bind와 함께 사용할 수 없는 것이나 마찬가지이기 때문에 함수의 this가 정확히 어디에 묶이는지 파악하고 사용해야한다고 말한다.

profile
내가 나중에 보려고

0개의 댓글