[JavaScript] this

정진우·2024년 6월 3일
0

JavaScript

목록 보기
18/20
post-thumbnail

this

자바스크립트에서 this는 함수가 호출되는 방법에 따라 달라지는 특별한 키워드입니다. this는 함수가 실행될 때 해당 함수가 소속된 객체를 참조합니다. 이 참조는 함수 호출 문맥(컨텍스트)에 따라 달라집니다. 즉, 함수를 어떻게 호출하느냐에 따라서 this가 가리키는 대상이 달라진다는 뜻입니다.


함수와 메서드

정의 위치

  • 메서드 : 객체 안에 정의됩니다. 객체 내에 정의된 함수를 의미합니다.
  • 함수 : 객체 외부에 독립적으로 정의됩니다. 별도의 함수 선언을 통해 만들어집니다.

호출 방식

  • 메서드 : 객체를 통해 호출됩니다. 객체.메서드명() 형식으로 호출합니다.
  • 함수 : 함수 이름을 직접 호출하거나, 변수에 할당 후 호출할 수 있습니다. 함수명() 또는 변수명() 형식으로 호출합니다.

this 키워드

  • 메서드 : this키워드는 메서드 내에서 호출된 객체를 참조합니다. 메서드를 통해 객체의 속성과 메서드에 접근하고 변경할 수 있습니다.
  • 함수
    - this키워드는 일반적으로 함수 내에서 특별한 의미를 가지지 않습니다.
    - 하지만 strict mode에서 실행되는 함수 내에서 thisundefined를 참조합니다.
    - strict mode가 실행되지 않는 일반적인 함수에서 this는 전역 객체를 참조합니다. 브라우저 환경에서 전역 객체는 window 객체입니다.

명시적 바인딩, 암시적 바인딩, 동적 바인딩, new 바인딩

JavaScript에서 this의 값을 설정하는 네 가지 방법입니다.

동적 바인딩

동적 바인딩은 함수가 호출될 때 this의 값이 결정되는 방식입니다. this의 값은 함수가 어떻게 호출되는지에 따라 달라집니다.

전역 문맥(Global Context)

전역에서의 this는 전역 객체를 참조합니다.

console.log(this); // 브라우저에서는 window 객체, Node.js에서는 global 객체
  • window 객체는 웹 브라우저에서 JavaScript의 전역 객체로 사용되며, 웹 페이지에 관한 정보를 담고 있습니다. 또한, JavaScript의 내장 함수, 브라우저 제공 API, 그리고 사용자가 정의한 변수와 함수 등이 window 객체의 프로퍼티 혹은 메서드로 포함되어 있습니다.

함수 호출

일반 함수 호출에서는 전역 객체를 참조합니다.

function showThis() {
  console.log(this);
}

showThis(); // 브라우저에서는 window

엄격 모드에서는 undefined를 참조합니다.

"use strict";

function showThis() {
  console.log(this);
}

showThis(); // 엄격 모드에서는 undefined

메서드 호출

객체의 메서드로 호출될 때 this는 그 메서드를 소유한 객체를 참조합니다.

const person = {
  name: 'jinwoo',
  greet: function() {
    console.log(this.name);
  }
};

person.greet(); // 'jinwoo'

생성자 함수 호출

생성자 함수 내의 this는 새로 생성된 객체를 참조합니다.

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

const bob = new Person('Bob');
console.log(bob.name); // 'Bob'

명시적 바인딩

명시적 바인딩은 함수의 this값을 명시적으로 설정하는 방법입니다. call, apply, bind메서드를 사용합니다.

call

call메서드는 함수를 호출하면서 첫 번째 인자로 전달된 값을 this로 설정합니다. 추가 인자는 함수의 매개변수로 전달됩니다.

function greet(greeting) {
  console.log(`${greeting}, my name is ${this.name}`);
}

