Class와 Instance 그리고 this

김민석·2021년 2월 25일
0

Immersive

목록 보기
8/30

들어가기 전에..

Singleton 패턴

let counter1 = {
  value: 0,
  increase: function() {
    this.value++ // 메소드 호출을 할 경우, this는 counter1을 가리킵니다
  },
  decrease: function() {
    this.value--
  },
  getValue: function() {
    return this.value
  }
}

counter1.increase()
counter1.increase()
counter1.increase()
counter1.decrease()
counter1.getValue() // 2
  • singleton 패턴은 단 하나의 객체만 생성 가능하다.

클로저를 이용해 매번 새로운 객체 생성

function makeCounter() {
  return {
    value: 0,
    increase: function() {
      this.value++ // 메소드 호출을 할 경우, this는 makeCounter 함수가 리턴하는 익명의 객체입니다
    },
    decrease: function() {
      this.value--
    },
    getValue: function() {
      return this.value;
    }
  }
}

let counter1 = makeCounter()
counter1.increase()
counter1.getValue() // 1

let counter2 = makeCounter()
counter2.decrease()
counter2.decrease()
counter2.getValue() // -2

<Class와 Instance>

😄 객체 지향 프로그래밍이란?

하나의 모델이 되는 청사진(Class)을 만들고, 그 청사진을 바탕으로 한 객체(Instance)를 만드는 프로그래밍 패턴.


Class를 정의하는 방법

class ME {
  	// 속성 정의
	constructor(height, weight, name, job){
      this.height = height;
      this.weight = weight;
      this.name = name;
      this.job = job;
	}
  	// method 정의
  	pig() {
    	if(this.weight > 80){return 'pig'}
    }
}  

prototype을 통한 메소드 정의시에...

실습 중에 발견한 것은 class를 통해 생성한 메소드는 함수의 이름이 속성의 이름으로 바로 들어간다. 예를 들어서, 위에서 생성한 pig method는

요로코롬 pig 속성에 담긴 함수의 이름과 속성 이름이 같이 생성된다.
그러나 위의 사진에서 보는 short 속성은

ME.prototype.short = function(){
  if(this.height>170){
    return 'short'
  } 
}

에 의해 생성된 것인데, 익명 함수가 들어와 있는 것을 알 수 있다.

ME.prototype.short = function short(){
  if(this.height>170){
    return 'short'
  } 
}

이런 식으로 기명 함수를 넣어주니,

똑같이 만들어졌다. (사실 중요한 건지는 모르겠다)

Intance 생성

let me = new ME(176,83,'ms','student');
console.log(me);
// ME {height: 176, weight: 83, name: "ms", job: "student"}

me.weight; // 83
me.pig();  // 'pig'

참고:

prototype vs constructor vs this


  • prototype : 모델의 청사진을 만들 때 쓰는 원형 객체

  • constructor : 인스턴스가 초기화 될 때 실행하는 생성자 함수 (new 키워드에 의해 호출 됨)

  • this : 함수가 실행될 때, 해당 scope마다 생성되는 고유한 실행 context (new 키워드로 인스턴스를 생성하면, 해당 인스턴스가 this의 값이 된다.)

😡 this

this는 함수 실행시 호출 방법에 의해 결정되는 객체다.
따라서 this는 실행되는 맥락에 따라 달라진다.

함수 실행 시 호출 방법에 따라 결정되므로, 함수 실행 방법들이 어떤 것이 있는지에 대해 먼저 정리하고 시작해야겠다

함수 실행 방법

JS에서 함수를 실행하는 방법은 총 4가지가 있다.

  • Function 호출

    foo()

  • Method 호출

    obj.foo()

  • new 키워드를 이용한 생성자 호출

    new Foo()

  • .call 또는 .apply 호출

    foo.call()
    foo.apply()


각 함수 실행 방법에 따른 this 바인딩 패턴

  • Function 호출

    Function 호출 시 this를 사용할 이유가 없다.... 고 한다.

    하지만 궁금해서 찾아봤다.

    Node.js에서

    • 함수 표현식/선언식 내부의 this는 global 객체를,
    • 전역 환경에서의 this는 module.exports를 가리킨다. (화살표 함수의 경우도 마찬가지)
  • Method 호출

    this : 부모 객체 (실행 시점에 dot 왼쪽에 있는 객체)

  • new 키워드를 이용한 생성자 호출

    this : 새롭게 생성된 인스턴스 객체

  • .call 또는 .apply 호출

    this : 첫번째 인자로 전달된 객체

    this 값을 특정할 때 사용하며, 특히 apply의 경우 배열의 엘리먼트를 풀어서 인자로 넘기고자 할 때 유용하다.


.call.apply

"A for array and C for comma"

theFunction.apply(valueForThis, arrayOfArgs)

theFunction.call(valueForThis, arg1, arg2, ...)

.apply

