객체지향과 this

괴발·2023년 2월 16일
0

울지말고JavaScript

목록 보기
9/9
post-thumbnail

해당 게시글은 유튜브 '코딩알려주는누나'의 영상을 보고 정리한 내용입니다.
객체지향 그리고 this
이 사람은 찐이야!👍👍👍👍👍👍

객체지향이 뭔가요

왜태어났나
왜써야하나

let name = 'samsung store';

let tv1 ={
	name : 'tv1',
	price : 200,
	size : '56inch',	
}
let tv2{
	name : 'tv2',
	price : 200,
	size : '27inch',
}
let tv3{
	name : 'tv3',
	size : '36inch',
}

보면 tv 객체 3개 만드는데 벌써 오류가 있다.(tv3에 price가 빠졌다.)
게다가 이렇게 tv12345...9999 까지 하나하나 만들려고 한다면 이는 개노가다가 아니겠는가.
개발자는 업무를 효율적으로 만드는 사람.
이로인해 말들어진게 객체지향이다.

CLASS = 작업지시서

class TV {
	name = '';
	price = 0;
	size = '';
}

class를 정의해 놓고 class 객체 안에 사용할 값들의 key를 미리 넣어두고 여기에 value만 입력 받아서 사용하면 된다.

그럼 value값은 어떻게 입력 받을까?

class는 내부 속성(key)들의 값value를 초기화하고 입력을 도와주는 함수가 있다.
constructor(){}

class TV {
	name = '';
	price = 0;
	size = '';

	constructor(name, price, size){ //생성자함수
		name = 'name';
		price = price;
		size = 'size';
	}
}

🚨
이때, name = 'name', price = price, size = 'size' 동일한 이름의 값들을 사용하다 보면 100% 헷갈린다.
이를 보완하기 위한 개념이 this 이다.
class 안에 있는 (초기값을 지정한!) 속성이라는 것을 지시하기 위해 앞에 this를 붙여준다.

class TV {
	name = '';
	price = 0;
	size = '';

	constructor(name, price, size){ //생성자함수
		this.name = 'name';
		this.price = price;
		this.size = 'size';
	}
}

🚨 this.name 이 가리키는 것은 class TV 안에 있는 name = ' ' 이다.

결과적으로 class TV(작업지시서)를 통해 알 수 있는 것은
1. class TV는 name, price, size 속성이 반드시 있어야 한다.
2. class TV를 만들 때 반드시 name, price, size 속성을 넣어줘야 한다.

이로써 TV객체를 만들 사전준비가 끝났다.
이제 실제로 TV객체를 만들어 보겠다.
간단하다.

let tv100 = new TV('television', 200, '30inch');
let tv99 = new TV('visionTV', 300, '40inch');

이제 프로그램을 실행시키면 tv100, tv99 라는 객체가 생성되고
점표기법(tv100.name)으로 객체 속성property에 접근할 수 있다.

이런 방식으로 냉장고, 컴퓨터, 정수기 등등 많은 class를 만들어 둘 수 있다.

class freezer {
	name = '';
	price = 0;
	weight = 0;
}
class laptop {
	name = '';
	price = 0;
	weight = 0;
	type = '';
}

이렇게 만들다보면 겹치는 정보가 있다.
바로 name = ' '; 과 price = 0; 이다.
이 또한 class를 통해 간단하게 작성할 수 있지 않을까

class product{
	name = '';
	price = 0;
}

그럼 이걸 어떻게 상품 class에 넣을 수 있을까

class TV extends produce{
	size = '';
}
class freezer extends produce{
	weight = 0;
}
class laptop extends produce{
	weight = 0;
	type = '';
}

위와 같이 extends 를 사용해 class를 확장하여 사용할 수 있다.
이러면 class TV는 name과 price를 적지 않아도, class product를 가져와 내 것 처럼 사용할 수 있다.

이처럼 공통의 프로퍼티를 뽑아내서 별도의 상위 class를 만드는 것을 '추상화' 라고 하며
이 '추상화'된 class를 상품 class에 삽입하는 것을 '상속' 이라고 한다.

그럼 우리가 상품 객체를 만들때 쓰던 constructor 함수는 그래도 두면 되는 건가? > 아니지.

class TV {
	size = '';

	constructor(name, price, size){ //생성자함수
		this.name = 'name';
		this.price = price;
		this.size = 'size';
	}
}

우리가 만든 constructor의 name 과 price는 this가 가리키는 class TV안에 존재하지 않는다.

class TV 보다 상위 객체인 class product를 불러야 한다. 이때 사용하는 것이
super()
super()는 내 상위 클래스를 부를 때 쓴다.

class product{
	name = '';
	price = 0;
}

class TV {
	size = '';

	constructor(name, price, size){ //생성자함수
		super(name, price);
		this.size = 'size';
	}
}

이렇게 super() 를 통해서 부모인 class product의 속성까지 불러올 수 있게 된 것이다.
그리고 이제 class product에도 constructor를 적어주면 된다.

