TIL 030 | 코어 자바스크립트 - this(2)

JU CHEOLJIN·2021년 8월 11일
0

JavaScript

목록 보기
12/13
post-thumbnail

저번에는 this에 대해서 정리를 했다. 이번에는 this를 원하는 대상에 바인딩하는 방법에 대해서 정리해보려고 한다. this를 이해하지 못하면 앞날이 무척이나 피곤할 예정이니 어서 뿌셔보자.

명시적으로 this를 바인딩하는 방법

상황에 따라서 this는 다른 값을 바인딩하지만 규칙을 깨고 별도의 대상을 바인딩하도록 하는 방법이 있다.

call 메서드

Function.prototype.call(thisArg[, arg1[, arg2[, ...]]])

call 메서드는 호출 주체인 함수를 즉시 실행하도록 하는 명령이다. 이 때 call 메서드의 첫 번째 인자를 this로 바인딩하고, 이후의 인자들을 호출할 함수의 매개변수로 한다.

let func = function(a, b, c){
  console.log(this, a, b, c);
};

func(1, 2, 3); // window{...} 1 2 3
func.call({x:1}, 4, 5, 6) // {x:1} 4 5 6

위의 경우는 함수로 호출하는 경우이다. call 을 사용하자 함수를 호출했음에도 불구하고 this 가 전역 객체가 아닌 첫 번째 인자로 제공한 객체를 바인딩했다.

let obj = {
  a: 1,
  method: function(x, y) {
    console.log(this.a, x, y);
  }
};

obj.method(2,3); // 1 2 3
obj.method.call({a: 4}, 5, 6); // 4 5 6

메서드로서 호출하는 경우도 비슷하다. 메서드를 그냥 호출하면 this 가 객체를 참조하지만, call을 사용하면 임의의 객체를 지정할 수 있다.

apply 메서드

apply 메서드는 call 메서드와 기능적으로 동일하다. 다만, apply 메서드의 경우 두 번째 인자를 배열로 받아 그 배열의 요소들을 호출할 함수의 매개변수로 지정한다는 점에서 차이가 있다.

Function.prototype.apply(thisArg[, argsArray]);

call / apply 메서드의 활용

유사배열객체에 배열 메서드를 적용

원래 객체에는 배열 메서드를 직접 적용할 수 없지만, 키가 0 또는 양의 정수인 프로퍼티가 존재하고 length 프로퍼티의 값이 0 또는 양의 정수인 객체, 즉 유사배열객체의 경우에는 call 또는 apply 를 통해서 배열 메서드를 차용할 수 있다.

1. 유사배열객체에 배열 메서드를 적용

let obj = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3
};
Array.prototype.push.call(obj, 'd');
console.log(obj); // {0: 'a', 1: 'b', 2: 'c', 3: 'd', length: 4}

let arr = Array.prototype.slice.call(obj);
console.log(arr); // ['a', 'b', 'c', 'd']

2. arguments

function a () {
  let args = Array.prototype.slice.call(arguments);
  args.forEach(function(arg){
    console.log(arg);
  });
}
a(1, 2, 3); // 1 2 3

3. NodeList
NodeList는 querySelectorAll, getElementsByClassName 등의 Node 선택자로 선택한 결과로 유사배열이다.

document.body.innerHTML = '<div>a</div><div>b</div><div>c</div>';
let nodeList = document.querySelectorAll('div');
let nodeArr = Array.prototype.slice.call(nodeList);
nodeArr.forEach(function (node) {
  console.log(node); 
  // <div>a</div>
  // <div>b</div>
  // <div>c</div>
});

4. 문자열

let str = 'cheol jin';

Array.prototype.push.call(str, ', pushed string');
// Error: Cannot assign to read only property 'length' of object [object String]

Array.prototype.concat.call(str, 'string'); // [String {"cheol jin"}, "string" ]
// 원본에 변경을 가하지 않는 경우
Array.prototype.every.call(str, function(char){return char !== ' ';}); // false

Array.prototype.some.call(str, function(char){return char === ' ';}); // true

let newArr = Array.prototype.map.call(str, function(char) { return char + '!';});

console.log(newArr); // ["c!", "h!", "e!", "o!", "l!", " !", "j!", "i!", "n!"]

let newStr = Array.prototype.reduce.apply(str, [ function(string, char, i){return string + char + i; }, '']);
console.log(newStr); // c0h1e2o3l4 5j6i7n8

