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

시대의 명작 코어 자바스크립트를 읽고 자바스크립트 마스터에 도전합니다...

명시적으로 this를 바인딩하기

상황별로 this에 값이 바인딩 되는 규칙을 깨고 별도의 대상을 바인딩 하는 방법도 있다.

call 메서드

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

call 메서드는 메서드의 호출 주체인 함수를 즉시 실행하도록 하는 명령어이다. call 메서드의 첫번째 인자를 this로 바인딩하고, 이후의 인자들은 호출할 함수의 매개변수가 된다. 이렇게 하면 함수가 실행되어도 this가 전역객체를 참조하는 것이 아니라 임의의 지정된 객체를 바라보게 된다. 메서드로서 호출할 때도 call 메서드를 이용하면 this가 객체가 되는 것이 아니라 지정된 객체를 바라본다!!

// 함수로 호출할 때
var 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

// 메서드로 호출할 때
var 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

apply 메서드

apply 메서드는 call 메서드와 거의 동일하다고 볼 수 있다. 다만 apply 메서드는 두번째 인자를 배열로 받아 호출한 함수의 매개변수로 지정한다.

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

call / apply 메서드의 활용

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

유사배열객체는 call, apply 메서드를 사용해서 배열 메서드를 차용할 수 있다.

유사배열객체 : 키가 0 또는 양의 정수인 프로퍼티가 존재하고, length 프로퍼티의 값이 0 또는 양의 정수인 객체

1) 유사배열객체

var obj = {
  0: 'a',
  1: 'b',
  length: 2
};

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

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

2) arguments

function aaa () {
  var args = Array.prototype.slice.call(arguments);
  args.forEach(function (arg) {
    console.log(arg);
  });
}

aaa(1, 2, 3); // 1 2 3

3) NodeList

: querySelectorAll, getElementsByClassName 등의 Node 선택자로 선택한 결과

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

4) 문자열

* 문자열은 length 프로퍼티가 읽기 전용**이기 때문에 원본에 변경을 가하는 메서드 (push, pop, shift, unshift, splice ...)를 사용하면 에러가 뜬다. concat처럼 대상이 반드시 배열이여야 하는 경우에도 제대로 된 결과를 얻을 수 없다.

var str = 'mia lee';

Array.prototype.push.call(str, ' pushed string');
// 에러 발생!
Array.prototype.concat.call(str, 'zzang');
// [String {'mia lee'}, 'zzang']

// 원본에 변경을 가하지 않는 경우
Array.prototype.every.call(str, function(char) { return char !== ' ' }); // false
Array.prototype.some.call(str, function(char) { return char === ' ' }); // true

var newArr = Array.prototype.map.call(str, function(char) { return char + '!'; });
console.log(newArr); // ['m!', 'i!', 'a!', 'l!', 'e!', 'e!']

var newStr = Array.prototype.reduce.apply(str, [
  function(string, char, i) { return string + char + i; }, ''
]);
console.log(newStr); // 'm0i1a2 3l4e5e6'

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

생성자에 공통된 내용이 있을 경우에 call이나 apply를 사용해서 중복을 줄일 수 있다.

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

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

function Employee(name, age, company) {
  Person.call(this, name, age);
  this.company = company;
}

var jh = new Student('지형', '26', '홍익대');
var mia = new Employee('미아', '36', '구글');

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

배열에서 최대/최소값을 구하는 경우를 비교해보자.

직접 구현하기

var numbers = [10, 20, 3, 18, 93];
var max = min = numbers[0];
numbers.forEach(function(number) {
  if (number > max) {
    max = number;
  }
  if (number < min) {
    min = number;
  }
});
console.log(max, min);

Math.max/Math.min 메서드에 apply 적용하기

var numbers = [10, 20, 3, 18, 93];
var max = Math.max.apply(null, numbers);
var min = Math.min.apply(null, numbers);
console.log(max, min);

와우..! 쏘 이지!!!

스프레드 연산자 (ES6+)

var numbers = [10, 20, 3, 18, 93];
var max = Math.max(...numbers);
var min = Math.min(...numbers);
console.log(max, min);

bind 메서드

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

call 메서드와 거의 유사하지만, 바로 호출하지 않고 새로운 함수를 반환한다. 인자도 지정하여 부분으로 적용된 함수를 구현할 수 있다.

var func = function (name, surname) {
  console.log(this, name, surname);
};
func('dongwon', 'kang'); // Window{ ... } dongwon kang

// this를 지정
var person1 = func.bind('great');
person1('mia', 'lee'); // great mia lee

// this를 지정하고 부분 적용 함수를 구현
var person2 = func.bind('great', 'jieun');
person1('lee'); // great jieun lee

name 프로퍼티

bind 메서드를 적용해서 만든 함수에는 name 프로퍼티에 bound라는 접두어가 붇는다. 코드를 추적할때 유용하게 사용할 수 있다.

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

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

self 등의 우회법 없이, call, apply, bind 메서드로 상위 컨텍스트의 this를 그대로 바라보게 할 수 있다.

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

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

화살표 함수의 예외사항

화살표 함수를 사용하면 스코프체인상 가장 가까운 this에 접근하게 된다. 화살표 함수를 사용한다면 굳이 this를 우회하거나 위의 메소드들을 사용하지 않아도 된다.

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

별도의 인자로 this를 받는 경우(콜백 함수 내에서의 this)

콜백 함수를 인자로 받는 메서드 중 일부는 this로 지정할 객체를 직접 인자로 지정할 수 있는 경우도 있다.

콜백 함수와 함께 thisArg를 인자로 받는 메서드

Array.prototype.forEach(callback[, thisArg])
Array.prototype.map(callback[, thisArg])
Array.prototype.filter(callback[, thisArg])
Array.prototype.some(callback[, thisArg])
Array.prototype.evert(callback[, thisArg])
Array.prototype.find(callback[, thisArg])
Array.prototype.findIndex(callback[, thisArg])
Array.prototype.flatMap(callback[, thisArg])
Array.prototype.from(arrayLike[, callback[, thisArg]])
Set.prototype.forEach(callback[, thisArg])
Map.prototype.forEach(callback[, thisArg])

이렇게 쓴다!

var report = {
  sum: 0,
  count: 0,
  add: function () {
    var args = Array.prototype.slice.call(arguments);
    args.forEach(function (entry) {
      this.sum += entry;
      ++this.count;
    }, this); // 여기 두번째 인자로 전해준 this!
  },
  average: function () {
    return this.sum / this.count;
  }
};

좋은 웹페이지 즐겨찾기