class product{
	name = '';
	price = 0;
	constructor(name, price){ //생성자함수
		this.name = 'name';
		this.price = 'price';
	}
}

class TV {
	size = '';

	constructor(name, price, size){ //생성자함수
		super(name, price);
		this.size = 'size';
	}
}

이렇게 구조는 바뀌었지만 객체 생성 방법은 동일하다.

let tv100 = new TV('television', 200, '30inch');
let tv99 = new TV('visionTV', 300, '40inch');

document.write(tv100,tv99);

TV {
name : 'television',
price : 200,
size : '30inch'
}
TV{
name : 'visionTV',
price : 300,
size : '40inch'
}

부모 class에 있는 name이나 price도 내것처럼 가져올 수 있다.

console.log(tv100.name, tv99.price);

television, 300

class의 기능은 여기서 끝이 아니다.

예를 들어 price에 금액 + '만원' 이라는 표기를 해줘야 한다면 어떻게 해야 할까?
class 는 함수를 내부에서 정의해줄 수도 있다.

class product{
	name = '';
	price = 0;
	constructor(name, price){ //생성자함수
		this.name = 'name';
		this.price = 'price';
	}
	getPrice() {
		return this.price + '만원';
	}
}

📍 변수들과 변수에 관련된 함수들을 class 안에 함께 정의해서 패키지 처럼 사용할 수 있다. 이것을 '캡슐화' 라고 한다.

'캡슐화'
변수에 유저가 직접적으로 접근하는 것을 막을 수 있고,
값을 정의할 때 악의적인 행위를 예방할 수 있다.

class product{
	name = '';
	price = 0;
	constructor(name, price){ //생성자함수
		this.name = 'name';
		this.price = 'price';
	}
	getPrice() {
		return this._price + '만원';
	}
	setPrice(price) {
		if(price<0){
			throw new Error('price는 양의 정수만 가능합니다.')
		}
		this._price(price);
	}
}

tv100.setPrice(-1000);
console.log(tv100.gerPrice()); // Error: price는 양의 정수만 가능합니다. 

this

자바스크립트에서의 this는 다른 언어들과 다르게 값이 바뀐다.

MDN
this
JavaScript에서 this의 값은 함수를 호출하는 방법에 의해 결정됩니다.

뭔솔?

this의 값은 함수호출 될 때 결정된다.

함수가 생성(x) 정의(x) 호출(o) 될 때 결정된다.

const car = {
	name : 'kia',
	getName : function(){
		console.l og("car getName : ", this)
	}
}

car.getName(); 
//'this는 this가 위치하는 객체라고 했으니 car 객체 자체가 나오겠지?' 
// car getName > {name : 'kia', getName : f}
// 만일 this.name 이였다면
// car getName > kia
const car = {
	name : 'kia',
	getName : function(){
		console.l og("car getName : ", this)
	}
}

const globalCar = car.getName;
globalCar();
//car getName
// > window{ window : window, self : window, document : document, name : '', ...}

?????????????????????
이게뭐지???????????????
????????window???????
window는 JavaScript 내장객체인데?

이런 현상의 원인을 알아보자면,
우선 이전 car.getName();car 라는 객체가 호출한 것이다. (A.b = A가 b를 부른다.)
하지만 globalCar(); 를 보면 그냥 밖에서 부른 것으로 확인된다. (그냥 b)
그렇기때문에 최상단에 있는 객체인 window가 자동으로 globalCar를 호출한 것 처럼 된 것이다.

const car = {
	name : 'kia',
	getName : function(){
		console.l og("car getName : ", this)
	}
}

const car2 = {
	name : 'hyundai',
	getName : car.getName,
};

car2.getName();

그렇다면 위와 같이 car에 있는 getName을 car2 에서 부른다면,
this가 가리키는 name은 'kia' 일까 'hyundai' 일까

car2.getName();

//car getName
// > {name : 'hyundai', getName : f};

car2.getName()'hyundai' 를 보여주었다.
📍 이를 통해 this는 this 가 선언된 위치가 아니라 this 를 호출한 함수의 위치에 따라 정해지는 것을 알게 되었다.

극단적인 예시를 들자면

<button id='button'>this를 찾아라</button>

위와 같은 button이 있을 때,

const btn = document.getElementById('button');
btn.addEventListerner("click", car.getName);

javascript로 button에 접근하여 addEventListener로 click 이벤트를 car.getName 으로 추가해줬다.
이러면 car.getName 은 누구를 this 로 잡을까?

car getName
<button id='button'>this를 찾아라</button>

WOW
button 태그 자체가 나와버렸다.
이번 this는 car.getName 함수를 호출한 button 태그를 가리켰다.

🔥 이처럼 this값이 흔들리는 샴푸향기처럼 종잡을 수 없다면 this는 어떻게 사용할 수 있겠는가?!🔥

바로 .bind를 이용하면 된다.

이전 자동차 예시를 다시 불러와서,

const car = {
	name : 'kia',
	getName : function(){
		console.log("car getName : ", this)
	}
}

