프로그래밍 공부하기

함수 내의 this 지정: call, apply, bind 본문

Web/[JS] Common

함수 내의 this 지정: call, apply, bind

ihl 2021. 1. 13. 23:18

JavaScript에서 this는 함수 호출 방법에 따라 다른 값을 가리킨다.(바인딩) 그러나 프로그래머가 직접 함수 안의 this를 명시적으로 지정할 수 있는 방법이 있다. 바로 call, apply, bind 함수를 사용하는 것이다.

 

1. call

const student = {
  name: "ihl",
  age: 14,
  printProfile: function(repeat) {
  for(let i = 0; i < repeat; i++)
    console.log(`이름: ${this.name}, 나이: ${this.age}`);
  }
}
let kim = {name: 'kim', age: 21};

student.printProfile(3);  //'이름: ihl, 나이: 14' x3회 출력
student.printProfile.call(kim, 2);  //'이름: kim, 나이: 21' x2회 출력

  call은 첫 번째 인자에 함수 안에서 this가 가리킬 대상, 나머지 인자로 call을 이용해 호출당하는 함수의 매개변수를 사용하여 함수를 바로 실행한다. 위에서 student.printProfile(3)를 호출 했을 때 printProfile 함수 내에서 가리키는 this는 student 객체가 되므로 this.name은 'ihl', this.age는 14가 된다. 반면 student.printProfile.call(kim, 2)를 호출했을 때 printProfile 함수 내에서 가리키는 this는 객체 kim이 되므로 this.name은 'kim', this.age는 21이 된다.

 

//call활용: 유사배열이 Array.prototype 기능 빌려쓰기1
let obj1 = {
  0: 1,
  1: 2,
  2: 3,
  length: 3
}
Array.prototype.map.call(obj1, function(e) { return e*2 } ); //[2, 4, 6]

//call활용: 유사배열이 Array.prototype 기능 빌려쓰기2 (유사배열 string)
let str = 'abc'
Array.prototype.map.call(str, function(char) { return char+char } ); //["aa", "bb", "cc"]

  call은 위와 같이 배열과 유사하지만 배열이 아닌 유사배열에 배열의 메소드를 사용할 때 유용하다. obj1은 length 속성을 갖고 있으며, 나머지 속성 이름이 0,1,2로 숫자이므로 배열의 형태와 유사한 유사배열이다. 따라서 직접 obj1.map()을 호출할 수는 없지만 Array.prototype이 대신 map을 호출하고 call을 이용하여 map 함수의 this를 obj1로 지정함으로써 obj1도 map을 사용할수 있는 것이다.

 

  string도 length 속성이 있으며, 객체처럼 str[0]으로 str 내부 값에 접근할 수 있으므로 유사배열이다. 따라서 string에 없는 method를 Array.prototype이 대신 호출하고 call을 이용하여 함수의 this를 str로 지정함으로써 string이 array의 함수를 사용할 수 있다.

 

2. apply

const student = {
  name: "ihl",
  age: 14,
  printProfile: function(repeat) {
  for(let i = 0; i < repeat; i++)
    console.log(`이름: ${this.name}, 나이: ${this.age}`);
  }
}
let kim = {name: 'kim', age: 21};

student.printProfile(3);  //'이름: ihl, 나이: 14' x3회 출력
student.printProfile.apply(kim, [2]);  //'이름: kim, 나이: 21' x2회 출력

  apply는 call과 동일하나 함수의 매개변수를 배열로 받는 차이가 있다. 위와 같이 printProfile()함수를 실행하기 위한 매개변수 repeat를 call에서는 2로 보내지만, apply에서는 [2]라는 배열 형식으로 보내야 한다.

 

//apply 활용: 여러 개의 인자를 필요로하는 함수에 하나의 인자를 넣어 사용 가능
Math.max(1, 3, 5, 7, 9) //원래 Math.max 사용방법
Math.max.apply(null, [1, 3, 5, 7, 9]);

//spread operator로도 가능하다
Math.max(...[1, 3, 5, 7, 9]);

  apply는 call과 비슷하게 활용가능하다. 다만 apply를 사용하면 함수의 인자가 여러개가 필요한 경우 하나의 배열에 담아 더 간단하게 전달할 수 있다.

 

 

3. bind

const student = {
  name: "ihl",
  age: 14,
  printProfile: function(repeat) {
  for(let i = 0; i < repeat; i++)
    console.log(`이름: ${this.name}, 나이: ${this.age}`);
  }
}
let kim = {name: 'kim', age: 21};
let lee = {name: 'lee', age: 24};

//bind: bind의 첫 번째 인자를 this로 하는 함수 리턴
let kimProfileTwice = student.printProfile.bind(kim, 2);
kimProfileTwice(); //'이름: kim, 나이: 21' x2회 출력

let leeProfileOnce = student.printProfile.bind(lee, 1);
leeProfileOnce(); //'이름: lee, 나이: 24' x1회 출력

  call, apply와 달리 bind는 바로 함수를 실행하지 않고, 전달된 값으로 this가 바인딩된 함수를 리턴한다. kimProfileTwice와 같이 print.Profile에 bind를 이용하여 this를 kim으로 지정하고 함수의 repeat 인자를 2로 지정한 뒤 이를 변수에 저장할 수 있다. 해당 변수를 다시 호출했을 때 bind를 이용해 호출했을 때 당시의 this를 이용한 함수 실행 결과를 출력한다.

 

//부분적용함수: 함수 파라미터의 일부는 나중에 정하기
let kimProfile = student.printProfile.bind(kim);
kimProfile(5); //'이름: kim, 나이: 21' x5회 출력

