본문 바로가기

자바스크립트의 정석 🟡

[자바스크립트] this 정리 ✨

참고자료: 인프런 코어자바스크립트(클래스 편)

참고자료 2

참고자료3

 

 

⭐️ this

- 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수

- this를 통해 자신이 속한 객체 또는 자신이 생성할 인스턴스의 프로퍼티/메서드를 참조

- 암묵적으로 생성

- 코드 어디에서든 참조 가능

- 객체의 프로퍼티나 메서드를 참조하기 위한 자기 참조 변수이므로 일반적으로 객체의 메서드 또는 생성자 함수 내부에서만 의미가 있음

- 함수를 호출하면 인자와 this가 암묵적으로 함수 내부에 전달

- 인자를 지역 변수처럼 사용할 수 있는 것처럼, this도 지역변수처럼 사용 가능

- this가 가리키는 값, 즉, this 바인딩은 함수 호출 방식에 의해 동적으로 결정

- 크게 전역에서 사용할 때와 함수 안에서 사용할 때로 나눔

 

 

✨ 참고로, 바인딩이란 ❓

- 식별자와 값을 연결하는 과정

- 변수 선언은 변수 이름과 확보된 메모리 공간의 주소를 바인딩

- this 바인딩은 this와 this가 가리킬 객체를 바인딩

 

 

 

 

 

 

 

this는 저번 강의에 이어 이부분에 해당 !

 


 

 

✅  CASE 1 :  전역공간에서 호출

📚 전역공간에서 호출시 : window(자바스크립트 런타임) / global (Node.js)

- 브라우저라는 자바스크립트 런타임의 경우 this는 항상 windows라는 전역객체를 참조

- 전역 객체란 ❓ 전역 범위에 항상 존재하는 객체 ( node.js에서 전역객체는 global)

 

a= 'a';
//"a"

this.a
//"a"

window.a
//"a"

 

// in browser console
this === window // true

// in Terminal
node
this === global // true

 

 

💻 코드 )

function myFn(){
return this;
}

📌 이 함수를 호출하는 순간의 주체가 전역공간이기 때문에, 함수 안에서도 전역객체가 가능하다

 

 

 

 

 


 

 

 

 

✅ CASE 2. 함수 내부

📚 this를 함수 내부에서 사용한 경우 : window/global 객체 출력

- 함수는 전역에 선언된 일반 함수와 객체 안에 메소드로 크게 구분

- 객체 안에 선언된 함수를 전역에 선언된 함수와 구분하기 위해 메소드라고 함

- 그런데 전역에 선언된 일반 함수도 결국 window 전역 객체의 메소드. 즉, 모든 함수는 객체 내부에 있음

- 이때 this는 현재 함수를 실행하고 있는 그 객체를 참조

- 정리하면, 함수 내부에서 this의 값은 함수를 호출하는 방법에 의해 바뀜

- 엄격모드 여부에 따라 참조 값이 달라짐

- 엄격모드에서 일반 함수 내부의 this는 undefined가 바인딩 됨

 

 

 

 

 

 

💻 코드 1 )

var d = {
  e : function() {
    function f() {
      console.log(this);
    }
    f(); // 전역객체
  }
}

📌 f 메소드 안에 this를 출력할 때, 전역객체가 나옴.

📌 이유는 뒤에서 말하겠지만, f 메소드 앞에 아무런 지정값이 없어서 바로 전역객체가 출력

 

 

 

 

💻 코드 2)

function a() {
  function b() {
    console.log(this);
  }
  c(); // 전역객체 호출
}
b(); // 전역객체 호출

📌 암기할 수밖에 없는 부분.. c 함수를 호출했을 때, c함수의 주체는 b 함수이지만 전역객체를 호출한다(this의 값을 따로 정해놓지 않음)

📌 b 함수도 메서드 앞에 this를 참조할 함수가 없어 전역객체로 호출

 

 

 

 