문자열의 경우에 length 가 읽기 전용이기 때문에 원본에 변경을 가하는 메서드를 사용하는 경우(pop, push, shift, unshift, splice 등) 에러를 보낸다. concat의 경우에도 배열이 아니기 때문에 제대로 된 결과값을 얻을 수 없다.

생성자 내부에서 다른 생성자를 호출

생성자에 공통된 내용이 있을 경우 call 또는 apply 를 이용해 다른 생성자를 호출하면 간단하게 반복을 줄일 수 있다.

function Person(name, gender){
  this.name = name;
  this.gender = gender;
}
function Student(name, gender, school){
  Person.call(this, name, gender);
  this.school = school;
}
function Employee(name, gender, company){
  Person.apply(this, [name, gender]);
  this.company = company;
}
let jy = new Student("주영", "male", "전남대");
let cj = new Employee("철진", "male", "구글"); 

여러 인수를 묶어 하나의 배열로 전달하고 싶을 때

여러 개의 인수를 받는 메서드에게 하나의 배열로 인수들을 전달하고 싶을 때 apply 메서드를 이용하면 좋다. 최대 / 최솟값을 구해야 하는 경우를 보자.

직접 구현하는 코드

// 직접 구현하는 코드
let numbers = [10, 20, 3, 16, 45];
let max = min = numbers[0];
numbers.forEach(function(number){
  if(number > max){
    max = number;
  }
  if(number < min){
    min = number;
  }
});
console.log(max, min); // 45, 3

apply 활용

// apply 활용하는 코드
let numbers = [10, 20, 3, 16, 45];
let max = Math.max.apply(null, numbers);
let min = Math.min.apply(null, numbers);

ES6 펼리치 연산자 활용

const numbers = [10, 20, 3, 16, 45];
const max = Math.max(...numbers);
const min = Math.min(...numbers);
console.log(mx, min); // 45 3

bind 메서드

Function.prototype.bind(thisArg[, arg1[, age2[, ...]]])

bind 메서드는 ES5에서 추가된 기능으로 call 과 비슷하지만 즉시 호출하지는 않고 넘겨 받은 this 및 인수들을 바탕으로 새로운 함수를 반환하기만 하는 메서드이다.

let func = function(name, age){
  console.log(this, name, age);
};
func("cheoljin", 20); // window{...} "cheoljin" 20 
// this를 지정
let bindFunc1 = func.bind("kind people");
bindFunc1("cheoljin", 20); // {"kind people"} "cheoljin" 20
// this를 지정하고 부분 적용 함수를 구현
let bindFunc2 = func.bind("kind people", "cheoljin"); 
bindFunc2("20"); // {"kind people"} "cheoljin" 20

name 프로퍼티

bind 메서드를 사용해서 만든 함수는 독특한 성질이 있다. 바로 name 프로퍼티에 동사 bind의 수동태인 bound라는 접두어가 붙는다는 점이다. 이를 통해 call 이나 apply에 비해 추적하기 좋아졌다.

console.log(func.name); // func
console.log(bindFunc1.name); // bound func

상위 컨텍스트의 this를 내부함수나 콜백함수에 전달하기

call, apply, bind 메서드를 사용하면 우회를 할 필요없이 깔끔하게 처리할 수 있다.

// call 메소드 사용
let obj = {
  outer: function() {
    console.log(this);
    let innerFunc = function () {
      console.log(this);
    };
    innerFunc.call(this);
  }
}

// bind 메소드 사용
let obj = {
  outer: function() {
    console.log(this);
    let innerFunc = function () {
      console.log(this);
    }.bind(this);
    innerFunc();
  }
}

화살표 함수의 예외사항

ES6에 새롭게 도입된 화살표 함수는 this를 바인딩하는 과정이 제외됐기 때문에 접근하려고 하면 스코프 체인 상 가장 가까운 this 에 접근하게 된다.

let obj = {
  outer: function(){
    console.log(this);
    let innerFunc = () => {
      console.log(this);
    };
    innerFunc();
  }
};
obj.outer();

마치며! ✨
this는 무엇일지 예측하는 연습이 필요하다. 명시적으로 바인딩하지 않은 상황에서 어떤 값으로 사용될지 주의하며 사용하자.

profile
사회에 도움이 되는 것은 꿈, 바로 옆의 도움이 되는 것은 평생 목표인 개발자.

1개의 댓글

comment-user-thumbnail
2021년 8월 13일

this는 알면 알수록 어려운 것 같아요ㅠㅠ 정리가 깔끔해서 계속 읽어보고 이해해보겠습니다!!

답글 달기

관련 채용 정보