const car2 = {
	name : 'hyundai',
	getName : car.getName,
};

//car2.getName();

const bindGetName = car2.getName.bind(car);
bindGetName();

//car getName
// > {name : 'kia', getName : f};

보다시피 car2.getName.bind(car); 를 통해 getName이 가리키는 this의 값을 car 로 고정을 시켜주고,
함수를 호출하면 이번에 this가 가리키는 값은 car2가 호출했지만 car의 name인 kia가 불러와졌다.

그렇다면,

const btn = document.getElementById('button');
btn.addEventListerner("click", car2.getName.bind(car));

이러면 button을 click 하면 addEventListerner가 보여주는 car2.getName은 < button > 태그가 아닌 car의 car.getName 을 가져올 것이다.

여기서 문제~!

const testCar = {
	name : 'benz',
	getName : function () {
		console.log('getName', this.name);
		const innerFunc = function() {
			console.log('inner', this.name);
		}
		innerFunc();
	}
}

testCar.getName();

과연 testCar.getName(); 의 값은 무엇이 나올까?

console.log('getName', this.name);
// getName benz

console.log('inner', this.name);
// innerFunc

첫번째 console.log는 예상한 결과값이 나왔지만
두번째 console.log는 왜 저런지 모르겠다.

두개의 console.log가 가리키는 this는 무엇일까?

console.log('getName', this);
// getName > {name : 'benz', getName : f}

console.log('inner', this);
// innerFunc > window {window : window, self : window, document : document, ...}

innerFunc 은 최상위의 window객체를 부르고 있다. 왜그런가?
다시 innerFunc()이 호출된 모습을 보면 단순히 innerFunc() 만 적혀있고 누가 호출했는지가 적혀있지 않다.
그렇기 때문에 innerFunc()의 위치가 getName 안에 있더라도 호출될 때 범위를 지정해주지 않으면 자동으로 최상위 window 객체로 정해진다.

그렇다면 testCar.getName의 this와 innerFunc의 this가 같게 만들려면 어떻게 해야 할까?

위에서 얘기한 .bind를 사용해도 되지만 화살표 함수를 사용하면 간단하다.

const testCar = {
	name : 'benz',
	getName : function () {
		console.log('getName', this.name);
		const innerFunc = () => {
			console.log('inner', this.name);
		}
		innerFunc();
	}
}

testCar.getName();


// console.log('getName', this);
// getName > {name : 'benz', getName : f}

// console.log('inner', this);
// getName > {name : 'benz', getName : f}

// 두 값 모두 동일한 값이 나왔다.

📍 화살표 함수에서의 this는 함수가 속해있는 곳의 상위 this를 계승 받는다.
javascript에서 화살표 함수의 this는 일반함수의 this와 달라서,
화살표 함수가 있는 곳 상위의 this를 계승 받는다.
🔥 즉 innerFunc을 화살표함수로 변경하면 innerFunc의 this는 상위 함수인 testCar의 this와 같아진다.

여기서 또 문제~!

const ageTest = {
	unit : '세',
	ageList : [10,20,30],
	getAgeList : function(){
		const result = this.ageList.map(function(age){
			return age;
		});
		
		console.log(result);
	}
};

ageTest.getAgeList(); // [10,20,30]
//여기에 뒤에 unit에 있는 '세'를 붙여주고 싶다면
const ageTest = {
	unit : '세',
	ageList : [10,20,30],
	getAgeList : function(){
		const result = this.ageList.map(function(age){
			return age + this.unit;
		});
		
		console.log(result);
	}
};

ageTest.getAgeList(); // [NaN,NaN,NaN]

🤔 왜 NaN이 나온 것일까?
여러 방식으로 만져봤는데 마지막 30에만 '세' 가 붙거나 아예 안붙었다.
map 함수가 돌면서 숫자를 뽑아내고 숫자마다 '세' 가 붙어야 하는데,
this의 범위를 정해주는 것이 포인트 인 것 같다.

this의 범위를 지정하는 것은 점 표기법으로 불러올 객체를 적어주거나
.bind를 사용하거나 화살표함수를 사용하여 상위 this로 고정하는 방법이 있다.

이 중 .bind는 다른 함수를 고정하여 호출하는 용도인데 지금은 하나의 함수뿐이니 패스.

다음은 점표기법으로 불러올 객체를 적어주는 것.

const ageTest = {
	unit : '세',
	ageList : [10,20,30],
	getAgeList : function(){
		const result = this.ageList.map(function(age){
			return age + ageTest.unit;
		});
		
		console.log(result);
	}
};

ageTest.getAgeList(); //["10세","20세","30세"]

다음은 화살표 함수를 사용해 상위 this로 고정하는 방법

const ageTest = {
	unit : '세',
	ageList : [10,20,30],
	getAgeList : function(){
		const result = this.ageList.map((age)=>{
			return age + this.unit;
		});
		
		console.log(result);
	}
};

ageTest.getAgeList(); //["10세","20세","30세"]
profile
괴발개발

0개의 댓글