this Keyword in Depth

this in the Global Context

globalThisInBrowser

this === global // true

this in Function Calls

function func() {
  console.log(this === global) // true
  ;('use strict')
  console.log(this === undefined) // true
}

func()
// use strict 모드가 아닐경우
function Person(firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}

const person = Person('Sun', 'Lee')
console.log(person) // undefined
console.log(global.firstName) // Sun
console.log(global.lastName) // Lee
// use strict 모드일 경우
;('use strict')

function Person(firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}

const person = Person('Sun', 'Lee')
console.log(person) // undefined
console.log(global.firstName) // TypeError: Cannot set property 'firstName' of undefined
console.log(global.lastName) // TypeError: Cannot set property 'lastName' of undefined
// use strict 모드일 경우 && new 생성자를 사용하였을 경우
;('use strict')

function Person(firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}

const person = new Person('Sun', 'Lee')
console.log(person) // Person {firstName: 'Sun', lastName: 'Lee'}
console.log(global.firstName) // undefined
console.log(global.lastName) // undefined

this in Constructor Calls

function Person(firstName, lastName) {
  console.log(this) // Person {}
  this.firstName = firstName
  console.log(this) // Person {firstName: 'Sun'}
  this.lastName = lastName
  console.log(this) // Person {firstName: 'Sun', lastName: 'Lee'}
}

const person = new Person('Sun', 'Lee')

this in Method Calls

const person = {
  firstName: 'Sun',
  sayHi(){
      console.log(`Hi my name is${this.firstName}!`) // Hi, my name is Sun!
  }

위의 코드는 아래와 동일한 코드이다.

function sayHi() {
  console.log(`Hi my name is${this.firstName}!`) // Hi, my name is Sun!
}

const person = {
  firstName: 'Sun'
}

person.sayHi = sayHi
person.sayHi()

Specify this using .call() or .apply()

call()

function sayHi() {
  console.log(`Hi my name is ${this.firstName}!`) // Hi my name is Sun!
}

const person = {
  firstName: 'Sun',
  lastName: 'Lee'
}

sayHi.call(person)

apply()

function sayHi() {
  console.log(`Hi my name is ${this.firstName}!`) // Hi my name is Sun!
}

const person = {
  firstName: 'Sun',
  lastName: 'Lee'
}

sayHi.apply(person)

difference

const numbers = [10, 20, 30, 40, 50]

const slice1 = numbers.slice(1, 4)
const slice2 = numbers.slice.call(numbers, 1, 4)
const slice3 = numbers.slice.apply(numbers, [1, 4])

console.log(slice1) // [20, 30, 40]
console.log(slice2) // [20, 30, 40]
console.log(slice3) // [20, 30, 40]

// call => (c)omma
// apply => (a)rray
function func() {
  console.log(this === global) // browser에서는 global이 아닌 window
}

func.call(null) // true
func.call(undefined) // true

func.apply(null) // true
func.apply(undefined) // true
;('use strict')

function func() {
  console.log(this === global)
}

func.call(null) // false
func.call(undefined) // false

func.apply(null) // false
func.apply(undefined) // false

Hard-Bind a Function's this Value with the .bind() Method

const person = {
  firstName: 'Sun',
  sayHi() {
    console.log(`Hi, my name is ${this.firstName}!`)
  }
}

setTimeout(person.sayHi, 1000) // Hi, my name is undefined!
setTimeout(person.sayHi.bind(person), 1000) // Hi, my name is Sun!

const greet = person.sayHi.bind(person)
greet() // Hi, my name is Sun!

const otherPerson = {
  firstName: 'Young'
}

greet.call(otherPerson) // Hi, my name is Sun! // greet 함수가 person에 영구히 bind 되어 firstName은 여전히 "Sun"이 된다

Capture this with an Arrow Function

const outerThis = this
const func = () => {
  console.log(this === outerThis)
}

func() // true
func.call(null) // true
func.apply(undefined) // true
func.bin({})() // true

new func() // TypeError: func is not a constructor
// 기본 함수 표기법 사용
const counter = {
  count: 0,
  incrementPeriodically() {
    setInterval(function() {
      console.log(++this.count)
    }, 1000)
  }
}

counter.incrementPeriodically()

// NaN
// NaN
// NaN
// NaN
// ...
// 화살표 함수 사용
const counter = {
  count: 0,
  incrementPeriodically() {
    setInterval(() => {
      console.log(++this.count)
    }, 1000)
  }
}

counter.incrementPeriodically()

// 1
// 2
// 3
// 4
// ...

this in Class Bodies

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }

  sayHi() {
    console.log(`Hi, my name is ${this.firstName}!`)
  }
}

const person = new Person('Sun', 'Lee')
person.sayHi() // Hi, my name is Sun!

const greet = person.sayHi
greet() // Cannot read property 'firstName' of undefined

const greet2 = person.sayHi.bind(person)
greet2() // Hi, my name is Sun!