💻 코드 3 )

var value = 1;

var obj = {
  value: 100,
  foo: function() {
    console.log("foo's this: ",  this);  // obj
    console.log("foo's this.value: ",  this.value); // 100
    function bar() {
      console.log("bar's this: ",  this); // window
      console.log("bar's this.value: ", this.value); // 1
    }
    bar();
  }
};

obj.foo();

📌 foo 메소드 함수에서는 바로 앞 주체가 obj라는 함수. 그렇기 때문에 foo의 this는 obj이고, this.value의 값은 obj의 value값이 100이 출력

📌 bar 메소드 함수는 이중함수로써 bar 함수 앞 주체가 foo함수. 하지만, foo 함수에서 명확히 어떤 주체할 만한 값이 없어서 this의 값도 정해놓은 것이 없기 때문에 전역객체가 출력. this는 window라는 전역객체, this.value는 전역변수인 1이 출력

✅ 뒤에서 전역객체와 전역변수가 다르게 

 

 

 

💻 코드 4 )

var value = 1;

var obj = {
  value: 100,
  foo: function() {
    setTimeout(function() {
      console.log("callback's this: ",  this);  // window
      console.log("callback's this.value: ",  this.value); // 1
    }, 100);
  }
};

obj.foo();

📌 this가 전역객체를 가져와야 되지만, 전역변수 a를 호출.

📌 전역객체는 전역객체고, 전역변수는 전역변수로 생각하면 맞지만 별개의 개념으로 너무 생각하지 말자!

 

 

 

📚 예외적으로 )  that을 사용하는 경우 ((참고만))

var value = 1;

var obj = {
  value: 100,
  foo: function() {
    var that = this;  // Workaround : this === obj

    console.log("foo's this: ",  this);  // obj
    console.log("foo's this.value: ",  this.value); // 100
    function bar() {
      console.log("bar's this: ",  this); // window
      console.log("bar's this.value: ", this.value); // 1

      console.log("bar's that: ",  that); // obj
      console.log("bar's that.value: ", that.value); // 100
    }
    bar();
  }
};

obj.foo();

 

 

 

 


 

✅ CASE 3. 메서드

 

📚 참고, 메서드에서 this의 영역

 

 

📚 객체의 메소드 함수에서 this

 

💻 코드 1)

const fn = {
  title: 'Hello World',
  showTitle(){
    console.log(this.title);
  }
};

fn.showTitle();
//Hello World

📌 showTitle 메서드 앞에 fn이 this로 정해져 showTitle() 메서드를 호출했을 때, 콘솔에 this가 fn으로 결정되고 fn의 title를 콘솔에 찍혀서 "Hello World"가 출력

 

 

 

 

 

💻 코드 2)

var a = {
  b : function(){
    console.log(this);
  }
}

a.b(); //a가 this

📌 메서드에서는 점 앞에 있는 a가 this가 되는 것

📌 b 함수를 a 객체의 메서드로서 호출

✅ 메서드라는 것이 원래는 함수인데, 어떤 객체와 관련된 동작을 하게 되면 그때는 메서드로서 호출

 

 

 

💻 코드 3)

var a = {
  b : {
    c:  function(){
    console.log(this);
    }
  }
}

a.b.c(); //a.b가 this

📌 여기서는 a.b까지가 this를 가리킴

 

 

 

 

 

 

💻 코드 3-1) 

const fn = {
  title: "hello world",
  tags : [1,2,3,4],
  showTag(){
    this.tags.forEach(function(tag){
      console.log(tag);
      console.log(this); //window
    })
  }
}

fn.showTag();

// 1
// window 객체 출력
// 2
// window 객체 출력
// 3
// window 객체 출력
// 4
// window 객체 출력

📌  고차 함수의 콜백 함수 안에서 this 콜백함수가 일반 함수이기 때문에 전역객체를 참조

