diff --git "a/Chapter_19/GeonHo/\354\240\225\353\246\254.md" "b/Chapter_19/GeonHo/19.1~19.7 \354\240\225\353\246\254.md" similarity index 100% rename from "Chapter_19/GeonHo/\354\240\225\353\246\254.md" rename to "Chapter_19/GeonHo/19.1~19.7 \354\240\225\353\246\254.md" diff --git "a/Chapter_19/GeonHo/19.8~19.14 \354\240\225\353\246\254.md" "b/Chapter_19/GeonHo/19.8~19.14 \354\240\225\353\246\254.md" new file mode 100644 index 0000000..668508d --- /dev/null +++ "b/Chapter_19/GeonHo/19.8~19.14 \354\240\225\353\246\254.md" @@ -0,0 +1,183 @@ +## 19.8 오버라이딩과 프로퍼티 섀도잉 + +`오버라이딩`이란?
+ +- 상위 클래스의 메서드를 하위 클래스에서 재정의하는 것을 말한다. + +`프로퍼티 섀도잉`이란?
+ +- 프로토타입 프로퍼티와 같은 이름의 프로퍼티를 인스턴스에 추가하면, 프로토타입 프로퍼티를 덮어쓰는 것이 아닌 인스턴스 프로퍼티로 추가된다. +- 이렇게 프로퍼티를 오버라이딩하여 상속 관계에 의한 프로퍼티가 가려지는 현상이 프로퍼티 섀도잉이다. + +
+ +**인스턴스 등의 하위 객체를 통해 상위 프로토타입의 프로퍼티를 변경 또는 삭제하는 것은 불가능하다.**
+다시 말해, get은 허용되나 set은 허용되지 않는다 + +
+ +## 19.9 프로토타입의 교체 + +프로토타입은 임의의 다른 객체로 변경할 수 있다.
+생성자 함수의 prototype 또는 인스턴스의 **proto** 접근자 프로퍼티를 통해 프로토타입을 교체할 수 있다. + +```js +const Person = (function () { + function Person(name) { + this.name = name; + } + + // Person 생성자 함수의 프로토타입을 아래 객체로 교체하였다. + Person.prototype = { + sayHello() { + console.log(`Hi! My name is ${this.name}`); + }, + }; + + return Person; +})(); + +const me = new Person("Lee"); +``` + +위 코드에서 Person 생성자 함수의 프로토타입을 새로운 객체로 교체했다.
+다만, 위 프로토타입 객체는 constructor 프로퍼티가 없다.
+때문에 인스턴스 me는 생성자 함수가 Person이 아닌 Object다. + +```js +console.log(me.constructor === Person); // false +console.log(me.constructor === Object); // true +``` + +
+ +생성자 함수가 Person을 가르키게하려면 아래와 같이 객체를 수정하면 된다. + +```js +Person.prototype = { + constructor: Person, + sayHello() { + console.log(`Hi! My name is ${this.name}`); + }, +}; +``` + +위와 같이 생성자 함수를 활용한 프로토타입 교체 방식과 인스턴스의 **proto** 접근자 프로퍼티를 활용한 프로토타입 교체 방식은 조금 다르다.
+생성자 함수의 경우, 생성자함수는 생성자 함수.prototype을 가르키지만 인스턴스의 **proto** 접근자 프로퍼티를 사용한 방식은 그렇지 않다.
+ +
+ +## 19.10 instanceof 연산자 + +```js +객체 instanceof 생성자 함수 +``` + +instanceof 연산자는 생성자 함수의 prototype에 바인딩된 객체가 객체의 프로토타입 체인 상에 존재하면 true를 반환한다.
+ +
+ +## 19.11 직접 상속 + +## 19.12 정적 프로퍼티/메서드 + +어떤 생성자 함수 객체가 소유한 프로퍼티/메서드를 뜻한다. + +정적 프로퍼티/메서드는 new로 생성된 인스턴스도 참조할 수 없다.
+왜냐하면 인스턴스로 참조할 수 있는 프로퍼티/메서드는 프로토타입 체인 상에 존재해야하기 때문인데,
+정적 프로퍼티/메서드는 생성자 함수 객체에 바인딩되어 있기 때문이다. + +정적 프로퍼티/메서드와 프로토타입 프로퍼티/메서드의 차이점은
+`생성자함수.fn()` 인지 `생성자함수.prototype.fn()` 로 구분할 수 있다. + +
+ +## 19.13 프로퍼티 존재 확인 + +### in + +in 연산자는 확인 대상 객체 뿐 아니라, 상속받은 모든 프로토타입 프로퍼티를 확인한다. + +```js +const person = { + name: "Lee", + address: "Seoul", +}; + +console.log("name" in person); // true +console.log("address" in person); // true +console.log("age" in person); // false +console.log("toString" in person); // true +// toString은 Object.prototype의 메서드이다. +// person 객체가 Object.prototype을 상속받았으므로 true를 리턴한다. +``` + +### hasOwnProperty + +상속받은 프로퍼티의 경우 false를 리턴, 고유 프로퍼티의 key인 경우에만 true를 리턴한다. + +
+ +## 19.14 프로퍼티 열거 + +### for...in + +for...in 문은 객체의 프로퍼티를 열거할 때 사용한다.
+in 연산자와 마찬가지로 상속받은 프로토타입의 프로퍼티까지 열거한다. + +해서 vsc의 for...in 문의 snippet을 보면 다음과 같이 hasOwnProperty 메서드가 포함되어 있다. + +```js +for (const key in object) { + if (Object.hasOwnProperty.call(object, key)) { + const element = object[key]; + ... + } +} +``` + +다만, Object.prototype의 toString 같은 프로퍼티는 열거하지 않는다.
+ +왜일까?
+이는 프로퍼티의 열거 가능 여부를 판별하는 `내부 슬롯 [[Enumerable]]`이 false이기 때문이다. + +또한, for...in은 열거시 순서를 보장하지 않는다.
+다만, 대부분의 브라우저에서는 순서를 보장하고, *숫자인 프로퍼티 키에 대해서는 정렬*을 한다. + +
+ +객체 자신의 고유 프로퍼티만 열거하기 위해서는 for...in 문 보다는 Object.keys / values / entries 메서드를 사용하는 것이 좋다. + +### Object.keys + +```js +const person = { + name: "Lee", + address: "Seoul", + __proto__: { age: 20 }, +}; + +console.log(Object.keys(person)); // ['name', 'address'] +``` + +객체 자신의 열거 가능한 프로퍼티 키를 **배열**로 반환한다. + +
+ +### Object.values + +```js +console.log(Object.values(person)); // ['Lee', 'Seoul'] +``` + +'' value를 배열로 반환한다. + +
+ +### Object.entries + +```js +console.log(Object.entries(person)); // [['name', 'Lee'], ['address', 'Seoul']] +``` + +'' key, value를 배열로 반환한다. diff --git a/Chapter_23/GeonHo/img/image-1.png b/Chapter_23/GeonHo/img/image-1.png new file mode 100644 index 0000000..39a046f Binary files /dev/null and b/Chapter_23/GeonHo/img/image-1.png differ diff --git a/Chapter_23/GeonHo/img/image-2.png b/Chapter_23/GeonHo/img/image-2.png new file mode 100644 index 0000000..a058c4c Binary files /dev/null and b/Chapter_23/GeonHo/img/image-2.png differ diff --git a/Chapter_23/GeonHo/img/image-3.png b/Chapter_23/GeonHo/img/image-3.png new file mode 100644 index 0000000..dd94144 Binary files /dev/null and b/Chapter_23/GeonHo/img/image-3.png differ diff --git a/Chapter_23/GeonHo/img/image-4.png b/Chapter_23/GeonHo/img/image-4.png new file mode 100644 index 0000000..1c5271e Binary files /dev/null and b/Chapter_23/GeonHo/img/image-4.png differ diff --git a/Chapter_23/GeonHo/img/image.png b/Chapter_23/GeonHo/img/image.png new file mode 100644 index 0000000..02a0fcf Binary files /dev/null and b/Chapter_23/GeonHo/img/image.png differ diff --git "a/Chapter_23/GeonHo/\354\240\225\353\246\254.md" "b/Chapter_23/GeonHo/\354\240\225\353\246\254.md" new file mode 100644 index 0000000..28ed6ab --- /dev/null +++ "b/Chapter_23/GeonHo/\354\240\225\353\246\254.md" @@ -0,0 +1,83 @@ +# 23장 실행 컨텍스트 + +1. 소스코드의 타입 + - 전역 코드 + - 함수 코드 + - eval 코드 + - 모듈 코드 +2. 소스코드의 평가와 실행 + 모든 소스코드는 실행 전 평과 과정을 거친다. + 즉 JS 엔진은 소스코드를 '평가' 와 '실행' 과정으로 나누어 처리한다. + + '평가'과정에서는 실행 컨텍스트를 생성하고, 변수나 함수 선언문만 실행하여 + 실행 컨텍스트가 관리하는 스코프(렉시컬 환경의 환경 레코드)에 등록한다 + + 평가 과정 이후 '실행'과정이 시작된다. 즉 런타임이 시작된다. + 변수나 함수에 대한 정보는 실행 컨텍스트가 관리하는 스코프에서 가져온다. + 또 변수 값의 변경 등은 다시 스코프에 등록한다. + +3. 실행 컨텍스트의 역할 + + ![alt text](img/image.png) + + 위와 같은 상황에서, 각 코드들을 스코프에 맞게 구분하여 관리하게 위하여 + 스코프, 식별자, 코드 실행 순서 등의 관리가 필요하다. + + 이를 위해 실행 컨텍스트가 존재한다. + + 실행 컨텍스트는 식별자를 등록하고 관리하는 스코프와 코드 실행 순서 관리를 구현한 내부 매커니즘으로, + 모든 코드는 실행 컨텍스트를 통해 실행되고 관리된다. + + 식별자와 스코프는, 실행 컨텍스트의 렉시컬 환경으로 관리하고, + 코드 실행 순서는 실행 컨텍스트 스택으로 관리한다. + +4. 실행 컨텍스트의 스택 + JS 엔진은 먼저 전역 코드를 평가하여 '전역' 실행 컨텍스트를 생성한다. + 그러다 함수가 호출되면, 함수 코드를 평가하여 '함수' 실행 컨텍스트를 생성한다. + 이렇게 생성된 실행 컨텍스트는 스택 자료구조로 관리되는데, 이를 실행 컨텍스트 스택이라고 한다. +5. 렉시컬 환경 + 실행 컨텍스트가 코드 실행 순서를 관리한다면, + 렉시컬 환경은 스코프와 식별자를 관리한다. + ![alt text](img/image-1.png) + + 사진과 같이, 렉시컬 환경은 key | value 형태의 객체 스코프이다. + 실행 컨텍스트는 렉시컬 환경과 변수 환경으로 구성되어있는데, + 일단 간략하게 배우기 위해, 두개를 합쳐 렉시컬 환경이라 하겠다. + + 렉시컬 환경은 두 개의 컴포넌트로 구성된다. + + ![alt text](img/image-2.png) + + 1. 환경레코드 + 스코프에 포함된 식별자와 바인딩 된 값을 관리한다. + + 2. 외부 렉시컬 환경에 대한 참조 + 상위 스코프, 즉 해당 실행 컨텍스트를 생성한 소스코드를 포함하는 렉시컬 환경을 가르킨다. + +6. 실행 컨텍스트의 생성과 식별자 검색 과정 + + ![alt text](img/image-3.png) + + 위 코드의 실행 컨텍스트와 렉시컬 환경에 대해 아라보자. + + ![alt text](img/image-4.png) + + 우선 전역 먼저. + 전역 실행 환경에서 전역 렉시컬 환경을 생성하고 전역 렉시컬 환경을 바인딩. + 전역 환경 레코드는 var같은 객체 환경 레코드와 const, let 같은 선언 환경 레코드를 분리해서 바인딩한다. + var의 경우 변수의 선언 단계와 초기화 단계가 동시에 진행되다보니 전역 객체에 바로 undefined가 할당된다. + + 허나 let, const는 선언 단계 후 초기화 단계가 분리되어 실행되기에, + 런타임이 변수 선언문에 닿아 값이 할당되기 전 까지, 변수에 접근할 수 없는 일시적 사각지대에 빠지게 된다. + 때문에 y에 가 바인딩되어있다. + + this도 전역 환경 레코드 내부 슬롯에 바인딩 된다. + + 외부 렉시컬 환경 참조에는 null 이 할당된다. 왜냐면 전역이라 참조할 곳이 없기 때문. + + 이렇게 전역 실행 컨텍스트와 렉시컬 환경에 대하여 알아보았다. + 마지막으로 전역 코드가 실행되면 x, y에 값이 할당되고, foo 함수가 호출된다. + + 소스코드를 보면, 동일한 이름의 식별자가 각기 다른 스코프에 존재할 수 있다. + 이럴때 JS는 식별자 결정을 위해 실행중인 실행 컨텍스트에서부터 식별자를 검색하기 시작한다. + 이때 식별자를 못찾으면, 외부 렉시컬 환경이 가르키는 상위 스코프로 이동하여 식별자를 검색한다. diff --git "a/Chapter_24/GeonHo/\354\240\225\353\246\254.md" "b/Chapter_24/GeonHo/\354\240\225\353\246\254.md" new file mode 100644 index 0000000..3d46346 --- /dev/null +++ "b/Chapter_24/GeonHo/\354\240\225\353\246\254.md" @@ -0,0 +1,229 @@ +# 클로저 + +### 24.1 렉시컬 스코프 + +자바스크립트 엔진은 함수를 어디서 호출하는지가 아니라 어디에 정의 했는지에 따라 상위 스코프가 결정된다. + +그리고 객체가 생성되는 시점에 상위 스코프가 결정된다.([[Environment]] 내부 슬롯에 저장)
+이후 함수가 호출되고 함수 몸체 코드가 평가되는 시점에 외부 렉시컬 환경에 대한 참조에 객체가 생성될때 저장한 상위 스코프를 참조값을 저장한다. + +
+ +### 24.2 함수 객체의 내부 슬롯[[Environment]] + +함수 정의가 평가되어 함수 객체를 생성할 때 자신이 정의된 환경(위치)에 의해 결정된 상위 스코프의 참조를 함수 객체 자신의 내부 슬롯[[Environment]]에 저장한다.
+[[Environment]]내부 슬롯에 저장된 상위 스코프의 참조는 현재 실행 중인 실행 컨텍스트의 렉시컬 환경을 가리킨다. + +함수 객체의 [[Environment]] 내부 슬롯에 저장된 상위 스코프는 자신이 호출되었을 때 생성될 함수 렉시컬 환경의 외부 렉시컬 환경에 대한 참조에 저장될 참조 값이다. + +```js +const x = 1; + +function foo() { + const x = 10; + + // 상위 스코프는 함수 정의 환경(위치)에 따라 결정된다. + // 함수 호출 위치와 상위 스코프는 아무런 관계가 없다. + bar(); +} + +// 함수 bar는 자신의 상위 스코프, 즉 전역 렉시컬 환경을 [[Environment]]에 저장하여 기억한다. +function bar() { + console.log(x); +} + +foo(); // 1 +bar(); // 1 +``` + +### 24.3 클로저와 렉시컬 환경 + +외부 함수보다 중첩 함수가 더 오래 유지되는 경우, 중첩 함수는 이미 생명 주기가 종료한 외부 함수의 변수를 참조할 수 있다.
+이러한 중첩 함수를 클로저(closure)라고 부른다. + +JS의 모든 함수는 자신의 상위 스코프를 기억한다고 했다.
+모든 함수가 기억하는 상위 스코프는 함수를 어디서 호출하든 상관없이 유지된다. + +따라서 함수를 어디서 호출하든 상관없이 함수는 언제나 자신이 기억하는 상위 스코프의 식별자를 참조할 수 있으며 식별자에 바인딩된 값을 변경할 수도 있다. + +### 24.4 클로저의 활용 + +클로저는 상태state를 안전하게 변경하고 유지하기 위해 사용된다. + +상태를 안전하게 은닉(information hiding)하고 특정 함수에게만 상태 변경을 허용하기 위해 사용된다. + +1.전역 변수로 관리 + +```js +// 카운트 상태 변수 +let num = 0; + +// 카운트 상태 변경 함수 +const increase = function () { + // 카운트 상태를 1만큼 증가 시킨다. + return ++num; +}; + +console.log(increase()); // 1 +console.log(increase()); // 2 +console.log(increase()); // 3 +``` + +위 코드는 오류의 가능성이 있어 좋지 않은 코드다.
+num 전역 변수에 언제든지 누구나 접근하여 변경이 가능하기 때문이다. + +2.함수의 지역 변수로 관리 + +```js +// 카운트 상태 변경 함수 +const increase = function () { + // 카운트 상태 변수 + let num = 0; + + // 카운트 상태를 1만큼 증가 시킨다. + return ++num; +}; + +// 이전 상태를 유지하지 못한다. +console.log(increase()); // 1 +console.log(increase()); // 1 +console.log(increase()); // 1 +``` + +지역 변수로 활용하여 첫 번째 예제보다는 좋아 보이지만 함수가 호출될 때마다 지역 변수num은 다시 선언되어 0으로 초기화되기 때문에 상태를 유지하지 못한다. + +3.클로저 활용 + +```js +// 카운트 상태 변경 함수 +const increase = (function () { + // 카운트 상태 변수 + let num = 0; + + // 클로저 + return function () { + // 카운트 상태를 1만큼 증가 시킨다. + return ++num; + }; +})(); + +console.log(increase()); // 1 +console.log(increase()); // 2 +console.log(increase()); // 3 +``` + +위 코드는 클로저다. + +클로저로 num의 상태state를 은닉하여 의도치 않게 변경되지 않도록 안전하게 관리하고
+특정 함수에게만 상태 변경을 허용하여 상태를 안전하게 변경하고 유지한다. + +4.감소 기능 추가(클로저) + +```js +const counter = (function () { + // 카운트 상태 변수 + let num = 0; + + // 클로저인 메서드를 갖는 객체를 반환한다. + // 객체 리터럴은 스코프를 만들지 않는다. + // 따라서 아래 메서드들의 상위 스코프는 즉시 실행 함수의 렉시컬 환경이다. + return { + // num: 0, // 프로퍼티는 public하므로 은닉되지 않는다. + increase() { + return ++num; + }, + decrease() { + return num > 0 ? --num : 0; + }, + }; +})(); + +console.log(counter.increase()); // 1 +console.log(counter.increase()); // 2 + +console.log(counter.decrease()); // 1 +console.log(counter.decrease()); // 0 +``` + +5.생성자 함수로 표현(클로저) +increase, decrease 메서드는 프로토타입 메서드이다. +이 메서드들이 평가되어 함수 객체가 생성될 때 실행 중인 실행 컨텍스트는 즉시 실행 함수의 실행 컨텍스트이다. 따라서 increase, decrease메서드는 즉시 실행 함수의 실행 컨텍스트의 렉시컬 환경을 기억하는 클로저 이다. + +```js +const Counter = (function () { + // ① 카운트 상태 변수 + let num = 0; + + function Counter() { + // this.num = 0; // ② 프로퍼티는 public하므로 은닉되지 않는다. + } + + Counter.prototype.increase = function () { + return ++num; + }; + + Counter.prototype.decrease = function () { + return num > 0 ? --num : 0; + }; + + return Counter; +})(); + +const counter = new Counter(); + +console.log(counter.increase()); // 1 +console.log(counter.increase()); // 2 + +console.log(counter.decrease()); // 1 +console.log(counter.decrease()); // 0 +``` + +6.고차 함수 활용한 클로저(클로저) + +```js +// 함수를 인수로 전달받고 함수를 반환하는 고차 함수 +// 이 함수는 카운트 상태를 유지하기 위한 자유 변수 counter를 기억하는 클로저를 반환한다. +function makeCounter(predicate) { + // 카운트 상태를 유지하기 위한 자유 변수 + let counter = 0; + + // 클로저를 반환 + return function () { + // 인수로 전달 받은 보조 함수에 상태 변경을 위임한다. + counter = predicate(counter); + return counter; + }; +} + +// 보조 함수 +function increase(n) { + return ++n; +} + +// 보조 함수 +function decrease(n) { + return --n; +} + +// 함수로 함수를 생성한다. +// makeCounter 함수는 보조 함수를 인수로 전달받아 함수를 반환한다 +const increaser = makeCounter(increase); // ① +console.log(increaser()); // 1 +console.log(increaser()); // 2 + +// increaser 함수와는 별개의 독립된 렉시컬 환경을 갖기 때문에 카운터 상태가 연동하지 않는다. +const decreaser = makeCounter(decrease); // ② +console.log(decreaser()); // -1 +console.log(decreaser()); // -2 +``` + +### 24.5 캡슐화와 정보 은닉 + +캡슐화(encapsulation)는 객체의 상태(state)를 나타내는 프로퍼티와 프로퍼티를 참조하고 조작할 수 있는 동작인 메서드를 하나로 묶는 것을 말한다. + +캡슐화는 객체의 특정 프로퍼티나 메서드를 감출 목적으로 사용하기도 하는데 이를 정보 은닉(information hiding)이라 한다. + +정보 은닉은 외부에 공개할 필요가 없는 구현의 일부를 외부에 공개되지 않도록 감추어 적절치 못한 접근으로부터 객체의 상태가 변경되는 것을 방지해 정보를 보호하고, 객체 간의 상호 의존성, 즉 결합도(coupling)를 낮추는 효과가 있다. + +대부분의 객체지향 프로그래밍 언어는 public, private, protected와 같은 접근 제한자(access modifier)를 제공하지만 자바스크립트는 제공하지 않는다.
+즉 모든 프로퍼티와 메서드는 public이다.