From 8b05ceeaf32fb6564ce7eed6f9c469a552dcc8de Mon Sep 17 00:00:00 2001 From: applevalley Date: Tue, 5 Sep 2023 18:43:18 +0900 Subject: [PATCH 1/2] =?UTF-8?q?7=EC=9E=A5=20=ED=81=B4=EB=9E=98=EC=8A=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../\353\202\250\355\230\204\354\244\200.md" | 241 ++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 "7_\355\201\264\353\236\230\354\212\244/\353\202\250\355\230\204\354\244\200.md" diff --git "a/7_\355\201\264\353\236\230\354\212\244/\353\202\250\355\230\204\354\244\200.md" "b/7_\355\201\264\353\236\230\354\212\244/\353\202\250\355\230\204\354\244\200.md" new file mode 100644 index 0000000..5af00b1 --- /dev/null +++ "b/7_\355\201\264\353\236\230\354\212\244/\353\202\250\355\230\204\354\244\200.md" @@ -0,0 +1,241 @@ +# 7장 클래스 + +- 자바스크립트는, 프로토타입 기반 언어 + - 따라서 상속의 개념이 존재하지 않았다! + - ES6 이후로는, 클래스 문법이 추가되었음 + - 다만 클래스에서도 일정 부분은 프로토타입을 활용한다. + + + +## 클래스, 그리고 인스턴스 + +- 어떠한 범주 안에는, 다양한 하위 분류들이 있다. + + - 음식이라는 커다란 범주 안에는 고기, 과일등의 하위 분류가 있다. + + - 고기라는 하위 분류 안에는 또 다시 돼지, 소, 닭 등의 하위 분류가 있다. + + - 돼지고기라는 하위 분류 안에는 다시 안심, 등심, 앞다리 등의 분류가 있다.. + + ![image-20230904113112893](C:\Users\Rin\AppData\Roaming\Typora\typora-user-images\image-20230904113112893.png) + + - 돼지고기라는 하위 분류는 실존하는 개념이지만, 음식, 고기라는 상위 분류는 추상적인 개념이다! + +- 이를 통해, 음식이나 고기와 같은 상위 분류는 모두 집단, 다시 말해 **클래스**이다. + - **하위 개념은 상위 개념을 포함하면서, 더 구체적인 개념을 가진다.** + - 클래스는 하위 개념으로 내려갈수록, **상위 클래스의 속성을 상속하며 더 구체적인 요건(혹은 자신만의 특징)을 가지게 된다!** +- 어떤 클래스의 속성을 가지는 **실존하는 개체를 인스턴스라고 부른다.** + - 현실에서는 개체가 이미 실존하는 상태에서 이를 구분짓기 위해 클래스의 개념을 사용한다. + - 그러나 컴퓨터는 이러한 구분을 알지 못하기에, **사용자가 직접 여러 클래스를 정의해야 한다!** + - 그를 바탕으로 인스턴스를 만들 때 비로소 만들어진 개체가 특정 클래스의 속성을 가지게 된다. + - 현실과 마찬가지로 `공통 요소를 지니는 집단을 분류하기 위한 개념`으로 클래스를 정의한다는 부분에선 유사한 점이 있지만, **이미 존재하는 인스턴스들의 공통점을 찾아 클래스를 정의하는 현실**과 다르게, 프로그래밍 언어상에서는 **클래스가 먼저 정의되어야만 그로부터 공통적인 요소를 지니는 인스턴스를 생성할 수 있다**는 차이점이 있는 것! +- **한 인스턴스는, 하나의 클래스만을 바탕으로 만들어진다.** + - 다중상속의 지원 유무와 무관하게, 인스턴스 생성시 호출 가능한 클래스는 오직 하나이다! + + + +## 자바스크립트에서의 클래스 + +- 자바스크립트는 프로토타입 기반 언어이지만.. + - 클래스라는 관점에서 접근할 수 있는 부분도 있다. + - 생성자 함수 X를 new 키워드와 함께 호출해 인스턴스를 생성하는 프로토타입에서, + - X를 일종의 클래스라고 가정했을 때, 그를 통해 만들어지는 인스턴스에는 X의 prototype 객체 내부 요소들이 인스턴스에 상속된다 볼 수 있다. + - 실제로는 상속보다는 **프로토타입 체이닝**을 통해 `__proto__` 객체가 생성자 함수의 prototype 객체를 참조하고 있는 것이지만, 클래스를 통한 상속과 유사하게 동작한다고 보아도 무방하다. + - 생성자 함수 X 내부의 프로퍼티 중 prototype 객체를 제외한 나머지는 인스턴스에 상속되지 않는다! + + + +### 스태틱 멤버, 인스턴스 멤버 + +- 이들을 나누는 기준은 **인스턴스에 상속되는지(인스턴스가 참조하는지) 여부이다! + + ```javascript + // 생성자 함수 + const Square = function (width, height) { + this.width = width; + this.height = height; + }; + + // 프로토타입(인스턴스) 메서드 + // 생성자 함수의 prototype 객체에 정의된 메서드이지만, + // 인스턴스의 __proto__객체에 의해 참조되기 때문에, 인스턴스에 상속(참조)되는 메서드이다! + Square.prototype.getArea = function () { + return this.width * this.height; + }; + + // 스태틱 메서드 + // 인스턴스에서는 해당 메서드에 접근할 수 없다. + Square.isSquare = function (instance) { + return instance instanceof Square && instance.width === instance.height; + }; + + const sq1 = new Square(5, 5); + + console.log(sq1.getArea()); // 25 + console.log(sq1.isSquare(sq1)); // 인스턴스에서 접근 시도하면, TypeError 발생 + console.log(Square.isSquare(sq1)); // true + ``` + + - 인스턴스에서 스태틱 메서드 isSquare에 접근할 수 없는 이유는 무엇일까? + - 우선, 인스턴스에서 스태틱 메서드 isSquare에 접근하기 위해 인스턴스 sq1 내에서 해당 메서드를 찾는다. + - 찾지 못한 경우, 프로토타입 체이닝 과정을 따라 `sq1.__proto__`에서 다시 해당 메서드를 찾는다. + - 그럼에도 찾지 못한 경우, 프로토타입 체인을 거슬러 올라 마지막으로 `Object.prototype`에 까지 접근해서 해당 메서드를 찾는다. + - 여기에서도 찾지 못한다면, `Uncaught TypeError: ~~ is not a function` 과 같은 오류가 발생한다. + - 이처럼 인스턴스에서 직접 접근할 수 없는 메서드를 **스태틱 메서드**라고 한다! + - 이 경우, 클래스는 추상적인 개념이 아닌 this에 바인딩되는 하나의 개체가 된다. + + + +## 클래스 상속 + +- 클래스 개념이 본격적으로 등장하기 전인 ES5까지는... + + - 클래스 상속을 구현했다는 말은, + - 기본적으로 **잘 연결된 프로토타입 체이닝**을 의미했다. + - 하지만, 상위 클래스와 하위 클래스간의 관계를 완벽히 구현한 것은 아니다! + + ```javascript + const Students = function () { + const args = Array.prototype.slice.call(arguments); + for (let i = 0; i < args.length; i++) { + this[i] = args[i]; + } + this.length = args.length; + }; + + Students.prototype = []; + const g = new Students("김철수", "홍길동"); + + console.log(g); // Array { '0': '김철수', '1': '홍길동', length: 2 } + + delete g.length; + console.log(g); // Array { '0': '김철수', '1': '홍길동' } + + g.push("이순신"); + console.log(g); // Array { '0': '이순신', '1': '홍길동', length: 1 } + ``` + + - length 프로퍼티를 삭제하고, 새로운 값을 배열에 넣었더니 0번째 인덱스의 값이 변경되었다! + - 생성자 함수 Students의 prototype이 빈 배열을 참조하는 것이 원인이다. + - 인스턴스 g에서 length 프로퍼티를 삭제한 이후, 배열에 요소를 추가할 때 JS 엔진이 g.length를 읽고자 하는데, g에는 length 프로퍼티가 없기에(delete를 통해 제거했기에) 프로토타입 체이닝을 통해 Students.prototype.length(`g.__proto__.length`)를 읽어온 것이다. + - 생성자 함수의 prototype은 빈 배열이기 때문에, 여기에 새롭게 값을 추가하고, 프로토타입에서의 length를 1 추가시킨 것이다. + - 만약 생성자 함수의 prototype이 n개의 요소를 가지고 있는 배열을 가리키는 상황이었다면, 위의 예시에서 `g.push("이순신")` 명령이 수행되면, 이순신이라는 값은 배열의 n번째 인덱스에 들어가고, 배열의 length 값은 n + 1이 된다. + - 만약 프로토타입 체이닝을 통한 상속의 구현이 일반적인 부모-자식 관계를 완벽히 구현했다면, 생성자 함수 Students의 특징을 인스턴스 g에서도 동일하게 가지고 있어야 한다. + - 이처럼, 클래스가 구체적인 데이터를 가져버리고, 클래스에 있는 값이 인스턴스의 동작에 영향을 주는 경우가 발생하면, 클래스의 추상성을 해치게 된다. + + + +### 클래스가(프로토타입이) 구체적인 데이터를 지니지 않게 하려면? + +- 가장 쉬운 방법은 인스턴스 생성 이후 새로운 프로퍼티를 추가할 수 없게 하는 것! + + - 인스턴스가 생성되면, 생성자 함수의 prototype에 정의된 프로퍼티를 삭제한다. + - prototype에 정의된 프로퍼티가 많다면, 삭제하는 과정이 비효율적일 수 있다. + - 그 후, `Object.freeze()` 메서드를 통해 객체를 동결시켜 더 이상 변경될 수 없게(새로운 프로퍼티를 추가할 수 없게) 만든다! + - 동결된 객체는 새로운 속성을 추가하거나, 존재하는 속성을 제거하는 것을 방지한다. + - 동결된 객체는 그 프로토타입이 변경되는 것도 방지한다. + +- 다음 방법은 하위 클래스의 prototype에 직접 상위 클래스의 인스턴스를 할당하는 대신, 아무런 프로퍼티를 생성하지 않는 빈 생성자 함수를 하나 더 만들어 그 prototype이 상위 클래스의 prototype을 바라보게 한 다음, 하위 클래스의 prototype에는 빈 생성자 함수의 인스턴스를 할당하는 것이다! + + ```javascript + const Rectangle = function (width, height) { + this.width = width; + this.height = height; + }; + + Rectangle.prototype.getArea = function () { + return this.width * this.height; + }; + + const Square = function (width) { + Rectangle.call(this, width, width); + }; + + const Bridge = function () {}; + + Bridge.prototype = Rectangle.prototype; + Square.prototype = new Bridge(); + Object.freeze(Square.prototype); + + console.log(Bridge.prototype); // { getArea: [Function (anonymous)] } + console.log(Square.prototype); // Rectangle {} + ``` + + - Bridge라는 빈 생성자 함수를 만든다. + - Bridge.prototype이 Rectangle.prototype을 참조하게 한다. + - 마지막으로 Square.prototype에 Bridge의 인스턴스를 할당한다. + - 그 후 Square.prototype을 동일하게 freeze() 메서드로 동결한다! + - 객체가 동결되었기 때문에, 이제 Square.prototype에 프로퍼티를 생성하거나, 삭제하려는 시도는 무시된다. + + ```javascript + Bridge.prototype = Rectangle.prototype; + Square.prototype = new Bridge(); + Object.freeze(Square.prototype); + + // 새로운 프로퍼티 생성 시도 + Square.prototype.introduce = function () { + return "hello"; + }; + // 기존 프로퍼티 삭제 시도 + delete Square.prototype; + + console.log(Square.prototype); // Rectangle {} + ``` + + + +## ES6의 클래스, 그리고 클래스 상속 + +- ES6에 들어서, 클래스 문법이 도입되었음! + + - 클래스 내 메서드와 메서드 사이에는, 콤마로 구분하지 않는다. + + ```javascript + const cl = class { + // `constructor`라는 이름에서 알 수 있듯, 생성자 함수의 역할을 수행한다. + constructor(name) { + this.name = name; + } + // static 키워드를 통해, 해당 메서드가 스태틱 메서드임을 알림 + // ES6 이전 프로토타입에서는, 생성자 함수 안에 메서드를 정의해 인스턴스에서 직접 호출할 수 없었던 것 + static staticMethod() { + return this.name + " is static method"; + } + // prototype 객체 내부에 할당되는 메서드 + // ES6 이전 프로토타입에서는, prototype에 메서드를 정의해 인스턴스에서 프로토타입 체이닝을 통해 접근할 수 있었던 것 + method() { + return this.name + " is method"; + } + }; + + const clInstance = new cl("pizza"); + console.log(cl.staticMethod()); // cl is static method + console.log(clInstance.method()); // pizza is method + ``` + +- `super()`키워드를 통해, 상위 클래스의 생성자 함수를 호출할 수 있다. + + ```javascript + const Rectangle = class { + constructor(width, height) { + this.width = width; + this.height = height; + } + getArea() { + return this.width * this.height; + } + }; + + // extends 키워드를 통해 상속 관계를 나타낸다. + const Square = class extends Rectangle { + constructor(width) { + super(width, width); // 상위 클래스의 생성자 함수를 실행한다! + } + getArea() { + console.log(this) // Square { width: 3, height: 3 } + console.log("size is :", super.getArea()); + } + }; + ``` + + \ No newline at end of file From 739bf95c8394d22c6e329d8af4c76064044472ac Mon Sep 17 00:00:00 2001 From: applevalley Date: Tue, 5 Sep 2023 18:46:57 +0900 Subject: [PATCH 2/2] image update --- .../\353\202\250\355\230\204\354\244\200.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/7_\355\201\264\353\236\230\354\212\244/\353\202\250\355\230\204\354\244\200.md" "b/7_\355\201\264\353\236\230\354\212\244/\353\202\250\355\230\204\354\244\200.md" index 5af00b1..677f207 100644 --- "a/7_\355\201\264\353\236\230\354\212\244/\353\202\250\355\230\204\354\244\200.md" +++ "b/7_\355\201\264\353\236\230\354\212\244/\353\202\250\355\230\204\354\244\200.md" @@ -17,7 +17,7 @@ - 돼지고기라는 하위 분류 안에는 다시 안심, 등심, 앞다리 등의 분류가 있다.. - ![image-20230904113112893](C:\Users\Rin\AppData\Roaming\Typora\typora-user-images\image-20230904113112893.png) + ![class image](https://user-images.githubusercontent.com/62874043/265658381-0e535e33-3c16-42e5-b32b-97447eda488b.png) - 돼지고기라는 하위 분류는 실존하는 개념이지만, 음식, 고기라는 상위 분류는 추상적인 개념이다!