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

 1) call 메서드

  -call 메서드는 호출 주체인 함수를 즉시 실행하도록 명령

var func = function(){...};
func.call();

  -위와 같이 사용하여 call의 주체인 함수 func를 즉시 실행하도록 명령

  -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

  -func(1, 2, 3)은 첫번째 매개변수를 객체 형태로 지정하지 않았으므로 암묵적으로 원래 함수 호출 시 할당되는 this(전역 객체) 할당

  -func.call({x: 1}, 4, 5, 6)은 첫번째 매개변수를 객체 형태로 지정하여 명시적으로 func 함수의 this 객체를 지정해줌

 

  -객체의 메서드 또한 call 메서드 없이 호출하면 this 객체 = 메서드를 호출한 객체이지만 call 메서드를 통해 특정 객체를 this로 지정할 수 있음

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

  -call 없이 this.a를 호출하면 method를 호출한 객체 obj가 this로 할당되어 obj내에서 선언한 a가 this.a로 출력됨

  -call에서 {a: 4}를 지정하면 method의 this 객체를 명시적으로 {a: 4}로 지정하여 this.a에서 {a: 4}의 a값인 4가 출력됨

 

 

 2) apply 메서드

  -apply 메서드는 call 메서드와 기능적으로 동일하며 함수에 전달할 인자를 배열의 형태로 전달한다는 것만 다름

var func = function(a, b, c) {
  console.log(this, a, b, c);
};
func.apply({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.apply({a: 4}, [5, 6]);    // 4 5 6

 

 

 3) call / apply 메서드의 활용(1): 유사배열객체에 배열 메서드 적용

  -유사배열객체: 배열의 키 값이 0 또는 양의 정수인 프로퍼티가 존재 & length의 프로퍼티 값이 0 또는 양수인 객체(배열의 구조와 유사)

var obj = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3
};
Array.prototype.push.call(obj, 'd');                   // 배열 메서드인 push를 통해 프로퍼티 3에 'd' 추가, length는 자동으로 1 추가
console.log(obj);                                   // {0: 'a', 1: 'b', 2: 'c', 3: 'd', length: 4}

var arr = Array.prototype.slice.call(obj);             // 배열 메서드인 slice 메서드를 통해 유사배열객체의 얕은 복사본 반환
console.log(arr);                                   // ['a', 'b', 'c', 'd']

  -slice 메서드는 시작 인덱스값과 마지막 인덱스값을 매개변수로 받아 시작값부터 마지막 바로 전의 값까지의 배열 요소를 추출하는 메서드

  -slice 메서드에 매개변수를 아무것도 주지 않으면 그냥 원본 배열의 얕은 복사본 반환

  -위 예시에서 call 메서드를 통해 원본 유사배열객체의 얕은 복사를 수행하고, slice 메서드가 배열 메서드이기 때문에 배열로 반환

 

  -배열 형태의 객체 외에 arguments, nodeList 등도 유사배열객체로서 작동

// arguments객체도 유사배열객체이므로 slice 메서드를 사용하여 배열로 전환 가능
function a() {
  var argv = Array.prototype.slice.call(arguments);
  argv.forEach(function(arg) { // argv의 인자들을 각각 순서대로 함수의 인자로 넣음
    console.log(arg);
  });
}
a(1, 2, 3) // 1 2 3


// querySelectAll, getElementByClassName 등의 Node 선택자를 선택한 결과인 nodeList도 유사배열객체로 slice 메서드를 통해 배열로 전환 가능
document.body.innerHTML = '<div>a</div><div>b</div><div>c</div>';
var nodeList = document.querySelectAll('div');
var nodeArr = Array.prototype.slice.call(nodeList);
nodeArr.forEach(function (node) {
    console.log(node);
});

 

※ 주의: 문자열로 인덱스와 length프로퍼티를 지니므로 유사배열객체처럼 사용할 수 있지만, 문자열의 length프로퍼티가 읽기 전용임

  -원본 문자열에 변경을 가하는 메서드(push, pop, shift, unshift, splice 등)은 에러

  -concat처럼 대상이 반드시 배열이어야 하는 경우는 제대로 된 결과 X

// 문자열 선언
var str = 'abc def';

// 문자열에 push 사용 불가능
Array.prototype.push.call(str, ', pushed string');
// 일반적인 배열이면 push를 통해 'abc def'뒤에 ', pushed string'이 추가될 것이지만 문자열은 원본에 변경을 가하는 메서드에 에러 출력

// 문자열에 concat 사용 시 제대로 된 결과 X
Array.prototype.concat.call(str, 'string');
// [ String{"abc def"}, "string" ]