📌 showTag 함수 안에 tag 함수가 존재함. 여기서 fn.showTag()에서 this가 fn이라 생각할 수 있지만, 고차 함수에서 콜백함수는 그저 일반함수일 뿐.

 

 

 

💻  코드 3-2)  함수에 두번째 인자 넣어서 this 인자 객체 넘겨주기

const fn = {
  title: "hello world",
  tags : [1,2,3,4],
  showTag(){
    this.tags.forEach(function(tag){
      console.log(tag);
      console.log(this); //fn
    }, this); //여기는 일반 함수 바깥, fn 객체를 참조할 수 있음
  }
}

fn.showTag();

// 1
// fn 객체 출력
// 2
// fn 객체 출력
// 3
// fn 객체 출력
// 4
// fn 객체 출력

 

📌 코드 3-1에서 해결하지 못한 고차 함수 안에서 전역객체 참조 제외한 원하는 값을 참조하게끔 만들어줄 때 사용하는 것이 바로 콜백함수 bind (뒤에서 설명을 읽으면 이 코드 내용이 이해될 것임)

📌 두번째 인자로 바로 this를 넣음으로써 바로 바깥에 있는 fn이라는 객체를 this로 넘길 수 있음

 

 

 

💻 코드 3-3) 화살표 함수 사용하기

✨ 화살표 함수란 ❓

- function 키워드로 생성한 일반 함수와 화살표 함수의 가장 큰 차이점은 바로 this. 이를 Lexical this라고 말함

- 화살표 함수 안에서 this는 언제나 상위 스코프의 this를 가리킴

- 일반 함수는 함수를 선언할 때 this에 바인딩할 객체가 정적으로 결정되지 않고, 함수를 호출할 때 함수가 어떻게 호출되는지에 따라 this에 바인딩할 객체를 동적으로 결정함

- 화살표 함수는 함수를 선언할 때 this에 바인딩할 객체가 정적으로 결정

- 화살표 함수의 this 바인딩 객체 결정 방식은 함수의 상위 스코프를 결정하는 방식인 렉시컬 스코프와 유사

- 화살표 함수는 call, apply, bind 메소드를 사용하여 this를 변경할 수 없음

 

const fn = {
  title: "hello world",
  tags : [1,2,3,4],
  showTag(){
    this.tags.forEach((tag)=>{
      console.log(tag);
      console.log(this); //fn
    }); //여기는 일반 함수 바깥, fn 객체를 참조할 수 있음
  }
}

fn.showTag();

// 1
// fn 객체 출력
// 2
// fn 객체 출력
// 3
// fn 객체 출력
// 4
// fn 객체 출력

 

 

 

 


  CASE 4. 콜백함수

 call, apply, bind 

 

 

 

📚 명시적으로 this를 바인드하는 방법

function a(x,y,z){
    console.log(this, x,y,z);
}
var b = {
    bb:'bbb'
};

a.call(b, 1,2,3);
a.apply(b, [1,2,3]);

var c = a.bind(b);
c(1,2,3);

var d = a.bind(b, 1,2);
d(3);

//결과 
{ bb: 'bbb' } 1 2 3
{ bb: 'bbb' } 1 2 3
{ bb: 'bbb' } 1 2 3
{ bb: 'bbb' } 1 2 3

📌 call : 첫 번째 인자로 넘긴 것이 this, 넘기 두 번째부터는 차례대로 함수의 매개변수로 넘김

📌 apply : 첫 번째 인자로 넘긴 것이 this, 배열로 넘긴 각각의 요소들이 함수의 매개변수가 됨

📌 첫번째 bind : this를 a로 넘겨주고, b라는 매개변수를 넘겨준 상태. 여기서 c라는 바인드된 함수에 매개변수 1, 2, 3을 넘겨주면 결과값은 다른 결과값과도 같게 만들 수 있음

