10/21 스터디

10/21 스터디

* 10월 21일자 스터디 발표 내용을 토대로 작성

<이터러블(iterable)>

반복 가능한(iterable, 이터러블) 객체는 배열을 일반화한 객체를 가리킨다.

이터러블 이라는 개념을 사용하면 어떤 객체에든 for..of 반복문을 적용 가능하다.

배열, 다수의 내장 객체, 문자열 등이 이터러블의 예이다.

for (let char of "test") { alert(char); } // t, e, s, t가 차례대로 출력됨

<자바스크립트의 hidden class> - 146p, 자바스크립트의 객체 관리 방식

클래스 기반 언어와 자바스크립트의 구조 차이

1. 자바스크립트는 클래스 기반 언어처럼 클래스 내 offset이 따로 없고, 동적 타입 언어이기 때문에 객체를 이루는 테이블([key + value])을 항상 유지시키고 있다가, 그때그때 객체의 프로퍼티를 탐색한다.

이를 동적 탐색(Dynamic Lookup)이라고 하며, 클래스 기반 언어에 비해 메모리와 속도 측면에서 상대적으로 불리하다.

2. 구글의 자바스크립트 엔진인 V8은 이러한 비효율을 피하기 위해 'Hidden Class'의 개념을 사용한다.

- 히든 클래스는 하나의 객체마다 부여됨

- 각 프로퍼티에 대해 오프셋의 정보를 보유

- 프로퍼티가 추가, 수정, 삭제되면 새로운 히든 클래스가 만들어지고, 이 히든 클래스는 기존 히든 클래스에서 업데이트 된 정보를 지닌 채로 생성

- 기존에 존재하던 히든 클래스는 신규 히든 클래스를 참조하도록 하는 정보가 추가됨

// 객체 생성, 동시에 히든클래스 1 생성 let obj = {}; // 첫번째 프로퍼티 배정, 동시에 히든 클래스 2 생성 obj.color = "white"; . . .

<함수 closure> - 167p, 함수 생성자를 통한 함수 선언

1. 스코프에 따라 분류했을 때, 내부함수의 범위에서는 외부 함수 범위에 있는 변수에 접근이 가능하지만 그 반대는 실현이 불가능하다는 개념이다.

function makeFunc() { var name = "Mozilla"; function displayName() { alert(name); } return displayName; } var myFunc = makeFunc(); // myFunc변수에 displayName을 리턴함 myFunc(); // 리턴된 displayName 함수를 실행(name 변수에 접근), "Mozilla" 출력

2. 일반적으로는 함수 안의 지역 변수들은 그 함수가 처리되는 동안에만 존재하기 때문에 displayName 함수가 return되어 해당 함수가 종료되면 name 변수가 소멸될 것으로 예상 할 수 있다.

하지만 자바스크립트에서는 함수를 return할 때, 클로저를 형성하기 때문에 클로저가 형성될 당시의 함수와 함수가 선언된 어휘적 환경의 조합(쉽게 말해 당시의 관계되는 코드들)의 참조를 기억하고 있다.

따라서 예시의 마지막 코드에서 "Mozilla"가 정상적으로 출력된다.

<값에 의한 전달, 참조에 의한 전달> - 175p, 그림 12-10

1. 값에 의한 전달과 참조에 의한 전달은 원시 데이터 타입과 객체형 데이터 타입이 메모리에 기록되는 방식의 차이에 기인하여 발생한다.

let a = 80; let b = a; // a의 값(80)을 복사하여 변수 b에 할당 console.log(a); // 80 console.log(b); // 80 a = 90; // a의 값을 90으로 재할당 console.log(a); // 90 console.log(b); // 80

2. 원시 데이터 타입은 값에 의한 전달이 이루어진다. 따라서 let b=a;를 선언하는 순간, 변수 b에는 변수 a의 값이 할당되는 것이 아니라 a의 값인 80 그 자체가 할당되게 된다.

따라서 변수 a의 값을 변경해도 변수 b에는 아무 영향이 없다.

let a = {name: "Kim"}; let b = a; console.log(a); // {name: "Kim"} console.log(b); // {name: "Kim"} a.age = 20; console.log(a); // {name: "Kim", age: 20} console.log(b); // {name: "Kim", age: 20}

3. 객체는 참조에 의한 전달의 방식을 취하는데, 값 그 자체를 메모리에 할당하는 것이 아니라, 변수가 할당된 메모리에 참조할 항목을 입력해놓는 것이다.

따라서 위의 예시에서 객체 a의 프로퍼티를 추가하면 b 역시 추가된 프로퍼티가 출력된다.

<옵저버 패턴(Observer Pattern)> - 176p