// 문자열에 every와 some 메서드 사용
Array.prototype.every.call(str, function(char) {return char !== ' ' ;});    // false
// 문자열의 모든 문자에 대해 전부 ' '가 아니면 true, 하나라도 ' '이면 false

Array.prototype.some.call(str, function(char) {return char === ' ' ;});  // true
// 문자열의 모든 문자에 대해 하나라도 ' '이면 true, 전부 ' '가 아니면 false

// 문자열에 map 메서드 사용
var newArr = Array.prototype.map.call(str, function(char) {return char + '!';});
// 각 문자에 !를 더하여 배열의 요소로 하나씩 추가

// 문자열에 reduce 메서드 사용
var newStr = Array.prototype.reduce.apply(str, [
  function(string, char, i) {return string + char + i;},
  ''
]);
console.log(newStr);  // "a0b1c2 3d4e5f6"
// reduce 메서드의 매개변수는 앞에서부터 (callback 함수(function)의 반환값을 누적, 배열의 현재 요소, 인덱스, 호출할 배열)
// 처음 시행 시 callback 함수의 반환값은 없으므로 공백에서 시작하여 배열의 첫요소 부터 불러와 인덱스(0부터 시작하는 수)를 문자 뒤에 추가하고 반환

 

  -ES6부터는 유사배열객체, 순회가능한 모든 종류의 데이터타입을 배열로 전환하는 Array.from 메서드 도입

var obj = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3
};
var arr = Array.from(obj);
console.log(arr);          // ['a', 'b', 'c']

 

 

 4) call / appy 메서드의 활용(2): 생성자 내부에서 나른 생성자 호출

  -생성자 내부에 다른 생성자와 공통된 내용이 있으면 call / apply를 이용해 다른 생성자를 호출하면 반복을 줄일 수 있음

// Student, Employee 생성자 내부 함수에서 Person 생성자 함수를 호출해 인스턴스의 속정을 정의

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

function Student(name, gender, school) {
  Person.call(this, name, gender); // name과 gender를 반복적으로 속성으로 정의하지 않고 Person으로부터 호출하면 편리
  this.school = school;                 // Person에는 없고 Student에만 있는 속성, school만 추가하면 됨
}

function Employee(name, gender, company) {
  Person.apply(this, [name, gender]);
  this.company = company;
}
var student1 = new Student('학생1', 'female', 'oo대');
var employee1 = new Employee('직원1', 'male', 'oo');

 

 

 5) apply 메서드의 활용: 여러 인수를 묶어 하나의 배열로 전달

// apply 메서드를 적용하지 않으면
var numbers = [10, 20, 3, 16, 45];
var max = min = numbers[0];             // numbers 배열의 첫번째 값(10)을 max와 min 변수의 초깃값으로 선정
numbers.forEach(function(number) {   // numbers 배열의 각 값을 하나씩 초깃값과 비교하고, 더 큰지 작은지에 따라 max와 min 값을 바꿔가며 전체 배열 중 max와 min 탐색
  if (number > max ) {
    max = number;
  }
  if (number < min ) {
    min = number;
  }
});
console.log(max, min);
// apply 메서드를 적용하면
var numbers = [10, 20, 3, 16, 45];
var max = Math.max.apply(null, numbers); // Math.max 메서드에 apply 메서드 적용
var min = Math.min.apply(null, numbers); // Math.min 메서드에 apply 메서드 적용
console.log(max, min);
// ES6에서는 펼치기 연산자를 적용하여 apply 메서드보다도 간편하게 작성
const numbers = [10, 20, 3, 16, 45];
const max = Math.max(...numbers);
const min = Math.min(...numbers);
console.log(max, min);

 

 

 6) bind 메서드

  -call과 비슷하지만 함수를 즉시 호출하지 않고 넘겨받은 this와 인수들을 바탕으로 새로운 함수를 반환하기만 하는 메서드

  -다시 새로운 함수 호출 시 인수를 넘기면 기존 bund 메서드 호출 시 전달했던 인수 뒤에 추가되는 식으로 등록됨

  -bind 메서드는 함수에 this를 미리 적용하는 것과 부분 적용 함수를 구현하는 두 가지 목적

// bind 메서드로 호출할 함수
var func = function (a, b, c, d) {
  console.log(this, a, b, c, d);
};
func(1, 2, 3, 4); // Window{ ... } 1 2 3 4
// call이나 apply나 bind 메서드 없이 함수를 호출할 때 this는 전역객체임

// bind 메서드로 this 객체 선언(call이나 apply와 같은 기능)
var bindFunc1 = func.bind({x: 1});
bindFunc1(5, 6, 7, 8); // {x: 1} 5 6 7 8