📌 두번째 bind : this를 a로 넘겨주고, 매개변수로 1, 2를 넘겨준 상태에서 d라는 바인드 함수를 만듦. 만든 후 그 함수에 3을 넘기면서 다시 호출

 

 

 

 

💻 예시 코드 1-1)

var callback = function() {
  console.log(this);
};
var obj = {
  a: 1,
  b: function(cb){
    cb();
  }
}
obj.b(callback);

📌 obj.b의 함수를 호출했을 때, 매개변수로 callback 함수를 받는데, 이 callback함수는 this를 출력하는 함수

📌 그럼, obj의 메소드는 b를 보면, 함수 cb가 cb를 호출하는데, 여기서의 this는 따로 정해놓지 않았으므로 ( 메소드 앞에 this를 정한 값이 없기 때문) callback 함수에서 this를 출력하면 window(전역객체)라고 결과값이 나올 것이다

📌 즉, 그냥 함수로서 호출하면 무조건 전역객체

 

 

 

💻 예시 코드 1-2)

var callback = function() {
  console.log(this);
};
var obj = {
  a: 1,
  b: function(cb){
    cb.call(this);
  }
}
obj.b(callback);

📌 만약 이 경우라면, cb.call(this)에서 this를 바로 넘겨주었을 때, this는 b 메서드 앞에 있는 obj 함수가 b 메서드의 주체이기 때문에 이때의 this는 obj로 출력

 

 

 

💻 코드 2)

var callback = function(){
    console.dir(this); //따로 this를 지정한 값이 없어서 전역객체로 나옴
};
var obj = {
    a:1
};
setTimeout(callback, 100);

//this의 값을 지정할려면
setTimeout(callback.bind(this), 100);

📌 setTimeout(callback, 100) : setTimeout 함수에서는 callback 함수에 this가 딱히 정해진 것이 없어 전역객체가 나옴

📌 setTimeout(callback.bind(this), 100) : callback 함수에 this를 원하는 값으로 지정하고 싶다면, bind를 쓰면 됨

 

 

 

💻 코드 3-1)

document.body.innerHTML += '<div id="a"> 클릭하세요 </div';

document.getElementById('a').addEventListener(
    'click',
    function(){
        console.dir(this);
    }
);

📌 이때 this는 html dome element가 나옴. 왜냐하면, this는 이벤트가 발생한 그 타겟 대상 엘리먼트로 하도록 정의가 되어있기 때문에📌 addEventListner처럼 콜백함수의 this를 별도로 지정해놓은 경우도 얼마든지 있음

 

 

 

 

 

💻 코드 3-2) 만약 this에 특정 obj로 바꾸고 싶다면? bind 콜백함수를 사용해라

document.getElementById('a').addEventListener(
    'click',
    function(){
        console.dir(this);
    }.bind(obj)
);

 

 


 

✅  5. new 연산자

- 생성자 함수 방식으로 인스턴스를 생성한 경우

- 생성자 함수 MyFn가 빈 객체를 만들고 이 생성자 함수에서 this가 빈객체를 가리키도록 설정

 

 

💻 코드 1)

function MyFn(){
  this.title = "hello world";
  return this;
}


//new 연산자를 통해 새로운 객체를 얻음
const myfn = new MyFn()

myfn;
//MyFn{title: "hello world"}

 

 

💻 코드 2)

function Person(n, a){
    this.name=n;
    this. age = a;
}
var roy = new Person ('재남', 30); //new
console.log(roy);
console.log(window.name, window.age); //재남 30

 

 

 

 


 

 

 

✨ 결론

- this는 이처럼 어떤 위치에 있느냐, 어디에서 호출되느냐, 어떤 함수에 있느냐에 다라 참조 값이 달라지는 특성이 있음

- 제어권을 가진 함수는 this를 이미 지정해둔 경우도 있음 (예를들면, addEventListner)

-이를 직접 바꾸고 싶다면, 바인딩을 해라 !