이번 강의에서 배울것은요?

this에 대해 배울거에요. 다른 객체지향 언어에서는 this는 곳 클래스로 생성한 인스턴스를 말합니다. 그러나 자바스크립트에서는 this가 어디에서나 사용될 수 있어요. this가 상황별로 어떻게 달라지는지, 이유는 뭔지, this를 추적하는 방법 등을 알아 볼 예정입니다.

  1. 상황에 따라 달라지는 this

    1. this는 실행 컨텍스트가 생성될 때 결정(this binding) === this는 함수를 호출할 때 결정

      1. 전역 공간에서의 this
        1. 전역 공간에서 this는 전역 객체를 가리킴

        2. window(브라우저 환경), global(node 환경)

          console.log(this);
          console.log(window);
          console.log(this === window); //true
          
    2. 메서드로서 호출할 때 그 메서드 내부에서의 this

      1. 함수 vs 메서드

        1. 기준 : 독립성
        2. 함수 : 그 자체로 독립적인 기능을 수행
        3. 메서드 : 자신을 호출한 대상 객체에 관한 동작을 수행
      2. 함수와 메서드가 호출될 때, this는 각각 다르게 할당

        var func = function (x) {
        	console.log(this, x);
        };
        func(1); // Window { ... } 1
        
        var obj = {
        	method: func,
        };
        obj.method(2); // { method: f } 2
        
      3. 함수로서의 호출과 메서드로서의 호출 구분 기준 : . []

        var obj = {
        	method: function (x) { console.log(this, x) }
        };
        obj.method(1); // { method: f } 1
        obj['method'](2); // { method: f } 2
        
      4. 메서드 내부에서의 this

        var obj = {
        	methodA: function () { console.log(this) },
        	inner: {
        		methodB: function() { console.log(this) },
        	}
        };
        
        obj.methodA();             // this === obj
        obj['methodA']();          // this === obj
        
        obj.inner.methodB();       // this === obj.inner
        obj.inner['methodB']();    // this === obj.inner
        obj['inner'].methodB();    // this === obj.inner
        obj['inner']['methodB'](); // this === obj.inner
        
    3. 함수로서 호출할 때 그 함수 내부에서의 this

      1. 함수 내부에서의 this
        1. 어떤 함수를 함수로서 호출할 경우, this는 지정되지 않음(호출 주체가 없으므로)
        2. 실행컨텍스트를 활성화할 당시 this가 지정되지 않은 경우, this는 전역 객체를 바라봄
        3. 따라서, 함수로서 ‘독립적으로’ 호출할 때는 this는 전역 객체
      2. 메서드의 내부함수에서의 this
        1. 메서드의 내부라고 해도, 함수로서 호출한다면 this는 전역 객체

          var obj1 = {
          	outer: function() {
          		console.log(this);
          		var innerFunc = function() {
          			console.log(this);
          		}
          		innerFunc();
          
          		var obj2 = {
          			innerMethod: innerFunc
          		};
          		obj2.innerMethod();
          	}
          };
          obj1.outer();
          
        2. this 바인딩에 관해서는 함수를 실행하는 당시의 주변 환경(메서드 내부인지, 함수 내부인지)은 중요하지 않고, 오직 해당 함수를 호출하는 구문 앞에 점 또는 대괄호 표기가 있는지가 관건임!

        3. 하지만 그럼에도 불구하고 doesn’t make sense!

      3. 메서드의 내부 함수에서의 this 우회
        1. 변수 활용

          var obj1 = {
          	outer: function() {
          		console.log(this);
          		var innerFunc1 = function() {
          			console.log(this);
          		}
          		innerFunc1();
          
          		var self = this;
          		var innerFunc2 = function() {
          			console.log(self);
          		};
          		innerFunc2();
          	}
          };
          obj1.outer();
          
        2. 화살표 함수

          1. ES6에서 처음 도입된 화살표 함수는, 실행 컨텍스트를 생성할 때 this 바인딩 과정 자체가 없음(따라서, this는 이전의 값-상위값-이 유지됨)

            var obj = {
            	outer: function() {
            		console.log(this);
            		var innerFunc = () => {
            			console.log(this);
            		};
            		innerFunc();
            	}
            }
            
            obj.outer();
            
    4. 콜백 함수 호출 시 그 함수 내부에서의 this

      setTimeout(function () { console.log(this) }, 300);
      
      [1, 2, 3, 4, 5].forEach(function(x) {
      	console.log(this, x);
      });
      
      document.body.innerHTML += '<button id="a">클릭</button>';
      document.body.querySelector('#a').addEventListener('click', function(e) {
      	console.log(this, e);
      });
      
      1. setTimeout 함수, forEach 메서드는 콜백 함수를 호출할 때 대상이 될 this를 지정하지 않으므로, this는 곧 window객체
      2. addEventListner 메서드는 콜백 함수 호출 시, 자신의 this를 상속하므로, this는 addEventListner의 앞부분(button 태그)
    5. 생성자 함수 내부에서의 this

      1. 생성자 : 구체적인 인스턴스를 만들기 위한 일종의 틀

      2. 공통 속성들이 이미 준비돼 있음

        var Cat = function (name, age) {
        	this.bark = '야옹';
        	this.name = name;
        	this.age = age;
        };
        
        var choco = new Cat('초코', 7); //this : choco
        var nabi = new Cat('나비', 5);  //this : nabi
        
  2. 명시적 this 바인딩(자동으로 부여되는 상황별 this의 규칙을 깨고 this에 별도의 값을 저장하는 방법)

    1. call 메서드

      1. 호출 주체인 함수를 즉시 실행하는 명령어

      2. 예시

        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
        
    2. apply 메서드

      1. call 메서드와 완전 동일

      2. 두 번째 인자가 배열인 부분만 다름

      3. 예시

        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. 유사배열객체(array-like-object)에 배열 메서드를 적용

        (출처 : https://kamang-it.tistory.com/entry/JavaScript15유사배열-객체Arraylike-Objects)

        (출처 : https://kamang-it.tistory.com/entry/JavaScript15유사배열-객체Arraylike-Objects)

        (출처 : https://www.daleseo.com/js-array-slice-splice/)

        (출처 : https://www.daleseo.com/js-array-slice-splice/)

        //객체에는 배열 메서드를 직접 적용할 수 없어요.
        //유사배열객체에는 call 또는 apply 메서드를 이용해 배열 메서드를 차용할 수 있어요.
        
        var 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 }
        
        var arr = Array.prototype.slice.call(obj);
        console.log(arr); // [ 'a', 'b', 'c', 'd' ]
        
      2. arguments, NodeList에 배열 메서드를 적용(VSCode)

      3. Array.from 메서드(ES6)

        var obj = {
        	0: 'a',
        	1: 'b',
        	2: 'c',
        	length: 3
        };
        var arr = Array.from(obj);
        console.log(arr);
        
      4. 생성자 내부에서 다른 생성자를 호출(공통된 내용의 반복 제거)

        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;
        }
        var wj = new Student('원장', 'male', '서울대');
        var tj = new Employee('투장', 'female', '삼성');
        
      5. 여러 인수를 묶어 하나의 배열로 전달할 때 apply 사용

        1. 최대/최솟값 예제

          //비효율
          var numbers = [10, 20, 3, 16, 45];
          var max = min = numbers[0];
          numbers.forEach(function(number) {
          	if (number > max) {
          		max = number;
          	}
          	if (number < min) {
          		min = number;
          	}
          });
          
          console.log(max, min);
          
          //효율
          var numbers = [10, 20, 3, 16, 45];
          var max = Math.max.apply(null, numbers);
          var min = Math.min.apply(null, numbers);
          console.log(max, min);
          
          //Spread Operation(ES6)
          //https://paperblock.tistory.com/62
          const numbers = [10, 20, 3, 16, 45];
          const max = Math.max(...numbers);
          const min = Math.min(...numbers);
          console.log(max min);
          
    4. bind 메서드

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

      2. 목적

        1. 함수에 this를 미리 적용하는 것
        2. 부분 적용 함수 구현
        var func = function (a, b, c, d) {
        	console.log(this, a, b, c, d);
        };
        func(1, 2, 3, 4); // window객체
        
        var bindFunc1 = func.bind({ x: 1 });
        bindFunc1(5, 6, 7, 8); // { x: 1 } 5 6 7 8
        
        var bindFunc2 = func.bind({ x: 1 }, 4, 5);
        bindFunc2(6, 7); // { x: 1 } 4 5 6 7
        bindFunc2(8, 9); // { x: 1 } 4 5 8 9
        
      3. name 프로퍼티

        1. bind 메서드를 적용해서 새로 만든 함수는 name 프로퍼티에 ‘bound’ 라는 접두어가 붙음(추적하기가 쉬움)

          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
          
      4. 상위 컨텍스트의 this를 내부함수나 콜백 함수에 전달하기

        1. 내부함수

          1. 메서드의 내부함수에서 메서드의 this를 그대로 사용하기 위한 방법
          2. self 등의 변수를 활용한 우회법보다 call, apply, bind를 사용하면 깔끔하게 처리 가능
          var obj = {
          	outer: function() {
          		console.log(this);
          		var innerFunc = function () {
          			console.log(this);
          		};
          		innerFunc.call(this);
          	}
          };
          obj.outer();
          
          var obj = {
          	outer: function() {
          		console.log(this);
          		var innerFunc = function () {
          			console.log(this);
          		}.bind(this);
          		innerFunc();
          	}
          };
          obj.outer();
          
        2. 콜백함수

          1. 콜백함수도 함수이기 때문에, 함수가 인자로 전달될 때는 함수 자체로 전달
          2. bind메서드를 이용해 this를 입맛에 맞게 변경 가능
          var obj = {
          	logThis: function () {
          		console.log(this);
          	},
          	logThisLater1: function () {
          		setTimeout(this.logThis, 500);
          	},
          	logThisLater2: function () {
          		setTimeout(this.logThis.bind(this), 1000);
          	}
          };
          
          obj.logThisLater1();
          obj.logThisLater2();
          
    5. 화살표 함수의 예외사항

      1. 화살표 함수는 실행 컨텍스트 생성 시, this를 바인딩하는 과정이 제외
      2. 이 함수 내부에는 this가 아에 없으며, 접근코자 하면 스코프체인상 가장 가까운 this에 접근하게 됨
      3. this우회, call, apply, bind보다 편리한 방법
      var obj = {
      	outer: function () {
      		console.log(this);
      		var innerFunc = () => {
      			console.log(this);
      		};
      		innerFunc();
      	};
      };
      obj.outer();
      
    6. 별도의 인자로 this를 받는 경우(콜백 함수 내에서의 this)

      1. 콜백 함수를 인자로 받는 메서드 중 일부는 추가로 this로 지정할 객체를 인자로 지정할 수 있음
      2. 배열과 관련된 메서드에 많이 존재하며, set, map 등의 메서드에도 일부 존재함
      // forEach 예시
      
      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);
      	},
      	average: function () {
      		return this.sum / this.count;
      	},
      };
      report.add(60, 85, 95);
      console.log(report.sum, report.count, report.average());
      
      // 콜백 함수와 함께 thisArg를 인자로 받는 메서드
      // forEach, map, filter, some, every, find, findIndex, flatMap, from, forEach(Set, Map)