JavaScript

[JS] this 에 대한 모든 것

Leo(상원) 2022. 12. 1. 12:10
반응형

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

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

i. 전역 공간에서의 this

  • 1. 전역 공간에서 this는 전역 객체를 가르킴
  • window(브라우저 환경), global(node 환경)
console.log(this)  // 전역공간에서의 this는 window를 지칭한다.
console.log(window)
console.log(this === window) // true

 

메서드로서 호출할 때 그 메서드 내부에서의 this

i. 함수 vs 메서드

  • 기준: 독립성
  • 함수: 그자체로 독립적인 기능을 수행
  • 메서드: 자신을 호출한 대상 객체에 관한 동작을 수행

ii. 함수와 메서드가 호출될 때, this는 각각 다르게 할당

var func = function (x) {
     console.log(this, x)
}

var obj = {
     method: func,
}
obj.method(2) // { method: f } 2

iii. 함수로서의 호출과 메서드로서의 호출 구분 기준: . []

var obj = {
    method: function (x) {  console.log(this, x)  }
}
obj.method(1) // { method: f } 1
obj['method'](2)  // { method: f } 2

iv. 메서드 내부에서의 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

 

함수로서 호출할 때 그 함수 내부에서의 this

i. 함수 내부에서의 this

  • 어떤 함수를 함수로서 호출할 경우, this는 지정되지 않음(호출 주제가 없으므로)
  • 실행컨텍스트를 활성화할 당시 this가 지정되지 않은 경우, this는 전역 객체를 바라봄
  • 따라서, 함수로서 "독립적으로" 호출할 때는 this는 전역 객체

ii. 메서드의 내부함수에서의 this

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

iii. 메서드의 내부 함수에서의 this 우회

변수 활용
var obj1 = {
      outer: function() {
           console.log(this) // this === obj1
           var innerFunc1 = function() {
                 console.log(this)   // this === window
            }
            innerFunc1()

            var self = this
            var innerFunc2 = function() {
                    console.log(self)
             }
            innerFunc2()
         }
}
obj1.outer()
화살표 함수
  • ES6에서 처음 도입된 화살표 함수는, 실행 컨텍스트를 생성할 때 this 바인딩 과정 자체가 없음 (따라서, this는 이전의 값-상위값-이 유지됨)
var obj = {
      outer: function() {
            console.log(this)
            var innerFunc = () => {
                   console.log(this)
             }
             innerFunc()
         }
}

obj.outer()
콜백 함수 호출 시 그 함수 내부에서의 this
setTimeout (function () { console.log(this) }, 300)   // window

[1, 2, 3, 4, 5].forEach(function(x) { console.log(this, x)})  // window

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

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

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

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

 

명시적 this 바인딩 (자동으로 부여되는 상황별 this의 규칙을 깨고 this에 별도의 값을 저장하는 방법)

i. call 메서드

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

예시) 

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

ii. 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

 iii. call / apply 메서드 활용

  • 유사배열객체 (array-like-object)에 배열 메서드를 적용

 

객체에는 배열 메서드를 직접 적용할 수 없어요.
유사배열객체에는 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" ]
  • arguments, NodeList에 배열 메서드를 적용 (VScode)
  • Array.from 메서드(ES6)
var obj = {
    0: "a",
    1: "b",
    2: "c",
    length: 3
}

var arr = Array.from(obj)
console.log(arr)
  • 생성자 내부에서 다른 생성자를 호출 (공통된 내용의 반복 제거)
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 sw = new Student("상원", "male", "서울대")
var leo = new Employee("레오", "female", "애플")
  • 여러 인수를 묶어 하나의 배열로 전달할 때 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)
const numbers = [10, 20, 3, 16, 45]
const max = Math.max(...numbers)
const min = Math.min(...numbers)
console.log(max, min)
bind 메서드

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

ii. 목적

  • 함수에 this를 미리 적용하는 것
  • 부분 적용 함수 구현
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

iii. name 프로퍼티

  • 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 

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

내부함수
  • 메서드의 내부함수에서 메서드의 this를 그대로 사용하기 위한 방법
  • self 등의 변수를 활용한 우회법보다 apply, call, 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()
콜백함수
  • 콜백함수도 함수이기 때문에, 함수가 인자로 전달될 때는 함수 자체로 전달
  • bind메서드를 이용해 this를 입맛에 맞게 변경 가능
var obj = {
     logThis: function () {
         console.log(this)
      },
      logThisLater1: function () {   //  정상동작 x
           setTimeOut(this.logthis, 500)
       },
       logThisLater2: function () {   // 정상동작 o
            setTimeOut(this.logThis.bind(this), 1000)
        }
}

obj.logThisLater1()
obj.logThisLater2()
화살표 함수의 예외사항
  • 화살표 함수는 실행 컨텍스트 생성 시, this를 바인딩하는 과정이 제외
  • 이 함수 내부에서 this가 아예 없으며, 접근코자 하면 스코프체인상 가장 가까운 this에 접근하게 됨
  • this우회, apply, call, bind보다 편리한 방법
var obj = {
     outer: function () {
         console.log(this)
          var innerFunc = () => {
                console.log(this)
           }
           innerFunc()
       }
}
obj.outer()
별도의 인자로 this를 받는 경우 (콜백 함수 내에서의 this)
  • 콜백 함수를 인자로 받는 메서드 중 일부는 추가로 this로 지정할 객체를 인자로 지정 할 수 있음
  • 배열과 관련된 메서드에 많이 존재하며, set, map 등의 메서드에도 일부 존재함
// forEach 예시

var report = {
    sum : 0,
    count : 0,
    add : function () {
        var args = Array.prototpye.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)

 

반응형