IT world

[JavaScript] 24.02.05 JavaScript에 대해서(2) 본문

모두의 연구소(오름캠프)/AI 모델 활용 백엔드 개발 과정

[JavaScript] 24.02.05 JavaScript에 대해서(2)

엄킹 2024. 2. 5. 12:50

this

this 란? 객체를 가리키는 참조 변수로, 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수이다. 

this는 함수가 만들어질 때가 아닌 "실행"될때 그 값이 결정된다.

this가 가리키는 대상은 어떻게 this가 호출되는지에 따라 다르다. 아래 상황별로 this의 의미를 나눌 수 있다. 

  • 전역 공간에서의 this 
  • 메서드로서 호출할 때 내부에서의 this 
  • 함수로서 호출할 때 내부에서의 this
  • 콜백 함수 호출 시 내부에서의 this
  • 생성자 함수 내부에서의 this

 

this를 전역에서 사용한 경우

  • 브라우저라는 자바스크립트 런타임의 경우에 this는 항상 window라는 전역 객체를 참조한다.
  • 전역 객체란 전역 범위에 항상 존재하는 객체를 의미한다.
  • 브라우저라는 자바스크립트 런타임에서 모든 변수, 함수는 window라는 객체의 프로퍼티와 메소드이다.
  • 어떤 객체의 메소드가 아닌 단독 호출되는 함수의 this는 전역공간을 참조하게 된다.

아래 코드를 보면 함수 a를 호출했고 this를 출력했을 때 window 객체를 출력하는 것을 확인할 수 있다. → 전역공간

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

[실행 결과]

 

this를 메소드 호출로 사용한 경우

  • this는 현재 함수를 실행하고 있는 그 객체를 참조한다.

[실행 결과]

 

 

다음 예시는 각각의 this가 어떤 것을 출력하는 지 확인하는 예시이다.

function sayName(){
  console.log(this.name);
}

var name = 'Hero'; 
// 전역으로 선언한 name 변수의 앞에는 window 가 생략되어 있습니다. 
// 때문에 window.name === "Hero" 가 성립합니다.
let peter = {
  name : 'Peter Parker',
  sayName : sayName
}

let bruce = {
  name : 'Bruce Wayne',
  sayName : sayName
}

sayName(); // window
peter.sayName(); // peter
bruce.sayName(); // bruce

 

this의 특징

this는 함수가 만들어질 때가 아닌 '실행'될 때 그 값이 결정된다.

function sayName(){
  console.log(this.name);
}
var name = 'Hero';

let peter = {
  name : 'Peter Parker',
  sayName : sayName
};

let bruce = {
  name : 'Bruce Wayne',
  sayName : peter.sayName
};

bruce.sayName();

실행 결과를 확인하면 bruce.sayName()의 호출 값은 Bruce Wayne 출력. 즉 this 실행될 때의 객체를 가리키는 것을 확인할 수 있다. bruce의 sayName이 peter.sayName으로 되어있어서 다르게 출력될 것 같지만 결국 실행하는 bruce라는 객체의 속성값을 출력하는 것을 알 수 있다.


객체지향 프로그래밍

객체지향은 프로그래밍 방법론중 하나로 객체들을 만들어서 서로 소통하도록 하는 방법. 객체지향의 객체는 우리가 표현하고자 하는 구체적인 사물을 추상적으로 표현한 것이라고 볼 수 있다.

 

추상화란 필요한 최소한의 정보로 대상을 표현하는 것을 의미한다. 복잡하고 불필요한 세부 사항을 숨겨 코드를 더 간단하고 읽기 쉽고 유지 관리하기 쉽게 만드는 프로세스이다. 추상화를 달성하는 방법은 관련 작업을 그룹화하고 재사용 가능한 코드 블록을 제공할 수 있는 함수를 사용하는 것이다. 즉 특정 기능을 하는 그룹의 공통된 기능(프로퍼티, 메서드)을 정의 하는 작업을 말한다.

 

생성자(constructor)

생성자란 객체를 만드는 역할을 하는 함수로, 객체를 만들 때 new 연산자와 함께 사용하는 함수이다.

우리는 이미 사용해본 생성자가 있다. 해당 코드처럼 이러한 생성자를 내장 생성자라고 한다.

let myArr = new Array(1,2,3);

 

왜 생성자를 사용할까? 

생성자의 장점은 생성자를 통해 생성된 객체는 같은 프로퍼티와 메서드를 공유할 수 있다는 것이다. 또한 객체에 대한 초기화를 하여 불필요한 코드를 줄여 줄 수 있다. 비슷한 객체를 여러개 생성하기 위해 생성할때마다 key, value 값을 일일이 입력하여 객체를 만드는것이 아닌 하나의 생성자를 만들어 놓으면 해당 생성자를 사용하여 간편하게 객체를 생성할 수 있다.

(생성자는 암묵적으로 대문자로 시작하는 이름을 가지는 것으로 약속되어 있다..)

 

생성자 함수는 따로 return 값을 가지지 않지만 new 키워드를 사용하여 실행했을 때 자동으로 객체를 생성하고 반환한다. 이렇게 반환되어 만들어진 객체를 다른 말로 인스턴스(instance)라고 한다.

// 생성자 예시
function NewFactory(name){
    this.name = name;
    this.sayYourName = function(){
        console.log(`삐리비리. 제 이름은 ${this.name}입니다. 주인님.`);
    }
}

let robot1 = new NewFactory('브랜든');