// bind 메서드로 인자를 일부 넘겨주고 이후에 함수를 호출할 때 추가 인자 넘겨주기
var bindFunc2 = func.bind({x: 1}, 4, 5); // this 객체의 선언과 인자 4개 중 앞의 두 개만 미리 선언
bindFunc2(6, 7); // {x: 1} 4 5 6 7
// 나머지 뒤의 두 개의 인자와 함께 함수 호출
bincFunc2(8, 9); // {x: 1} 4 5 8 9
// 함수의 인자를 바꿨을 때, 마지막 두 개의 인자만 값이 바뀐 것을 알 수 있음

 

  -bind 메서드를 적용해서 만든 함수는 name 프로퍼티에 'bound(bind의 수동태)'라는 접두어가 붙음

  -call이나 apply 메서드를 적용한 함수보다 추적이 편리

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

var bindFunc = func.bind({x: 1}, 4, 5);

console.log(func.name)           // func
console.log(bindFunc.name)    // bound func

 

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

// call 메서드 사용
var obj = {
    outer: function() {
          console.log(this);
        var innerFunc = function() {
            console.log(this);
        };
        innerFunc.call(this); // call 함수를 통해 이 줄을 실행할 때의 객체, obj 객체를 innerFunc 실행 시의 this 객체로 전달
    }
};
obj.outer();
// {ouert: f} -> obj 객체
// {outer: f} -> obj 객체
// bind 메서드 사용
var obj = {
    outer: function() {
          console.log(this);
        var innerFunc = function() {
            console.log(this);
        }.bind(this);
        innerFunc();
    }
};
obj.outer();
// {ouert: f} -> obj 객체
// {outer: f} -> obj 객체
// 콜백 함수에 bind 메서드를 통해 this 객체 전달
var obj = {
  logThis: function() {
    console.log(this);
  },
  
  // 따로 this 객체를 지정해주지 않은 콜백함수는 일반 함수와 마찬가지로 전역객체를 this 객체로 가짐
  logThisLater1:function() {
    setTimeout(this.logThis, 500);
  },
  
  // bind 메서드를 통해 this 객체를 지정하여 콜백함수로 넘겨줄 수 있음
  // 바로 직전 객체(obj)를 this 객체로 넘겨줌
  logThisLater2: function() {
    setTimeout(this.logThis.bind(this), 1000);
  }
};
obj.logThisLater1();  // Window{ ... }
obj.logThisLater2();  // obj {logThis: f, ...}

 

 

 7) 화살표 함수는 실행 컨텍스트 생성 시 this를 바인딩하는 과정이 제외, 함수 내부에 this가 없고 접근하면 가장 가까운 this에 접근

var obj = {
  outer: function () {
    console.log(this);
    var innerFunc = () => {
      console.log(this);
    };
    innerFunc();
  }
};
obj.outer();
// {ouert: f} -> obj 객체
// {outer: f} -> obj 객체

 

 

 8) 콜백함수 내에서 this를 별도의 인자로 받기

var report = {
  // sum과 count 두 개의 프로퍼티에 초깃값 0을 부여하며 선언
  sum: 0,
  count: 0,
  
  // 합계를 구하는 메서드
  add: function () {
    var args = Array.prototype.slice.call(arguments);  // 인자로 받은 arguments를 배열 형태로 바꿔서
    args.forEach(function(entry) {                             // 각각 차례대로 함수에 넣음
      this.sum += entry;                                          // 함수 내에서는 초깃값이 0인 sum에 인자들을 더하며 반환하는 과정을 반복
      ++this.count;                                                 // 반복할 때마다 개수도 하나씩 늘려 총 몇개의 인자를 받았는지 계산
    }, this);                                                           // forEach 내부의 콜백함수의 this는 두번째 인자로 전달해준 add메서드의 this(report 객체)가 바인딩됨
  },                                                                      // 따라서, report.sum에 인자가 더해지고, report.count가 1씩 늘어나 저장됨

  average: function () {
    return this.sum / this.count; // average 메서드의 this 또한 report객체이며 this.sum, this.count는 각각 report.sum, report.count이며 add 메서드에서 더해져 저장된 값들임
  }
};

report.add(60, 85, 95);
console.log(report.sum, report.count, report.averge());  // 240 3 80

 

  -forEach 외에 thisArg를 인자로 받는 메서드

Array.prototype.forEach(callback[, thisArg])
Array.prototype.map(callback[, thisArg])
Array.prototype.filter(callback[, thisArg])
Array.prototype.some(callback[, thisArg])
Array.prototype.every(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])

+ Recent posts