2024-07-22 (TIL)

SanE·2024년 7월 22일
0

컴퓨터공학

목록 보기
17/23

📚 새로 알게 된 내용

💡 프로토타입


  • 생성자 함수에 프로퍼티 또는 메서드 정의 가능
  • ‘객체이름.prototype.프로퍼티명 = 코드’ 와 같이 사용 가능
function User(age, name){
	this.age = age;
	this.name = name;
}
User.prototype.mesage = function(){
	return "I'm SanE";
}
User.prototype.hobby = "baseketball";
const San = new User(26, "SeoSan");
console.log(San.hobby);
console.log(San);

프로토타입 도식

위의 도식은 아래 코드를 나타낸 것이다.

let instance = new Constructor();

위의 도식의 흐름을 따라가면 다음과 같다.

  • 생성자 함수(Constructor)를 new 연산자와 함께 호출.
  • Constructor에서 정의된 새로운 인스턴스(instance) 생성.
  • 이때 instance에는 던더 프로토('_ proto _')가 자동으로 부여됨.
  • 이 던더 프로토는 Constructor의 prototype을 참조.

이제 가장 처음에 보여준 코드와 비슷한 코드를 통해 확인해보자.

던더 프로토, instance

function User(age, name){
    this.age = age;
    this.name = name;
}

User.prototype.getName = function(){
    return this.name;
}

let san = new User(27, "SeoSan");
console.log(san.__proto__.getName()) // undefined
console.log(san.__proto__ === User.prototype); // true

위의 결과를 예상해보면 왜 첫번째 결과가 undefined가 나오는지 이해가 안될 수도 있다.
분명 san.__proto__User.prototype은 서로 같은 곳을 바라보고 있다.

문제의 원인은 this 바인딩이 잘못되어 있기 때문이다.
이번에 설명할 부분이 실행 컨텍스트가 아니라 prototype이기 때문에 간략하게 설명하면
User.prototype.getName() 로 메서드로서 함수가 호출이 되었을 때 this가 가리키는 곳은 "." 앞에 써있는 객체를 바라보기 때문에
san을 가리키는 것이 아니라 san.__proto__가 된다.

여기서 그럼 내가 원하는대로 하려면 간단하다 san.__proto__name을 선언하면 된다.

function User(age, name){
    this.age = age;
    this.name = name;
}

User.prototype.getName = function(){
    return this.name;
}

let san = new User(27, "SeoSan");
san.__proto__.name = "dannysir";
console.log(san.__proto__.getName()) // dannysir
console.log(san.__proto__ === User.prototype); // true

그럼 애초에 this를 san으로 하고 싶으면 어떻게 할까?
답은 아주 간단하다.
인스턴스에서 바로 메서드를 사용하면 된다.

let san = new User(27, "SeoSan");
let tmp = new User(20, "tmpName");

console.log(san.getName()); // "SeoSan"
console.log(tmp.getName()); // "tmpName"

그런데 이쯤에서 이상할 것이다.
나는 분명 User.prototypegetName() 메서드를 생성했다.

