[코어 자바스크립트] 06 클래스

코어 자바스크립트를 읽고 학습한 내용을 정리한 글이지만 모던 자바스크립트 정리 내용도 섞여있습니다. 정리한 내용에 오류가 있을 수 있으며 혹시 잘못된 부분이 있다면 댓글로 알려주시면 정말 감사드리겠습니다.

06 클래스

왜 클래스를 알아야 할까?

마지막 파트 '클래스'를 스터디에서 발표를 맡아 자료를 준비하면서 좀 당황했다.

왜냐하면 기존에 알고 있던 ES6 클래스에 대한 설명이 아니라 ES5까지 예전에는 어떤 다양한 방식으로 고군분투해왔는지가 담겨있기 때문이다.

이미 ES6가 나온지 꽤 된 시점에서 책에서 나온 예시처럼 이제는 extendClass라는 함수를 직접 만들어서 구현하지 않아도 extends와 super등 자바스크립트 내장 명령으로 손쉽게 클래스 상속을 구현할 수 있다.

그럼에도 저자분께서 이 내용을 담은 이유는 이 클래스 파트가 이 코어 자바스크립트의 모든 내용이 스며들어 있기 때문이라고 한다.

참조형 데이터가 어떤 식으로 정보를 저장하고, 다른 데이터를 할당하면 어떻게 되고, 스코프와 실행컨텍스트, 클로저 원리, this바인딩, 프로토타입과 체이닝 등 전체 흐름을 분석할 수 있다고 한다.

만약 ES6 클래스를 자세히 알고 싶다면 모던자바스크립트 클래스 파트를 추천하고 싶다.

클래스

자바스크립트는 상속의 개념이 없다.
자바스크립트는 프로토타입 기반 언어다.
ES6에 클래스가 생겼다고 할 수 있지만 사실 ES6의 클래스에서도 일정 부분은 프로토타입 활용한 거기 때문에 ES5 체제 하에서 클래스를 흉내내기 위한 구현이다.

클래스와 인스턴스의 개념 이해

음식은 과일의 상위 클래스 superclass가 될 수 있다.
음식은 한식, 양식, 중식 등 다양한 개념을 포괄적으로 가진 추상적 개념이다.

과일은 음식의 하위 클래스 subclass가 될 수 있다.
과일 역시 배, 사과, 바나나 등 다양한 과일을 포괄적으로 가진 추상적 개념이다.

반면 배, 사과, 바나나, 감, 오렌지는 눈으로 보고 만질 수 있고 먹을 수 있는 구체적인 물체다.
이 구체적인 물체가 바로 과일이라고 하는 클래스에 속하는 인스턴스다.

클래스

공통적인 속성을 모아 한 데 묶은 덩어리 또는 명세

인스턴스

어떤 클래스의 속성을 지니는 실존하는 개체
영한사전: 어떤 조건에 부합하는 구체적인 예시 → 조건 = 클래스

컴퓨터의 사고 방식

사람의 사고 방식에서 '나'는 이미 존재하는 다양한 클래스로 생성할 수 있다.
→ 여성, 취준생, 한국인

하지만 컴퓨터는 다르다. 컴퓨터는 '여성', '남성', '취준생' 등의 개념이 존재하지 않는다.
사용자가 직접 여러 가지 클래스를 정의해야 하며, 클래스를 바탕으로 인스턴스를 만들 때 비로서 어떤 개체가 클래스 속성을 지니게 된다.

자바스크립트의 클래스

프로토타입을 일반적인 의미에서의 클래스 관점에서 접근

  1. 생성자 함수 Arraynew 연산자와 함께 호출 → 인스턴스 생성
  2. Array를 일종의 클래스라고 하면 Arrayprototype 객체 내부 요소들이 인스턴스에 ‘상속’된다고 볼 수 있다. → 엄밀히 말하면 프로토타입 체이닝에 의한 참조다.
  3. 프로토타입 메서드: 인스턴스가 직접 호출할 수 있는 메서드
    스태틱 메서드: 인스턴스에서 직접 접근할 수 없는 메서드

클래스 관점에서 바라본 프로토타입 시스템

// 생성자
var Rectangle = function(width, height){
  this.width = width;
  this.height = height;
};

// (프로토타입)메서드
Rectangle.prototype.getArea = function(){
  return this.width * this.height
};

// 스태틱 메서드
Rectangle.isRectangle = function(instance){
  return instance instance of Rectangle && instance.width > 0 && instance.height > 0
};

var rect1 = new Rectangle(3, 4);
console.log(rect1.getArea()); // 12
console.log(rect1.isRectangle(rect1)); // Error
console.log(Rectangle.isRectangle(rect1)); // true

프로그래밍 언어에서의 클래스는 사용하기에 따라 추상적일수도 있고 구체적인 개체가 될 수도 있다.

→ 구체적인 인스턴스가 사용할 메서드를 정의한 ‘틀’의 역할을 담당하는 목적의 클래스: 추상적 개념
→ 클래스 자체를 this로 해서 직접 접근해야만 하는 스태틱 메서드를 호출할때의 클래스: 그 자체가 하나의 개체

클래스 상속

ES5까지의 자바스크립트에는 클래스가 없다.
ES6에서 클래스가 도입됐지만 프로토타입 기반으로한 것으로 결국 프로토타입 체이닝을 잘 연결한 것이다.