function add(x,y){ return x+y }
let add1 = add.bind(null, 3,5)
add1(); //3 + 5 = 8

let add2 = add.bind(null, 2)
add2(4); // 2+4 = 6

 

  bind는 함수를 리턴하므로 미리 지정된 초기 인수가 있는 함수를 작성할 수 있다. student.printProfile.bind를 할 때 this에만 인자를 주고 이를 kimProfile을 저장했다면 this에 kim 객체가 바인딩된 함수가 리턴된다. 이 함수를 변수에 담은 뒤 repeat에 해당하는 인자를 넣고 변수(함수)를 호출하면 해당 인자에 따라 콘솔로그 출력 횟수가 달라질 것이다.

 

  함수 수행에 this 인자가 필요 없는 경우 위와 같이 null을 넣어줄 수 있다. 또한 함수 파라미터의 일부를 나중에 정할 수 있다.

 

add.name  // "add"
add1.name // "bound add"
add2.name // "bound add"

  어떤 함수가 bind로 생성된 함수인지 확인하는 방법이 있다. 바로 함수의 name 속성을 출력해보는 것이다. 일반 함수의 경우 name 속성에는 함수의 이름이 있고, bind로 생성된 함수의 경우 원본 함수이름 앞에 bound가 붙어서 출력된다.

 

4. 기타

 1) setTimeout

function Student(name, age) {
  this.name = name;
  this.age = age;
}

Student.prototype.printProfile = function() {
    console.log(`이름: ${this.name}, 나이: ${this.age}`);
  };
  
Student.prototype.printProfileImmed = function() {
    this.printProfile();
  };
  
Student.prototype.printProfileLater = function() {
    setTimeout(this.printProfile, 1000);
  };
  
Student.prototype.printProfileLaterBind = function() {
    setTimeout(this.printProfile.bind(this), 1000);
  };
let kim = new Student('kim', 21);

kim.printProfile();  //① 이름: kim, 나이: 21
kim.printProfileImmed();  //② 이름: kim, 나이: 21
kim.printProfileLater();  //③ 이름: , 나이: undefined
kim.printProfileLaterBind();  //④ 이름: kim, 나이: 21

   setTimeout 콜백함수의 this는 window(또는 global)객체를 가리키는 특징이 있다. 코드의 ①, ②, ③ 에서 setTimeout을 사용한 ③만 유일하게 다른 결과를 출력하고 있다. 새 인스턴스를 가리키는 다른 함수의 this와 달리 setTimeout으로 호출한 함수의 this는 window이므로 this.name은 window.name, this.age는 window.age를 의미하기 때문이다.

  이러한 현상을 해결하는 방법은 bind를 지정하는 것이다. ④처럼 setTimeout의 인자인 함수에 bind로 프로그래머가 원하는 this를 전달할 수 있다.

 

2) this를 매개변수로 받는 함수

let student = {
  name: 'ihl',
  age: '11',
  goalScore: 80,
  score: [72, 88, 100],
  findUnderGoalScore: function(){
    return this.score.find(achieveGoal, this);
  },
  findUnderGoalScore2: function(){
    return this.score.find(achieveGoal);
  }
}

let achieveGoal = function(score){ //콜백함수
  return score < this.goalScore
}

student.findUnderGoalScore(); //① 72
student.findUnderGoalScore2(); //② undefine

  콜백함수를 인자로 받는 함수 중 일부는 콜백함수 내부에서 사용할 this를 인자로 받는 경우가 있다. 배열 메소드인 forEach, map, filter, some, every, find 등이다. 위와 같이 ①의 경우 콜백함수 내부에서 사용할 this(부모객체)를 넘겨주었기 때문에 예상되는 결과가 나왔다. 그러나 ②의 경우 this를 전달하지 않아 콜백함수 내부의 this가 window를 가리키게 된다. 따라서 예상과는 다른 결과가 나오게 되는 것이다.

 

let student = {
  name: 'ihl',
  age: '11',
  goalScore: 80,
  score: [72, 88, 100],
  findUnderGoalScore: function(){
    return this.score.find(function(e){
      return e < this.goalScore;
	}, this);
  },
  findUnderGoalScore2: function(){
    return this.score.find(function(e){
      return e < this.goalScore;
	});
  }
}

student.findUnderGoalScore(); //① 72
student.findUnderGoalScore2(); //② undefine

  콜백함수를 익명함수로 해도 실행은 동일하다.

 


참고 사이트

 

객체와 함수 - JavaScript 객체 지향 프로그래밍

수업소개 자바스크립트는에서 함수는 혼자 있으면 개인이고, new가 앞에 있으면 객체를 만드는 신이고, call을 뒤에 붙이면 용병이고, bind를 붙이면 분신술을 부리는 놀라운 존재입니다. 자바스크

opentutorials.org

 

코어 자바스크립트

자바스크립트의 근간을 이루는 핵심 이론들을 정확하게 이해하는 것을 목표로 합니다!최근 웹 개발 진영은 빠르게 발전하고 있으며, 그 중심에는 자바스크립트가 있다고 해도 결코 과언이 아닙

book.naver.com

'Web > [JS] Common' 카테고리의 다른 글

객체지향프로그래밍의 특징  (0) 2021.01.14
객체 지향 프로그래밍(Object Oriented Programming)  (0) 2021.01.14
화살표 함수  (0) 2021.01.12
undefined와 null  (0) 2021.01.08
불변성과 가변성 2 - let과 const  (0) 2021.01.07
Comments