diff --git "a/7\354\236\245/7\354\236\245_\354\260\275\355\227\214.md" "b/7\354\236\245/7\354\236\245_\354\260\275\355\227\214.md" index 6fee08a..39f2339 100644 --- "a/7\354\236\245/7\354\236\245_\354\260\275\355\227\214.md" +++ "b/7\354\236\245/7\354\236\245_\354\260\275\355\227\214.md" @@ -1 +1,278 @@ -### 벌써 7장이네요! 마지막까지 화이팅!😤 +# Class + +## 클래스와 인스턴스 + +> 클래스: 공통요소를 지니는 집단을 분류하기 위한 개념 +> 인스턴스: 어떤 클래스의 속성을 지니는 실존하는 개체 + +현실: 인스턴스의 공통점으로 클래스 생성 +프로그래밍: 클래스를 바탕으로 인스턴스 생성 + +한 인스턴스는 하나의 클래스만을 바탕으로 만들어진다. 어떤 인스턴스가 다양한 클래스에 속할 수는 있지만 결국 인스턴스를 생성할 때 호출할 수 있는 클래스는 하나이다. + +## 자바스크립트의 클래스 + +생성자 함수 Array + new 연산자 => 인스턴스 생성 +이때 Array를 일종의 클래스라고 하면 Array의 prototype 객체 내부 요소들이 인스턴스에 상속된다고 볼 수 있다.( 프로토타입 체이닝에 의한 참조지만 동일하게 동작 ) +하지만 Array 내부 프로퍼티 중에서 prototype 프로퍼티를 제외한 나머지는 인스턴스에 상속되지 않는다. + +인스턴스에 상속되는지에 따라 스태틱 멤버와 프로토타입 메서드로 나뉜다. + +- 스태틱 멤버: 인스턴스에서 직접 접근할 수 없는 메서드, 생성자 함수를 this로 해야만 호출 + +- 프로토타입 메서드(인스턴스 메서드): 인스턴스에서 직접 호출할 수 있는 메서드 + +```js +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 instanceof Rectangle && instance.width > 0 && instance.height > 0 + ); +}; +``` + +일반적인 사용방식 => 구체적인 인스턴스가 사용할 메서드를 정의한 틀의 역할=> 추상적 +클래스 자체를 this로 해서 직접 접근해야하는 스태틱 메서드를 호출할 때의 클래스=> 그 자체가 하나의 개체 + +## 클래스 상속 + +프로토타입 체이닝을 잘 연결하여 구현 but 문제점 존재 + +```js +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 = []; //문제점 2 +var g = new Grade(100, 80); +``` + +### 문제점 + +1. 프로퍼티를 삭제할 수도 있다. + + length라는 프로퍼티를 삭제할 수 있다. 이는 인스턴스가 기본적으로 배열의 메서드를 상속받지만 기본적으로는 일반 객체의 성질을 그대로 지니므로 삭제가 가능해서 문제가 된다. + + g.length를 삭제=> g.length 부름=> 삭제해서 없음=> `g.__proto__`까지 가서 length가져옴 + +2. Grade.prototype에 빈 배열을 참조 + + 만약에 값이 존재했다면 `Grade.__proto__.length` 가 달라져 다르게 동작하게 된다. + +=> 클래스에 있는 값이 인스턴스의 동작에 영향을 주면 안됨. + +### 여전히 남은 문제 + +1. 클래스에 구체적인 데이터를 가지고 있어 인스턴스의 값을 지우면 프로토타입 체이닝으로 잘못된 결과 나옴. +2. .constructor가 다른 곳을 바라보고있음(프로토타입 체이닝을따라서) + +이처럼 하위 클래스로 삼을 생성자 함수의 prototype에 상위 클래스의 인스턴스를 부여하는 것만으로도 기본적인 메서드 상속은 가능하지만 다양한 문제가 발생할 여지가 있어 구조적으로 안정성이 떨어진다. + +### 클래스가 구체적인 데이터를 지니지 않게 하는 방법 + +1. 일단 만들고 프로퍼티들을 일일이 지우고, 새로 추가 못하게 하기(freeze로 동결) + +2. SubClass의 prototype에 직접 SuperClass의 인스턴스를 할당하는 대신 아무런 프로퍼티를 생성하지 않고, 생성자 함수를 하나 더 만들어 그 prototype이 SuperClass의 prototype을 바라보게끔 한 다음 SubClass의 prototype에는 Bridge의 인스턴스를 할당(빈 함수 활용) + +3. Object.create활용 + +=> 결국 SubClass.prototype의 `__proto__`가 SuperClass.prototype을 참고하고, SubClass.prototype에는 불필요한 인스턴스 프로퍼티가 남아있지 않게 한다. + +### Constructor 복구 + +기본적인 상속에는 성공하지만 SubClass 인스턴스의 constructor가 여전히 Super Class를 가리키는 상태이다. + +SubClass의 constructor가 존재하지 않고, prototype에도 존재 하지 않는다. 따라서 체인상 가장 먼저있는 SuperClass의 constructor가 출력된다. + +SubClass.prototype.constructor가 원래의 SubClass를 바라보도록 해주면 된다. + +```js +SubClass.prototype.constructor = SubClass; +``` + +### 상위 클래스에의 접근수단 제공 + +하위 클래스의 메서드에서 상위 클래스의 메서드 실행 결과를 바탕으로 추가적인 작업을 수행하고 싶을 때가 있다. 이때 매번 `SuperClass.prototype.method.apply(this,arg)` 로 접근하는 것은 불편하다. + +하위클래스에서 상위 클래스의 프로토타입 메서드에 접근할 방법을 제공 => Super + +```js +SubClass.prototype.super = function (propName) { + var self = this; + if (!propName) + return function () { + SuperClass.apply(self, arguments); + }; + var prop = superClass.prototype[propName]; + if (typeof prop !== "function") return prop; + return function () { + return prop.apply(self, arguments); + }; +}; +``` + +인자가 비었을 경우 SuperClass 생성자함수에 접근하는 것으로 간주 +this가 달라지지 않게 클로저 사용 +prototype 내부의 propName에 해당하는 값이 함수가 아니면 해당 값을 그대로 반환, 함수인 경우 클로저를 활용해 메서드에 접근하는 것으로 여김 + +SuperClass의 생성자 함수에 접근하고자 할때 this.super(), +SuperClass의 프로토타입 메서드에 접근하려면 this.super(propName)으로 사용 + + + +--- + +## 2-depth + +Javascript는 class를 특별한 타입의 function으로 취급한다. +클래스와 함수의 가장 큰 차이점은 호이스팅이다. 함수와 다르게 Javascript 클래스는 접근하기 전에 선언되어져야 한다. +이미 존재하는 프로토타입 상속을 대신해 줄 class라는 키워드를 제공한다. + +### Constructor method + +Constructor 메서드는 Javascript의 클래스 객체를 초기화시켜주는 특별한 메서드이다. +Javascript에서 클래스가 선언되면 자동으로 불려진다. +하나의 클래스에 하나의 생성자만 있을 수 있다. 정의되지 않은 경우 JavaScript는 해당 클래스에 매개변수가 0인 빈 생성자를 추가합니다. + +### Static method + +정적 메서드는 클래스의 인스턴스가 아니라 클래스 자체에서 호출되는 메서드이다. 정적 메서드는 클래스의 인스턴스가 아니지만 기능면에서 클래스와 관련이 있다. + +```js +class Math { + static add(number1, number2) { + return number1 + number2; + } +} + +let additionResult = Math.add(2, 3); +``` + +### ES6 Class의 문제점 + +클래스는 JavaScript에서 특정 작업을 수행하는 간단한 방법에 대한 보다 복잡한 솔루션을 제공할 수 있다. 객체 지향 프로그래밍 언어에 대한 배경 지식이 있는 사람들은 필요하지 않은 경우에도 클래스로 간단한 작업을 수행하기로 결정할 수 있다. +일부 개발자는 클래스를 추가하면 JavaScript의 독창성이 사라지고 프로토타입을 사용하는 것이 클래스가 필요한 작업을 수행하는 더 유연한 방법이라고 주장할 수 있다. 다른 객체 지향 프로그래밍 언어의 클래스와 달리 JavaScript는 개인 변수 선언과 같은 기본 클래스 기능을 제공하지 않기 때문. +ECMASCRIPT 2020은 이러한 문제 중 일부를 해결하는 것을 목표로 합니다. + +매년 JavaScript 사용자의 요구를 충족시키기 위해 JavaScript에 추가 및 수정이 이루어집니다. 최신 수정 사항은 ECMASCRIPT 2020에 있다. 2020년 클래스에 추가된 일부 항목에는 개인 클래스 변수와 정적 필드가 포함된다. + +#### private 클래스 변수 + +변수 선언이 많은 대규모 코드베이스에서 작업할 때 클래스 내에서만 액세스할 수 있는 변수가 필요할 수 있다. 개인 클래스 변수는 이 문제를 해결한다. 변수명 앞에 해시를 추가하면 쉽게 private 변수를 생성할 수 있습니다. + +```js +class ClassWithPrivateField { + #privateField; +} + +class ClassWithPrivateMethod { + #privateMethod() { + return "hello world"; + } +} + +class ClassWithPrivateStaticField { + static #PRIVATE_STATIC_FIELD; +} +``` + +```js +class Detail { + #name = "steven"; + + welcome() { + console.log(this.#message); + } +} +``` + +이전 버전의 JavaScript에서는 새 클래스 인스턴스를 생성하지 않고 'welcome' 메서드에 액세스하려는 시도가 불가능하지만 최신 추가 기능으로 인스턴스 생성 없이 이러한 메서드에 접근할 수 있다. + +```js +const DetailMethod = Detail.welcome(); +``` + +### JavaScript에서 Class를 사용해야할까? + +oop는 소프트웨어 개발의 복잡성을 줄이기 위해 개발되었다. => 이는 궁극적으로 요구사항을 실행으로 변환하는데 도움이 되는 도구이다. +캡슐화, 추상 데이터 유형, 상속 등은 개발자가 프로그램 구조에 대해 추론하기 위한 것. + +```js +class Person { + constructor(name) { + this.name = name; + } + + talk() { + console.log(`${this.name} says hello`); + } +} +``` + +```js +function Person(name) { + this.name = name; +} +Person.prototype.talk = function () { + console.log(`${this.name} says hello`); +}; +``` + +talk함수가 Person 클래스의 encapsulated 메서드가 아니라는 것이다. Person 객체가 상속하는 일반 JavaScript 함수입니다. + +또한 `this` 함수가 정의된 위치가 아니라 함수가 호출되는 방식과 관련이 있다. + +객체가 불렸을 때 this가 어떻게 동작하는지가 관건. + +```js +const Grey = new Person("Grey"); +const mockDomObj = {}; +mockDomObj.onClick = Grey.talk; +mockDomObj.onClick(); // this.name -> undefined! +//mockDomObj에서 name을 찾아서 +``` + +JavaScript에서 클래스가 작동하는 방식은 다른 클래스 기반 언어에서 구현되는 캡슐화와 상당히 다르다. +JavaScript를 조금 아는사람이라면 bind로 this를 특정시켜줄수 있다는 것을 알지만 디자인 표현을 강요하기 위해 우리의 도구로 싸우는 것이 유용한지에 대한 의문이 든다. + +클래스는 객체를 만들기위한 설계도이다. (객체를 만드는 공장같은 역할을 한다.) +이런식으로 이해하면 새로운 방법을 준다. => **factory functions**(object를 리턴해주는 함수) + +```js +const PersonFactory = (name) => ({ + talk() { + console.log(`${name} says Hello`); + }, +}); +``` + +클로저를 통해 this나 bind를 사용하지 않아도 된다. +하지만 이 시점에서 일부 사람들은 클래스가 확장 가능해야 하며, 이러한 팩토리가 쉽게 확장 가능하지 않은 것 같다고 이의를 제기할 수 있다. 하지만 합성과 상속의 이유는 디자인 표현을 개선하기 위함이 아니라 코드 중복을 피하기 위함이라고 생각한다. +==>`extends` 대신 팩토리에서 반환된 객체가 생성되는 방식을 변경 + +```js +const Mammal = { + isVertebrae: true, +}; +const PersonMammalFactory = (name) => + Object.assign({}, Mammal, PersonFactory(name)); +``` + +이런식으로 assign을 사용 +하지만 생성되는 모든 객체에 talk라는 함수가 생성이 된다. 적은 코드에서는 문제가 없지만 코드가 커지게 되면 문제가 생길 수 있고, class와 bind가 이때는 더 적절한 방법일 수가 있다. + +[참고링크](https://blog.logrocket.com/the-bleeding-edge-of-javascript-classes/) +[참고링크](https://medium.com/@vapurrmaid/should-you-use-classes-in-javascript-82f3b3df6195)