-
Javascript 클래스Web/자바스크립트 2023. 3. 4. 17:54
아래 내용은 웹 개발자를 위한 자바스크립트의 모든 것 4장을 읽고 정리한 내용입니다.
클래스란 무엇인가?
언어가 클래스를 갖는다는 것은 “캡슐화”와 “상속”을 제공해야 한다.
클래스 “기반” 언어와 언어가 클래스를 “갖는다”는 것은 다른 의미에서 이해되어야 하며, 자바스크립트는 ES6부터는 어떠한 논쟁 없이 클래스를 갖는다고 할 수 있게 되었다.▸ 이번 장은 정말 처음부터 끝까지 읽기 힘들었지만, 정말 몰랐던 사실이라 무척 재밌게 읽을 수 있었다.
기본적인 클래스 선언 구조를 보면 아래와 같다.
c++, c#, java 등 다른 클래스 기반 언어로 개발을 했다면 무척이나 익숙한 구조의 클래스 문이다. 심지어 static (정적 메서드)까지 지원할 수 있는 클래스가 생겼을 줄 몰랐다!
기존에는 prototype을 이용해서 구현이 가능하긴 했으나, 라이브러리 개발 같은 것을 하지 않는다면 특별히 사용하지 않았었다 (개인적으로는). 그런데 클래스라는 명시적인 구조가 생기고 나니 여러모로 편하고 유용하게 쓸 수 있을 것 같다는 생각이 든다.class Hello { constructor(greeting) { this.greeting = greeting; } get sayHello() { return `Hello, ${this.greeting}`; } set sayHello(value) { this.greeting = `Hi, ${value}`; } toString() { return `Greeting : ${this.greeting}`; } static fromLang(lang) { switch (lang) { case "ko": return new this("안녕"); case "en": return new this("hello!"); } } } let c = new Hello("world"); console.log(String(c)); c = Hello.fromLang("ko"); console.log(String(c));
클래스 기본은 많은 곳에서 설명을 하고 있을 듯하여, 기억하면 좋은 요소와 좀 더 확인을 해본 것들 위주로 정리를 하고자 한다.
클래스 기본 개념
extends
위에서 언급했던 클래스를 가진 언어라면 응당 가져야 할 캡슐화는 위의 선언을 통해 설명이 되었고, 상속을 어떻게 하는지는 다른 언어와 동일하게 extends라는 키워드를 사용한다.
부모 클래스를 확장시켜서 자식 클래스를 만드는 것은 다른 언어와 동일하게 지원한다. 다만 조금 다른 게 있다면 생성자 부분에서 다른 언어는 생성자의 파라미터 별로 오버 라이딩을 해야 한다면, 자바 스크립트는 하나의 생성자만 사용한다class Hi extends Hello { /// 구현 }
super()
부모 클래스의 함수를 호출할 때 사용된다.
생성자로 호출할 때는 super() 형태이지만, 그 외는 super.props 형태가 된다.
super를 통해 prototype을 통해 힘겹게 호출하였던 부모 클래스를 훨씬 쉽게 호출할 수 있게 되었다.또한 super를 사용할 때 또 다른 이점은 리팩토링을 할 때 protoptye을 사용할 경우, 부모 클래스를 변경하는 것을 방지해 줌
super([arguments]); // 부모 생성자 호출 super.functionOnParent([arguments]);
아래 링크에서도 설명되어 있지만, static 함수에서도 super 사용이 가능!
super - JavaScript | MDNSymbol.species
Symbol.species는 static data property이며, getter만 있고 setter는 없다.
이 개념 이해를 위해서는, 클래스 변수 내의 함수를 실행했을 때 그 변수 자체가 아닌 새로운 인스턴스로 “생성자”를 통해 생성해서 돌려준다는 것을 인지하고 있어야 한다.
예를 들면 아래 예제의 map 함수는 결과 값을 a가 아닌 새로운 인스턴스인 mapped를 생성해 낸다. 이 과정에서 인스턴스를 생성하기 위해 생성 자가 사용된다.
MyArray가 단순하게 Array를 상속받았다면, MyArray 클래스는 생성자를 가지고 있지 않는 것처럼 보이지만 기본 생성 자가 호출되어 MyArray 객체로 mapped가 생성되었을 것이다.이때, MyArray가 아닌 Array로 인스턴스를 받고 싶을 때 사용할 수 있는 것이 Symbol.species이다. static data property이기 때문에 static으로 선언이 되어야 하며 아래와 같이 get 함수에 선언을 하게 되면, 해당 클래스를 통해 새로운 인스턴스를 생성할 때 원래의 클래스 인스턴스(MyArray)가 아닌 지정된 클래스 인스턴스(Array)로 생성이 된다.
그렇다면 모든 클래스 내의 함수가 생성자를 통해 새로운 인스턴스를 반환할까?
답은 당연히 “아니요”test 함수를 통해 this를 반환하게 되면 클래스 그 자체이므로 여전히 MyArray라는 결과를 얻게 된다.
대부분의 예제에서 기본 Array를 반환해서 내가 만든 클래스도 가능한 지 YourArray를 만들어 보았는데, 의도대로 MyArray는 아니지만 YourArray는 맞다는 결과가 나온다. 당연하게도 YourArray는 Array를 상속받고 있어서 Array의 인스턴스 또한 맞다는 결과가 나온다.그렇다면 YourArray가 Array의 확장이 아닌 String을 상속받았다면 어떨까?
Uncaught TypeError: Cannot redefine property
당연히 오류가 난다. 하지만, Array 속성으로 생성자를 통해 반환받는 함수가 없을 경우는 여전히 문제없이 동작한다.class YourArray extends Array {} class MyArray extends Array { // Overwrite species to the parent Array constructor static get [Symbol.species]() { return Array; // return YourArray; } test() { return this; } } const a = new MyArray(1, 2, 3); const mapped = a.map((x) => x * x); const t = a.test(); console.log(t); console.log(t instanceof MyArray); // true console.log(mapped instanceof MyArray); // false console.log(mapped instanceof Array); // true console.log(mapped instanceof YourArray); // true
Symbol.species - JavaScript | MDN
Symbol Propertynew.target
함수가 어떻게 호출 되었는지를 확인하면 아래 두 작업이 가능
- Abstract class
- 서브 클래스를 허용하지 않는 클래스
구현에 대한 기본 로직은 유사한데 추가 로직 구현이 필수 인 경우, Abstract class를 적용하면 좋을 듯하다.
class Shape { constructor(color) { if(new.target === Shape) { throw new Error(“Shape can’t be directoly instantiated”); } } }
위의 예제가 워낙 잘 작성되어 있어서 더 설명할 것이 없다. 이 예제를 보고 하나 더 느낀 부분은 throw new Error 라는 것을 잘 사용하면 공통 component 개발 시, 다른 개발자들에게 문제를 좀 더 직관적으로 설명해 줄 수 있겠다는 것!
new 를 통해 생성되지 않은 것도 감지가 가능한 데, new.target이 존재하지 않으면 단순 함수로써 생성을 했다는 의미. 이 것도 잘 활용하면 코드 가독성을 높이는데 도움이 되지 않을까 싶다.
if(!new.target) { console.log(‘Called directly; using ‘new’ insted’); return new Class(); }
Summary
클래스를 온전히 다른 언어와 유사하게 쓸 수 있다는 것은 나에게 매우 새로운 발견이다. 클래스를 통해 무엇을 좀 더 좋게 만들 수 있을지 기대된다.
새로이 알게 된 것
객체(object), 인스턴스(instance) 차이
위의 글을 정리하다 객체와 인스턴스의 차이가 문득 궁금해져서 검색해 보았다.
객체란 클래스를 통해서 생성할 수 있는 실체를 의미하며, 인스턴스는 실제로 구현된 실체.
아마 저 문장을 보면???? 이런 물음표가 떠오를 것이다. 확실히 그러해서 아래 링크에서 설명해 준 예제를 들어보면 좀 더 확 와닿는다.
클래스는 붕어빵 틀
객체는 붕어빵
인스턴스는 팥 붕어빵 혹은 슈크림 붕어빵객체는 붕어빵 틀로 만들 수 있는 실체(붕어빵)이며, 실제 클래스 내의 변수에 넣는 값(팥, 슈크림)에 따라서 다른 게 만들어지는 팥 붕어빵 혹은 슈크림 붕어빵은 인스턴스인 것이다.
체인지 인터레스트 : 네이버 블로그prototype & Object.getPrototypeOf
자바스크립트 프로토 타입 기반의 언어로 불렸으며 (클래스가 나오기 전), 모든 객체는 생성되는 순간 그 원형이 되는 프로토 타입을 복사해서 생성이 되었음
예를 들어, string 변수를 선언하게 되면 아무것도 없이 스트링 달랑 하나 있는 변수 선언이 아니라 이미 string이라는 객체가 가지고 있는 함수들을 내장한 채로 생성이 된다. 그렇기 때문에 indexOf 같은 스트링 함수를 아무런 정의 없이 쓸 수 있는 것이다.
let myString = 'This is my string.'; Object.getPrototypeOf(myString) myString.__proto__
이 개념은 아마 다른 언어에서도 비슷하다고 볼 수 있을 것 같다. c#도 클래스를 만들고 그 위로위로 올라가 보면 결국 Object가 있듯이.
클래스가 나오기 전에는 이 프로토타입에 수정을 가해 (함수나 변수를 추가하는 방식으로) 마치 클래스처럼 구현을 했었다.
실제로 위에서 배운 클래스를 생성하고 typeof를 통해 보면 class가 아닌 function이라는 타입으로 나오는데 prototype을 잘 wrapping 해서 만들었기 때문이 아닐까 싶다.function Person() {} Person.prototype.read = function () {console.log("read");} let a = new Person(); let b = new Person(); a.read(); // "read" b.read(); // "read"
ECMAScript 2015부터는 Object.getPrototypeOf(obj) 함수를 통해 객체의 프로토타입 객체에 바로 접근할 수 있게 됨
Object prototypes - Web 개발 학습하기 | MDN
JavaScript : 프로토타입(prototype) 이해prototype & __proto__
Function.prototype이라는 얘기가 나와서 찾다 보니 prototype과 proto가 다르다는 사실을 알게 되었다.
위에서 설명한 prototype은 정확히 말하면 함수의 prototype이라고 볼 수 있을 것 같다.
아래 스택 오버플로우 답변을 보면 명확한데, proto는 실제 사용 되는 객체이고, prototype은 그 객체를 new를 통해서 만들기 위한 객체라고 되어 있다. 오늘 학습한 인스턴스와 객체 정도의 관계라고 생각해도 될 것 같다.proto VS. prototype in JavaScript - Stack Overflow
__proto__ is the actual object that is used in the lookup chain to resolve methods, etc. prototype is the object that is used to build __proto__ when you create an object with new:좀 더 이해하기 위해 아래 예제로 확인해 보았다
function Person() {} let p = new Person(); p.__proto__ // {read: ƒ, constructor: ƒ} Person.prototype // {read: ƒ, constructor: ƒ} p.prototype //undefined let p2 = new Person(); p2.__proto__.s = function () {console.log("hehe"); p.s(); // "hehe"; p2.s(); // "hehe";
객체에 있는 __proto__는 그럼 Person의 prototype과 다른 것인가?
같다.
아래 그림을 보면 같은 것을 가리키고 있으며, 그래서 만약 __proto__를 통해 프로토타입을 수정할 경우, 해당 프로토타입을 생성된 모든 객체에 동일하게 수정된 내용이 반영된다.'Web > 자바스크립트' 카테고리의 다른 글
새로운 객체 기능 (0) 2023.04.15 Day 5. Are they the "same"? [6kyu] (0) 2023.03.05 화살표 함수 (0) 2023.02.11 let 과 const (2) 2023.02.06