const user = { name: 'jinwoo' };
greet.call(user, 'Hello'); // Hello, my name is jinwoo
  • greet.call(user, 'Hello')를 통해 greet()함수를 호출하고 있습니다. 여기서 call메서드의 첫 번째 전달인자로 user객체를 전달하면, greet()함수 내의 thisuser객체를 가리킵니다. 그래서 this.namejinwoo가 됩니다.
  • call메서드의 두 번째 인자로 "Hello"문자열을 넘기면, 이 값은 greet()함수의 첫 번째 인자 greeting으로 전달됩니다.
  • call메서드 없이 함수를 호출하면, this는 전역 객체를 참조합니다. call메서드를 사용하지 않고 greet()함수를 호출하면, this.name은 전역 객체의 name속성을 참조하는데, 전역에 name이 선언되어 있지 않다면, this.nameundefined를 출력합니다.

apply

apply 메서드는 call과 비슷하지만, 함수의 매개변수를 배열로 전달받는 점이 다릅니다.

function greet(greeting, punctuation) {
  console.log(`${greeting}, my name is ${this.name}${punctuation}`);
}

const user = { name: 'jinwoo' };
greet.apply(user, ['Hello', '!']); // Hello, my name is jinwoo!
  • greet.apply(user, ['Hello', '!'])를 통해 greet()를 호출하고 있습니다. apply메서드의 첫 번째 인자로 user 객체를 전달하면, greet()함수 내의 thisuser객체를 가리킵니다. 그래서 this.namejinwoo가 됩니다.
  • apply메서드의 두 번째 인자로 Hello!로 구성된 배열을 전달하면, greet()함수의 첫 번째와 두 번째 매개변수로 전달됩니다.

callapply 메서드의 주요 목적

callapply 메서드의 주요 목적은 함수를 즉시 호출하면서 this의 값을 특정 객체로 설정하는 것입니다. 이를 통해 함수 내부에서 this가 참조하는 값을 직접 제어할 수 있습니다.

  • 객체의 메서드를 다른 컨텍스트에서 호출하고 싶을 때 call이나 apply를 사용할 수 있습니다.
let person1 = {
  name: "jinwoo",
  greet: function () {
    console.log(`Hello, my name is ${this.name}`);
  },
};

let person2 = {
  name: "McDonald",
};

// person1의 greet 메서드를 person2의 컨텍스트에서 호출
person1.greet.call(person2); // "Hello, my name is McDonald"
  • call메서드를 사용해서 person1greet메서드를 person2의 컨텍스트에서 호출했습니다. 그래서 this.nameperson2name를 참조하게 되어 Hello, my name is McDonald를 출력합니다.

bind

bind메서드는 새로운 함수를 반환하며, 이 새로운 함수는 항상 주어진 this값을 사용합니다. 원래 함수와 같은 인수를 받을 수 있습니다.

function greet(greeting) {
  console.log(`${greeting}, my name is ${this.name}`);
}

const user = { name: 'jinwoo' };
const boundGreet = greet.bind(user);
boundGreet('Hello'); // Hello, my name is jinwoo
  • boundGreetgreet()함수를 바인딩한 새로운 함수 입니다.
  • greet.bind(user)를 통해 greet()함수를 호출하고 있는데, 여기서 bind메서드는 새로운 함수를 반환합니다.
  • 이 함수는 원래의 greet()함수와 같지만, thisuser객체를 가리키도록 고정됩니다. 그래서 boundGreet('Hello')를 호출할 때, this.namejinwoo가 됩니다.

bind메서드의 주요 목적

bind메서드의 주요 목적은 함수의 this값을 고정하는 것입니다.

  • 자바스크립트에서 this는 기본적으로 함수가 호출되는 방식에 따라 그 값이 달라집니다. 이것을 동적 바인딩이라고 합니다.
  • 함수가 어떻게 호출되었는지와 상관없이 this값이 특정 객체를 가리키도록 고정하고 싶다면, bind 메서드를 사용하면 됩니다.
  • bind메서드는 원래 함수와 동일한 함수를 반환하지만, this값이 특정 객체를 가리키도록 고정된 상태입니다. 이렇게 반환된 함수를 호출하면, this는 항상 bind메서드에 전달된 객체를 가리킵니다.
  • callapply메서드는 bind와 비슷한 방식으로 this를 명시적으로 지정할 수 있지만, this를 고정하지는 않습니다. callapply는 함수를 즉시 호출하면서 this값을 설정합니다. 그런데 bind는 새로운 함수를 반환하며, 이 함수는 원래 함수와 동일하지만 this값이 특정 객체를 가리키도록 고정되어 있습니다.