1. 옵저버 패턴(observer pattern)은 객체의 상태 변화를 관찰하는 관찰자들, 즉 옵저버들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 등을 통해 객체가 직접 목록의 각 옵저버에게 통지하도록 하는 디자인 패턴이다.

즉, 어떤 객체의 상태가 변할 때 그와 연관된 객체들에게 알림을 보내는 디자인 패턴의 한 종류이다.

옵저버 패턴 도식화

2. 브라우저에도 addEventListener를 통해 이벤트를 등록만 해 두면, 나중에 이벤트가 발생했을 때 사용자에게 통보를 한후, 콜백 함수가 실행한다.

다시 말하면, 콜백 함수가 실행되기 전까지의 과정이 바로 옵저버 패턴을 활용한 예라고 할 수 있다.

참고: https://stitchcoding.tistory.com/39

<즉시 실행 함수> - 177p

var buyCar = function(carName) { console.log('내가 구매한 차는 ' + carName + '입니다.'); }; buyCar('sonata'); (function(carName) { console.log('내가 구매한 차는 ' + carName + '입니다.'); }('sonata'));

1. 위의 예시에서, 상단 부분은 기명 함수 표현식을 작성한 것이고, 하단 부분은 익명 즉시실행함수를 작성한 것이다. 두 예시 모두 동일한 문장을 콘솔에 출력한다.

일반적인 함수표현식은 함수를 정의하고, 변수에 함수를 저장하고 실행하는 일련의 과정을 거친다. 그러나 즉시실행함수는 이러한 과정을 생략하고 즉시 실행에 들어간다.

var app = (function() { var privateVar = 'private'; return { prop : privateVar }; }()); console.log(app.prop); // "private" 출력

2. 즉시실행함수 내부에서 선언한 privateVar 변수를 외부에서도 접근할 수 있다. 이와 같이 즉시실행함수는 변수의 스코프를 포함시키는데 사용할 수 있다. 또한 익명으로 작성하는 것이 일반적인데, 이러한 이유로 네임스페이스에 변수를 추가할 필요가 없어서 코드 충돌을 줄일 수 있다.

<함수와 스택(stack)> -180p

1. 위의 이미지와 같은 자료 구조를 가정해보자. 만약 '운동하기' 데이터를 꺼내고 싶다면, '시장 보기', '숙제하기' 데이터를 출력해야만 한다. 이러한 식으로 자료의 입출력이 목록 한쪽 끝에서만 일어나는 자료구조를 스택(stack)이라고 한다.

이는 자료를 한쪽 끝에서만 넣거나 뺄 수 있는 선형적 구조이며, LIFO(Last in First Out)으로 칭하기도 한다.

2. 자바스크립트 엔진은 다음의 두 가지 요소로 구성되어 있다.

메모리 힙(Memory Heap)은 변수와 객체에 대한 할당을 담당한다.

호출 스택(Call Stack)은 코드를 실행하는 과정에서 쌓이는 스택의 종류이다. 각종 함수를 작동시킬 때에도 이 호출 스택을 통해 반복적으로 함수를 호출한다.

자바스크립트는 단일 스레드 프로그래밍이기 때문에, 단일 호출 스택의 형태를 취한다. 즉, 한번에 하나의 일만 처리할 수 있는 구조이다.

3. 함수에서 스택이 작동하는 원리는 다음과 같다.

function multiply(x, y) { return x * y; } function printSquare(x) { var s = multiply(x, x); console.log(s); } printSquare(5);

-183p

1. 자바스크립트는 비동기 처리 방식을 채택하고 있다. 이는 자바스크립트가, 특정 코드의 연산이 끝날 때 까지 코드의 실행을 멈추지 않고 다음 코드를 먼저 실행한다는 것이다.

이는 각 코드들의 실행되는 순간이 의도치 않고 꼬일 수 있다는 것을 의미하며, 이는 콜백 함수로 해결할 수 있다.

다만 콜백 함수의 경우에는 중첩이 잦아져 코드가 지저분해질 수 있고, 에러나 예외 처리가 힘들다는 단점이 있어 ES6에서는 promise 함수의 개념을 지원하게 되었다.

try { setTimeout(() => { throw 'Error!'; }, 1000); } catch (e) { console.log('에러를 캐치하지 못한다..'); console.log(e); }

위의 예시와 같이 콜백 함수는 중첩되는 구조 때문에 에러 처리가 힘들다.

//프로미스 생성 const promise1 = function(param){ return new Promise(function(resolve,reject){ if(param){ resolve("바보"); } else{ reject("아닌데"); } }); } //프로미스 실행 promise1(true).then(function(result){ console.log(result);//바보 },function(err){ console.log(err);//아닌데 });

실제 promise 생성과 실행은 다음과 같다.

비동기 메서드의 실행 메커니즘

<렉시컬 스코프(Lexical Scope)> - 198p