ES5까지의 자바스크립트 커뮤니티에서는 클래스 상속을 다른 객체지향 언어에 익숙한 개발자들에게 최대한 친숙한 형태로 흉내 내는 것이 주요한 관심사였다.

var Grade = function(){
  var args = Array.prototype.slice.call(arguments);
  for(var i = 0; i < args.length; i++){
    this[i] = args[i]
  }
  this.length = args.length;
};

Grade.prototype = [];
var g = new Grade(100, 80);

문제점

  1. length 프로퍼티가 configurable true하다는 점
    (configurablefalse인 경우 → 해당 프로퍼티의 삭제, 프로퍼티 어트리뷰트 값의 변경 금지)
    내장 객체인 배열 인스턴스의 length 프로퍼티는 configurable 속성이 false라서 삭제가 불가능하지만,
    Grade 클래스의 인스턴스는 배열 메서드를 상속하지만 기본적으로 일반 객체 성질을 그대로 지니므로 삭제가 가능하다.
g.push(90);
console.log(g)
    
delete g.length;
    
g.push(70);
console.log(g)

  1. Grade.prototype에 빈 배열을 참조시켰다는 점
    g.__proto__ = Grade.prototype이 빈 배열을 가리키고 있기 때문에
    push(70)명령에 의해 g.length를 읽었지만 삭제해서 프로토타입 체이닝을 타고 g.__proto__.length를 읽어온 것이다.
    때문에 빈 배열의 length0이므로 여기에 값을 할당하고 length1만큼 증가시키라는 명령에 문제 없이 동작한다.

클래스에 있는 값이 인스턴스의 동작에 영향을 줘서는 안된다.

인스턴스와의 관계애서는 구체적인 데이터를 지니지 않고 오직 인스턴스가 사용할 메서드만을 지니는 추상적인 ‘틀’로서만 작용하게 끔 작성하지 않는다면 언젠가 어딘가에서 예기치 않은 오류가 발생할 가능성이 생긴다.

사용자가 정의한 두 클래스 사이에서의 상속 관계를 구현

var Rectangle = function(width, height){
  this.width = width;
	this.height = height;
}

Rectangle.prototype.getArea = function(){
  return this.width * this.height
}

var rect = new Rectangle(3,4);
console.log(rect.getArea()); // 12

var Square = function(width){
  this.width = width;
}

Square.prototype.getArea = function(){
  return this.width * this.width;
}

var sq = new Square(5);
console.log(sq.getArea()); // 25

공통 요소 → width / getArea 비슷

var Square = function(width){
  this.width = width;
	this.height = width;
}

Square.prototype.getArea = function(){
  return this.width * this.height;
}

SquareRectangle의 하위 클래스로 삼기

// Square의 생성자 함수 내부에서 Rectangle의 생성자 함수를 함수로써 호출
var Square = function(width){
  Rectangle.call(this, width, width)
}

// 메서드를 상속하기 위해 Square 프로토타입 객체에 Rectangle 인스턴스 부여
Square.prototype = new Rectangle();

클래스에 있는 값이 인스턴스에 영향을 줄 수 있는 구조라는 동일한 문제

console.dir(sq)

  • Square의 인스턴스임을 표시하고 width, heigth 모두 잘 들어 있다.
  • __proto__Rectangle에 인스턴스임을 표시하는데 이 widthheight가 모두 undefined로 할당되어 있다.
    Square.prototype에 값이 존재하는 문제
  • 만약 임의로 Square.prototype.width에 값을 부여하고 sq.width의 값을 지워버리면 엉뚱한 결과 초래할 수 있다.
  • construtorRectangle을 바로보고 있다.
var rect2 = new sq.constructor(2,3);

하위 클래스로 삼을 생성자 함수의 프로토타입에 상위 클래스의 인스턴스를 부여하는 것만으로도 기본적 메서드 상속은 가능하지만 다양한 문제 발생 여지가 있고 이는 안정성 떨어진다.

클래스가 구체적인 데이터를 지니지 않게 하는 방법

1. 프로퍼티 지우기

만들고 나서 프로퍼티들을 일일이 지우고 더는 새로운 프로퍼티 추가 금지

delete Square.prototype.width;
delete Square.prototype.height;
Object.freeze(Square.prototype);

반복 작업을 없앨 함수는 189p에 더 자세히 나와있다.

2. 더글라스 크락포드 - 빈 생성자 함수 Bridge

SubClassprototype에 직접 SuperClass의 인스턴스를 할당하는 대신
아무런 프로퍼티를 생성하지 않는 빈 생성자 함수를 하나 더 만들어
prototypeSuperClassprototype을 바라보게끔 한 다음
SubClassprototype에는 Bridge의 인스턴스를 할당하게 하는 방법이다.

var Rectangle = function(width, height){
  this.width = width;
  this.height = height;
};

Rectangle.prototype.getArea = funtion(){
  return this.width * this.height;
}

var Square = function(width){
  Rectangle.call(this, width, height);
};

var Bridge = function(){}

Bridge.prototype = Rectangle.prototype;
Square.prototype = new Bridge();
Object.freeze(Square.prototype)

3. ES5 Object.create

SubClassprototype__proto__SuperClassprototype을 바라보되, SuperClass의 인스턴스가 되지는 않는다.

Square.prototype = Object.create(Rectangle.prototype);
Object.freeze(Square.prototype);

좋은 웹페이지 즐겨찾기