암시적 바인딩

암시적 바인딩은 자바스크립트에서 함수가 호출될 때 this가 암시적으로 설정되는 방식입니다. 객체의 메서드를 호출할 때 주로 발생하며, 메서드 호출 시점에 this가 결정됩니다. 객체.메서드명()형식으로 호출하는 형태를 의미합니다.

객체의 메서드로 호출될 때

const person = {
  name: 'jinwoo',
  greet: function() {
    console.log(this.name);
  }
};

person.greet(); // 'jinwoo'
  • person.greet()를 호출할 때, greet()메서드 내의 this는 그 메서드를 소유한 객체를 참조합니다.

메서드를 변수에 할당한 후 호출

const person = {
  name: 'Alice',
  greet: function() {
    console.log(this.name);
  }
};

const greet = person.greet;
greet();
  • 객체의 메서드를 변수에 할당한 후 호출하면 암시적 바인딩이 깨지고 this는 전역 객체를 참조합니다.
  • thisperson객체를 가리키지 않고 전역 객체를 가리키게 됩니다.

메서드를 다른 객체에 할당한 후 호출

const person1 = {
  name: 'Alice',
  greet: function() {
    console.log(this.name);
  }
};

const person2 = {
  name: 'Bob'
};

person2.greet = person1.greet;
person2.greet(); // 'Bob'
  • 객체의 메서드를 다른 객체에 할당한 후 호출하면, this는 새로운 객체를 참조합니다.
  • person.greet메서드를 person2.greet에 할당하고 호출하면 thisperson2 객체를 가리키게 됩니다.
const obj1 = {
  name: 'obj1',
  sayName() {
    console.log(this.name);
  },
};

const obj2 = {
  name: 'obj2',
  sayName: obj1.sayName,
}

obj2.sayName(); // obj2
  • obj2.sayName()에서 obj2sayName프로퍼티는 obj1sayName()메서드를 참조하고 있습니다.
  • obj2.sayName()이 호출되는 시점에서 this는 메서드를 호출한 객체 obj2를 참조하게 됩니다. 그래서 this.nameobj2name값을 참조하게 됩니다.

new 바인딩

new 바인딩 예제

new 키워드는 생성자 함수를 호출할 때, 생성자 함수 내부의 this는 새로 생성되는 객체를 가리킵니다.

function Person(name, age) {
  // this는 새로 생성된 빈 객체를 참조합니다.
  this.name = name;
  this.age = age;

  // 명시적인 반환이 없으므로 새로 생성된 객체가 반환됩니다.
}

// new 키워드를 사용하여 Person 객체를 생성합니다.
const person1 = new Person('jinwoo', 30);
console.log(person1); // Person { name: 'jinwoo', age: 30 }
console.log(person1.name); // jinwoo
console.log(person1.age); // 30
  • person1 변수는 new Person('Alice', 30)을 통해 생성된 새 Person 객체를 참조합니다. Person 생성자 함수내의 this는 새롭게 생성된 Person 객체를 가리킵니다. 그래서 person1this가 참조하는 Person을 가리키게 됩니다.
  • 일반적인 함수는 return문이 없을 경우 undefined를 반환합니다. 그런데 생성자 함수를 new키워드를 사용하여 생성자 함수를 호출하면, 생성자 함수가 명시적으로 객체를 반환하지 않을 때 this로 바인딩된 새로 생성된 객체가 반환됩니다.

프로토타입과 new 바인딩 예제

function Car(model) {
  this.model = model;
}

Car.prototype.start = function () {
  console.log(this.model + "가 출발합니다!");
};

const car1 = new Car("기아 K5");
const car2 = new Car("현대 싼타페");

car1.start(); // "기아 K5가 출발합니다!" 출력
car2.start(); // "현대 싼타페가 출발합니다!" 출력