// null을 this로 지정합니다. Math는 생성자가 아니므로 this를 지정할 필요가 없습니다.
Math.max.apply(null, [5,4,1,6,2]) // 6 

// spread operator의 도입으로 굳이 apply를 이용할 필요가 없어졌습니다.
Math.max(...[5,4,1,6,2]) // 6

.call

// '피,땀,눈물'을 this로 지정합니다.
''.split.call('피,땀,눈물', ',')

// 다음과 정확히 동일한 결과를 리턴합니다.
'피,땀,눈물'.split(',')

실제로 적용할 때 아래와 같은 방식으로 사용할 수 있을 것으로 보인다.

<!-- test.html-->
<div class = 'frst'>a</div>
<div class = 'rk'>b</div>
<div class = 'sk'>c</div>
<div>d</div>
<div>e</div>
<script src = "test.js"></script>

연결된 test.js는 아래와 같다.

//test.js
let allDivs = document.querySelectorAll('div'); // NodeList라는 유사 배열입니다.

// allDivs를 this로 지정합니다.
// call -> apply로 바꾸려면 function을 []로 감싸면 된다. 'AaCc'
let p = Array.prototype.map.call(allDivs, function(el) {
  return el.className
})
console.log(allDivs)
console.log(p)
  • allDivsnodelist로 유사배열이다.
  • test.js는 call을 이용하여 map함수에 allDivs를 this로 지정해주었고, map함수가 인자로 함수를 받기 때문에 function(el) {return el.className}) 부분을 인자로 넘겨준 것이다.
  • 결과적으로 유사 배열에서 map method를 사용할 수 있게 되었다.

bind

theFunction.bind(this값, 인자1, 인자2, ...)

바인드는 .call, .apply처럼 this 및 인자를 전달한다. 하지만 함수를 바로 실행하는 것이 아니라 함수 자체를🤗 리턴한다.

let target = document.querySelector("#target");
let users = [
  {
    name: "김코딩",
    skill: "JavaScript 코딩"
  },
  {
    name: "박해커",
    skill: "가창력"
  },
  {
    name: "최초보",
    skill: "도전정신"
  }
];

users.forEach((user) => {
  let btn = document.createElement("button");
  btn.textContent = user.name;
  btn.onclick = handleClick.bind(null,user);
  // btn.onclick = ()=>{handleClick(user)}
  // 익명 함수로도 가능하다.
  target.appendChild(btn);
});

function handleClick(user) {
  alert(`${user.name}의 스킬은 ${user.skill}입니다`);
}

handleClick은 인자를 필요로 한다.
따라서 foreach 함수를 통해 users 배열elements들이 하나씩 들어오므로 그 element를 뜻하는 user를 binding하여 넘겨주었다.
(this는 넘겨줘도 상관없다.)


참고

setTimeout 함수는 명시적으로 항상 전역'' 객체를 this 바인딩한다.
('' 브라우저에서는 window가 맞는 것으로 보이지만 Node.js에서는 global이 아니다. 뭔지는 모르겠다...)

class Rectangle {
    constructor(width, height) {
      this.width = width
      this.height = height
    }

    getArea() {
      return this.width * this.height
    }

    printArea() {
      console.log('사각형의 넓이는 ' + this.getArea() + ' 입니다')
    }
  
    printSync() {
      // 즉시 사각형의 넓이를 콘솔에 표시합니다
      this.printArea()
    }

    printAsync() {
      // 1초 후 사각형의 넓이를 콘솔에 표시합니다
      setTimeout(this.printArea, 2000)
    }


    printAsync_this() {
        // 1초 후 사각형의 넓이를 콘솔에 표시합니다
        setTimeout(this.printArea.bind(this), 2000)
    }

    printAsync_arrow() {
        // 1초 후 사각형의 넓이를 콘솔에 표시합니다
        setTimeout(() => {
            console.log(this)
            this.printArea()
        }, 2000)
      }
  }

  let box = new Rectangle(40, 20)
  box.printSync() // '사각형의 넓이는 800 입니다'
  box.printAsync_arrow() // '사각형의 넓이는 800 입니다'
  box.printAsync_this()  // '사각형의 넓이는 800 입니다'
  box.printAsync() // 에러 발생!

위의 코드에서

  • this를 명시적으로 바인딩 한 것과
  • arrow function을 이용한 것

두 가지는 잘 되지만

  • setTimeout 함수 안에, 바로 this를 사용한 것은 잘 안되는 것을 알 수 있었다. (this가 전역 객체를 바라보기 때문.)

되는 것 두 가지를 살펴보자면,

this.printArea.bind(this)는 instance를 가리키는 this를 printArea에 binding하여 실행하는 것으로 보인다. (아직 정확하게 이해하지는 못했다...)

printAsync_arrow()의 경우는 arrow function을 전달해주었다. 이때 arrow_function의 this는 outer scope의 this를 가리킨다고 한다. 이를 Lexical this라고 한다.

0개의 댓글