1. 렉시컬 스코프(Lexical Scope)는 함수가 선언이 되는 위치에 따라서 상위 스코프가 결정되는 스코프이다. 즉, 함수가 선언이 되는 순간 스코프가 생성된다.

let x = 'global' function foo(){ let x ='local' bar() } function bar(){ console.log(x) } foo() //global bar() //global

위의 예시에서, foo()의 지역변수에 x를 local이라 설정해 줬음에도 불구하고, bar 함수는 선언될 당시 전역 스코프에 있기 때문에 전역변수인 x를 참조하게 된다.

<지역 변수의 생명 주기> - 201p

var x = 'global'; function foo() { console.log(x); // ① var x = 'local'; } foo(); console.log(x); // global

호이스팅에 의해 함수 내부의 변수 x 선언문이 먼저 실행되기 때문에, ①에서는 undefined가 출력된다.

그러나 실제 var x = 'local'; 표현식 전까지는 undefined의 값을 가진다.

<전역 객체> - 203p

1. 전역 객체: 전역 객체는 특수한 형태의 객체로, 모든 객체는 이 전역 객체의 프로퍼티로 간주된다. 즉, 모든 객체의 유일한 최상위 객체를 의미한다.

브라우저 단에서는 window가, 서버(node.js)단에서는 global이 전역 객체를 지칭한다.

실행 컨텍스트에 들어가기 이전에 전역 객체는 자동으로 생성이 되며, 반드시 전역 스코프를 가지게 된다.

2. 전역 유효 범위 오염: 전역 변수와 전역 함수를 전역 객체에 선언하는 행위를 말한다. 이러한 상황에서는 변수 이름과 함수 이름이 겹치는 상황이 벌어질 수 있다.

전역 유효 범위가 오염되는 상황은 규모가 큰 파일을 다루거나, 여러 사람이 하나의 파일로 작업을 할 때 자주 일어난다.

// code1.js var global = 10; // code2.js var global = 20;

다음과 같이, 서로 다른 파일에서 똑같은 식별자로 전역 변수를 할당하면, 결국 global의 값은 20이 된다. 이는 자바스크립트의 식별자 결정 규칙에 따라 좀더 안쪽 코드에 입력된 값이 전역 변수에 할당되기 때문이다.

3. 네임스페이스 활용

네임스페이스는 변수에 쓰일 이름들을 한데 모아 충돌을 방지하고, 각각의 이름을 쓰는 변수들을 쉽게 가져다 쓰기 위해 만들어진 메커니즘이다. 하나의 전역 객체를 생성 후, 그 안에 필요한 기능들을 모두 프로퍼티로 정의한다.

// 객체 리터럴 var app = app || {}; // 프로퍼티 추가 app.name = "John"; app.age = 30; app.gender = "male";

논리합 연산자를 사용해서 app이 기존에 정의되어 있을 때는 그것을 사용하고, 그렇지 않으면 빈 객체를 app에 할당하게 된다. 이 상태에서 전역 유효 범위에서 사용하고자 하는 모든 변수와 함수들을 프로퍼티로 추가하면 app이라는 변수 하나만 전역 객체의 프로퍼티로 등록하는 구조가 가능해진다.

참고로 프로퍼티로 또다른 부분 네임스페이스를 만드는 것도 가능하다.

1. var는 함수 레벨 스코프 / let, const는 블럭 레벨 스코프이다.

var foo = 123; // 전역 변수 console.log(foo); // 123 { var foo = 456; } // 전역 변수 console.log(foo); // 456 let foo = 123; // 전역 변수 { let foo = 456; // 지역 변수 let bar = 456; // 지역 변수 } console.log(foo); // 123 console.log(bar); // ReferenceError: bar is not defined

2. var로 선언한 변수는 선언 전에 사용해도 에러가 나지 않지만 let, const는 에러가 발생한다.

(var의 경우에는 초기 값이 없으면 자동으로 undefined를 초기값으로 하여 메모리를 할당)

let, const의 경우 호이스팅이 되면서 초기 값이 없다면 var처럼 자동으로 초기값을 할당하지 않는다.

3. var는 이미 선언되어 있는 이름과 같은 이름으로 변수를 또 선언해도 에러가 나지 않지만 let, const는 이미 존재하는 변수와 같은 이름의 변수를 또 선언하면 에러가 발생한다.

4. var, let은 변수 선언시 초기 값을 주지 않아도 되지만 const는 반드시 초기값을 할당해야 함

5. var, let은 값을 다시 할당할 수 있지만 const는 한번 할당한 값은 변 불가능하다.

(단, 객체 안에 프로퍼티가 변경되는 것까지 막는 것은 불가능)

from http://oaat9309.tistory.com/18 by ccl(A) rewrite - 2021-10-21 19:01:36