// 생성된 객체는 생성자 함수의 프로토타입과 연결되어 프로토타입 체인을 통해 메서드와 프로퍼티를 상속받습니다.
  • 자바스크립트에서 prototype은 기본적으로 공유 공간으로 생각하면 됩니다. Car.prototype.startCar라는 공유 공간start라는 기능을 추가한 것 입니다. Car로 만든 모든 객체들은 start기능을 공유하게 됩니다. 즉 모든 Car 객체들은 start를 사용해 시동을 걸 수 있습니다.
  • car1car2객체를 만들었을 때, 각각의 객체에 start메서드를 따로 추가하지 않아도 start메서드를 호출할 수 있습니다. 이렇게 Car.prototype.start 메서드는 Car로 생성된 모든 객체가 공유하게 되는데, 이것이 prototype의 역할입니다.

화살표 함수

  • 일반 함수에서 this는 함수가 호출되는 방식에 따라 다르게 결정되지만, 화살표 함수에서 this는 항상 화살표 함수를 감싸는 외부 스코프에서 상속됩니다. 이것을 정적 바인딩(lexical binding) 이라고 합니다.
const obj = {
  name: 'Alice',
  regularFunction: function() {
    console.log(this.name); // 'Alice'
  },
  arrowFunction: () => {
    console.log(this.name); // undefined (또는 브라우저 환경에서 'window.name'의 값)
  }
};

obj.regularFunction(); // 'Alice'
obj.arrowFunction(); // undefined
  • regularFunction은 호출된 객체 objthis로 참조하지만, arrowFunctionobj가 아닌 상위 스코프(글로벌 스코프)의 this를 참조합니다.
  • 화살표 함수에서는 this가 함수를 선언한 시점의 this를 바인딩하므로, obj 내부에서 선언된 화살표 함수에서 this를 사용하더라도 obj를 가리키지 않습니다. 따라서 this.nameAlice가 아니라 undefined가 됩니다.
  • 객체의 메서드에서 this를 사용해 객체 자신의 속성이나 다른 메서드에 접근할 때는 일반 함수를 사용하는 것이 좋습니다. 일반 함수에서 this는 함수를 호출한 객체를 가리킵니다. 그래서 메서드 내에서 this를 통해 객체 자신의 속성이나 다른 메서드에 접근할 수 있습니다.

일반 함수와 화살표 함수 비교

// 일반 함수에서 this
const obj1 = {
  name: "Alice",
  sayHello: function() {
    setTimeout(function() {
      console.log(`Hello, ${this.name}`);
    }, 1000);
  }
};

obj1.sayHello(); // "Hello, undefined"

// 화살표 함수에서 this
const obj2 = {
  name: "Alice",
  sayHello: function() {
    setTimeout(() => {
      console.log(`Hello, ${this.name}`);
    }, 1000);
  }
};

obj2.sayHello(); // "Hello, Alice"
  • 일반 함수에서, setTimeout에서 사용된 콜백 함수는 일반 함수입니다. 일반 함수가 단독으로 호출되면 this는 전역 객체를 가리킵니다.
  • 화살표 함수에서는 setTimeout 안의 함수가 화살표 함수이므로, thissayHello 함수의 this를 가리키게 됩니다. sayHello 함수는 obj2 객체의 메서드로 호출되었으므로, thisobj2를 가리키게 됩니다. 따라서 this.name'Alice'가 됩니다.
  • 화살표 함수는 특별한 특성을 가지고 있는데, 자신을 포함하는 외부 스코프의 this값을 바인딩한다는 것입니다. 이것을 정적 바인딩(lexical binding) 이라고 합니다. sayHello 함수 내부의 화살표 함수에서 thissayHello가 호출되는 시점의 this를 가리키게 됩니다. 이 결과, this.name'Alice'를 출력하게 됩니다.

이벤트 핸들러

let button = document.querySelector("button");
button.name = "MyButton";

button.addEventListener("click", () => {
  console.log(this.name + " is clicked!");
});
  • 화살표 함수 내부에서 this는 버튼 요소를 가리킵니다. 클릭 이벤트가 발생하면 MyButton is clicked!라는 메지가 출력됩니다.

참고

profile
내가 바뀌지 않으면 아무것도 바뀌지 않는다 🔥🔥🔥

0개의 댓글