원래 함수안의 this는 함수를 호출한 객체를 참조하지만 new연산자가 사용되면 생성자가 만들어낸 객체 즉 인스턴스를 참조한다.

 

프로토타입

여기서 문제...!

손쉽게 객체를 생산할 수 있지만 객체의 메서드를 등록할 때 마다 새로운 함수를 계속해서 생성하고 있다.

바로 이부분이 문제이며 100개의 객체를 생성할 때마다 100개의 함수가 새로 만들어지고 있는 것이고 이러한 자원의 낭비를 해결하기 위해 등장한 것이 바로 프로토타입이다.

 

다시 말해 생성자를 만드는 이유는 동일한 속성과 메서드를 사용하는 객체를 쉽게 만들고 사용하기 위함이고 생성자 내에 함수를 정의한다면 계속해서 함수에 대한 메모리를 차지하는것. 이것은 자원의 낭비이고 비효율적이므로 프로토 타입을 사용하여 생성자의 공용으로 정의되는 함수를 하나의 메모리 공간에 정의 후 생성자가 만들어질 때마다 해당 메모리 주소를 가리키게 하여 메모리 효율을 높이는 것이다. 

 

즉 생성자내에 함수를 정의하지 않고 생성자.prototype.함수명 = function() {} 을 통해 하나의 메모리 공간에 해당 함수의 내용을 할당해놓고 생성자를 통해 객체를 만들면 해당 메모리주소를 가리키게 되고 함수명을 호출하여 사용하면 된다.

 

생성자 함수가 인스턴스를 생성하게 되면 그 안에는 숨겨진 프로퍼티인 [[Prototype]] 이 존재한다. 코드상에서는 __proto__로 표현되며 __proto__ 프로퍼티는 자신을 만든 생성자 함수의 prototype을 참조하는 역할을 한다. 즉, new 키워드를 통해 생성자 함수의 prototype과 인스턴스의 __proto__ 가 연결된다. (__proto__는 인스턴스에 존재하고 prototype은 함수에 존재)

 

이렇듯 프로토타입은 모든 인스턴스가 하나의 메서드를 공유하도록 만들어 자원을 더 효율적으로 사용하도록 도와준다.

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

NewFactory2.prototype.sayYourName = function(){
    console.log(`삐리비리. 제 이름은 ${this.name}입니다. 주인님.`);
}

const robot = new NewFactory2('로봇')
robot.sayYourName()

 

상속

 상속이란 어떤 객체의 프로퍼티 또는 메서드를 다른 객체가 상속받아 그대로 사용할 수 있는 것을 말한다.

이미 Python 과 CSS에서 다뤘으니 개념은 참고하길 바란다.

function Parent() {
    this.name = '재현';
}
Parent.prototype.rename = function (name) {
    this.name = name;
}
Parent.prototype.sayName = function () {
    console.log(this.name);
}

// 부모 객체를 상속
function Child() {
    Parent.call(this);
}

Child.prototype = Object.create(Parent.prototype); // 지정된 프로토타입 객체를 갖는 새 객체를 만듭니다.


Child.prototype.canWalk = function () {
    console.log('now i can walk!!');
}

위의 코드에서 call 함수는 Child 함수의 this가 Parent 생성자 함수의 this를 바라보게 만든다. 즉, Child 를 통해 생성된 인스턴스의 this 가 Parent 함수안의 프로퍼티에 접근할 수 있게 된다.

Object.create 함수는 주어진 인자를 Child.prototype에 연결하는 역할을 수행. 즉 Parent 객체의 프로토타입을 Child 객체의 프로토타입이 참조하게 한다.

위의 두 가지 과정을 통해 Child 객체는 Parent 객체의 모든 것을 상속받게 된다.

 

class

 class 사용법은 class 키워드 + 이름 + 중괄호로 이루어져 있다.

class Robot {
    // 클래스의 생성자 함수입니다. 하나의 클래스는 하나의 생성자만 정의할 수 있습니다. 
	// 그리고 생성자 함수는 new 키워드가 호출될때 자동으로 실행됩니다.
    constructor(name) {
        this.name = name;
    }

    // 메소드를 정의합니다. 메소드는 클래스가 생성한 인스턴스를 통해 사용할 수 있습니다.
    sayYourName() {
        console.log(`삐리비리. 제 이름은 ${this.name}입니다. 주인님.`);
    }
}

 

클래스의 결과물은 인스턴스를 생성하는 것으로 생성자를 이용한 타입 생성과 그 결과가 정확하게 일치한다.

 

class 상속

 class 상속은 extends 키워드를 사용합니다. 상속을 받는 클래스는 파생 클래스(derived class) 라고 부른다. 

이때 부모 클래스의 프로퍼티를 상속받기 위해 super 함수를 사용하고 super는 부모 생성자를 참조한다.

 

super 사용시 주의할 점

  • 만약 파생 클래스에 생성자 함수를 사용하고 싶다면 반드시 super 함수를 사용해야한다
  • 파생클래스에 생성자 함수가 없다면 super 함수가 자동으로 호출되어 부모 클래스의 프로퍼티를 상속 받게 한다
  • 생성자 함수에서 this 값을 사용할 경우 super 함수는 반드시 this 보다 먼저 실행되어야 한다
class BabyRobot extends Robot {
    constructor(name) {
        super(name);
        this.ownName = '아이크';
    }

    sayBabyName() {
				// 또한 상속을 받게되면 부모 클래스의 메소드를 사용할 수 있게 됩니다. 때문에 this로 접근 할 수 있습니다.
        this.sayYourName();
        console.log('Suceeding you, Father!');
    }
}
Comments