그런데 어떻게 `__proto__ 없이 바로 메서드를 instance에서 사용하는걸까???

그 이유는 __proto__가 생략 가능한 프로퍼티이기 때문이다.
그러면 또 복잡해질 것이다. 그럼 san.__proto__.getName()에서 생략이 가능하다고 했으니까 san.getName()이랑 결과가 같아야 하는거 아닌가?

이렇게 복잡하게 생각하면 답이 없다. 이런 "생략 가능한 프로퍼티" 라는 개념은 언어를 설계하고 창시한 사람의 아이디어이기 이해를 하지말고 받아들여야 할 것 같다.

여기서 우리가 생각할 것은 하나이다.
__proto__를 생략하면 this가 인스턴스를 가리키고, 생략하지 않으면 __proto__ 자체를 가리킨다는 것이다.

Array vs arr

이제 위의 지식을 바탕으로 배열 두개를 크롬에서 찍어보자.

let arr = [1,2,3,4];

console.dir(arr);
console.dir(Array);

먼저 처음 보이는 arr의 결과에서 Prototype열어보면 우리가 배열에 사용하는 모든 메서드들이 들어가 있다.

그런데 Array의 결과를 보면 서로 조금 다른걸 알 수 있다.
prototype에 들어있는 메서드는 같지만, 정적 메서드인 from(), isArray() 등이 보인다.

이 결과를 그림으로 표현하면 다음과 같이 표현할 수 있을 것이다.

이 그림을 보면 왜 isArray() 같은 정적 메서드를 반드시 Array와 함께 써야하는지 이해할 수 있다.

let arr = [1,2,3,4];
Array.isArray(arr); // true
arr.isArray(); // TypeError

프로토타입 체인

이제 프로토타입 체인이 무엇인지 알아보기 앞서 한가지 미리 확인하자.

위의 사진은 {a:1} 객체를 크롬에 찍어보면 나오는 결과이다.
보면 Prototype 안에 우리가 익숙한 hasOwnProperty() 같은 메서드들이 보인다.

이제 다시 우리가 아까 봤던 배열의 Prototype을 살펴보자.
그럼 다음과 같은 구조로 되어있는걸 알 수 있다.

  • prototype : Array(0)
    • 익숙한 메서드들.
    • Prototype(Object) (회색 글씨)

저 회색으로 되어 있는 객체 prototype을 열면 우리가 저 위에서 봤던 객체 Prototype과 동일한 내용으로 이루어져 있다는 것을 확인할 수 있다.
왜 그럴까?

그 이유는 바로 prototype 객체가 바로 "Object" 즉 "객체"이기 때문이다.
따라서 모든 객체의 __proto__ 에는 Object.prototype이 연결된다.

이제 우리가 알기 쉽게 그림으로 표현하면 다음과 같다.

이처럼 어떤 데이터의 __proto__ 내부에서 다시 __proto__ 가 연쇄적으로 이어진 것을 바로
프로토타입 체인이라고 하고 이 체인을 따라서 검색하는 것을 프로토타입 체이닝이라고 한다.

💡 클래스


  • ES6에서 다른 언어의 객체 지향 문법과 유사한 class 키워드 기반 객체 생성 문법이 표준화됨
  • 하지만, 지금까지도 javascript 개발자들은 객체 리터럴을 주로 사용하기도 함
    • javascrip 로 그렇게까지 복잡한 코드를 작성할 일이 없기도 하고, 기존 객체 리터럴 방식에 익숙한 개발자가 많음

클래스 정의

  • class 클래스명{} 으로 클래스 정의 가능
class User{
 ...
}

constructor(): 클래스 생성자 함수

  • 클래스 내부에 constructor() 라는 이름으로 하나의 생성자 함수를 작성할 수 있다.
class User {
	constructor() {
		...
	}
}
  • class 로 정의된 클래스는 new 클래스명() 으로 객체로 생성될 수 있음
  • 클래스 프로퍼티는 constructor 내부에서 this 키워드로 선언될 수도 있다.
class User {
	constructor(){
		this.name = "SanE"
	}
}
const san = new User();
console.log(san);
  • 객체 생성시 인자 정의는 constructor 에서 할 수 있음
class User {
	constructor(name){
		this.name = name;
	}
}
const san = new User("SanE");
console.log(san.name);
  • 클래스 내부에서 메서드 생성 가능
class User {
	constructor(name){
		this.name = name;
	}
	get_message(){
		return "Hello";
	}
}
const san = new User("SanE");
console.log(san.get_message());
  • 상속도 일반적인 객체지향 문법과 유사함
    • extends 를 사용해서, 상속할 클래스를 선언할 수 있다.
    • 자식 클래스에서는 super()(부모클래스의 constructor() 를 호출함 ) 을 constructor() 안에서 호출해야함
class Animal {
	constructor(){
		this.name = name;
	}
}
class User extends Animal {
	consturctor(name, brand) {
		super(name);
		this.brand = brand;
	}
}
const san = new User("SanE", "jnu");
console.log(san);
  • 객체지향의 다형성 또한 지원
class Test1 {
	constructor(name){
		this.name = name;
	}
	get_message(){
		return "Hello";
	}
}

class Test2 extends Test1{
	constructor(name, brand){
		super(name);
		this.brand = brand;
	}
	get_message(){
		return "Hello, World!";
	}
}
const san = new Test2("SanE","JNU");
console.log(san.get_message());

💡 hasOwnProperty() 사용법


  • 클래스명.property.프로퍼티 = 프로퍼티 값 으로도 클래스 외부에서 프로퍼티 값 추가 가능( 그래도 클래스 내부에서 선언하는 것을 권장 )
  • 클래스 내부에서 선언한 프로퍼티임을 확인하기 위해 hasOwnProperty(프로퍼티명)을 사용
class User {
	constructor(name){
		this.name = name;
	}
	get_message(){
		return "Hello";
	}
}

User.prototype.age = 26;
const san = new User("SanE");
console.log(san.name);
console.log(san.OwnProperty.name);
console.log(san.OwnProperty.age);
  • 결과
SanE
true
false

💡 Class in javascript


 class Position {
    constructor(score) {
        this.score = score;
        this.grade;
    }

    setGrade(grade) {
        this.grade = grade;
    }
}

 class Student extends Score{
    constructor(score, name) {
        super(score);
        this.name = name;
        this.age = 0;
    }

    setAge(age) {
        this.hp = age;
    }


}

만약 위와 같이 있다면 기본적으로 Position 클래스의 메서드는 Position의 프로토타입에 들어가게 된다.
그리고 extends를 통해 상속을 진행한다고 가정했을 때 아래와 같이 프로토타입 체인이 이어진다.

Position ------ Position.prototype (setGrade())
						|
                        |
                        |
                        |
Student ------ Student.prototype (setAge())
profile
완벽을 찾는 프론트엔드 개발자

0개의 댓글