https://developer.mozilla.org/ko/docs/Web/Performance/Critical_rendering_path

 

중요 렌더링 경로 - Web Performance | MDN

중요 렌더링 경로 (Critical Rendering Path)는 브라우저가 HTML, CSS, Javascipt를 화면에 픽셀로 변화하는 일련의 단계를 말하며 이를 최적화하는 것은 렌더링 성능을 향상시킵니다. 중요 렌더링 경로는 Doc

developer.mozilla.org

출처 : 모던 자바스크립트 딥다이브, 타입스크립트 프로그래밍

 

웹 어플리케이션 클라이언트 사이드 자바스크립트는 브라우저에서 HTML, CSS와 함께 실행된다. 따라서 브라우저 환경을 고려할 때 더 효율적인 클라이언트 사이드 자바스크립트 프로그래밍이 가능하다.

이를 위해 브라우저가 HTML, CSS, 자바스크립트로 작성된 텍스트 문서를 어떻게 파싱(해석)하여 브라우저에 렌더링하는지 살펴봐야 한다.

* 파싱: 파싱(구문 분석 syntax analysis)은 프로그래밍 언어의 문법에 맞게 작성된 텍스트 문서를 읽어 들여 실행하기 위해 텍스트 문서의 문자열을 토큰(token)으로 분해(어휘 분석 lexical analysis)하고, 토큰에 문법적 의미와 구조를 반영하여 트리 구조의 자료구조인 파스 트리(parse tree/syntax tree)를 생성하는 일련의 과정을 말한다. 일반적으로 파싱이 완료된 이후에는 파스 티리를 기반으로 중간 언어(intermediate code)인 바이트코드(bytecode)를 생성하고 실행한다. 

* 렌더링: HTML, CSS, 자바스크립트로 작성된 문서를 파싱하여 브라우저에 시각적으로 출력하는 것을 말한다.

 

브라우저는 다음과 같은 과정을 거쳐 렌더링을 수행한다.

 

1. 브라우저는 HTML, CSS, 자바스크립트, 이미지, 폰트 파일 등 렌더링에 필요한 리소스를 요청하고 서버로부터 응답을 받는다.

 

2. 브라우저의 렌더링 엔진은 서버로부터 응답된 HTML과 CSS를 파싱하여 DOM과 CSSOM을 생성하고 이들을 결합하여 렌더 트리를 생성한다.

 

3. 브라우저의 자바스크립트 엔진은 서버로부터 응답된 자바스크립트를 파싱하여 AST를 생성하고 바이트코드로 변환하여 실행한다. 이때 자바스크립트는 DOM API를 통해 DOM이나 CSSOM을 변경할 수 있다. 변경된 DOM과 CSSOM은 다시 렌더 트리로 결합된다.

 

4. 렌더 트리를 기반으로 HTML 요소의 레이아웃(위치와 크기)을 계산하고 브라우저 화면에 HTML 요소를 페인팅한다.

 

 

 

타입스크립트 컴파일러(Typescript Compiler, TSC)

타입스크립트는 자바스크립트나 자바 같은 주요 언어와는 다른 방식으로 동작

프로그램은 프로그래머가 작성한 다수의 텍스트 파일로 구성된다.

 

 

<다른 언어>

  1. 텍스트를 컴파일러가 파싱하여 추상 문법 트리(abstract syntax tree, AST)라는 자료구조로 변환
  2. 컴파일러가 AST를 바이트코드로 변환
  3. 런타임 프로그램에 2에서 변환된 바이트코드를 입력, 평가 후 결과얻음.

즉, 프로그램 실행이란, 컴파일러가 소스 코드를 파싱해 AST로 만들고, 다시 AST를 바이트코드로 변환한 것을 런타임이 평가하도록 지시하는 것

 

<타입스크립트>

타입스크립트는 컴파일러가 소스 코드를 자바스크립트 코드로 변환한다(바이트코드X).

타입스크립트는 코드는 언제 안전해지는가.

타입스크립트는 컴파일 단계에서 AST를 만들어 결과 코드를 내놓기 전에 타입 확인 과정을 거친다.

typechecker(타입검사기) 사용 : 코드의 타입 안전성을 검증하는 프로그램

타입 확인 단계 덕분에 프로그래머의 기대대로 실수를 방지할 수 있다.

전체적인 타입스크립트 컴파일 과정은 다음과 같다.

1~3은 TSC가 수행하며, 4~6은 브라우저, NodeJS, 기타 자바스크립트 엔진 등에서 수행한다.

 

 

 

 

 
1~3은 TSC(타입스크립트 컴파일러)
4~6은 브라우저, nodeJs, 기타 자바스크립트 엔진에서 수행한다.

<전체적인 타입스크립트 컴파일 과정>

  1. TS 소스를 TSC가 파싱하여 타입스크립트 추상 문법 트리(AST)로 변환 : 타입 이용됨
  2. 타입 검사기AST를 확인(타입 안정성 확보 구간) : 타입 이용됨
  3. TSCASTJS 소스로 컴파일(변환) : 더이상 타입 확인하지 않음
  4. JS 소스자바스크립트 추상 문법 트리(AST)로 변환
  5. AST를 바이트 코드로 변환
  6. 런타임 프로그램에 5에서 변환된 바이트코드를 평가

 

과정 1~2에서는 소스 코드에 사용된 타입을 사용하지만, 과정 3에서는 이용하지 않는다.

즉, 개발자가 코드에 기입한 타입 정보는 최종적으로 만들어지는 프로그램에 아무런 영향을 주지 않으며, 단지 2번 과정까지 타입을 확인하는 데만 쓰인다.


타입 시스템 (type system)

타입 검사기가 프로그램에 타입을 할당하는 데 사용하는 규칙 집합이다.

타입 시스템에는 사용하는 보통 2가지 종류가 존재

  1. 어떤 타입을 사용하는지를 컴파일러에 명시적으로 알려주는 타입 시스템
  2. 자동으로 타입을 추론하는 타입 시스템

2가지 시스템은 서로 장단점이 존재하고, 타입스크립트는 양측에 모두 영향을 받았다. 즉, 프로그래머는 선택할 수 있다.

  • 어노테이션 : 타입스크립트에 명시적으로 타입을 지정할 수 있다.
    'value:type’의 형태로 쓰이며 타입 검사기에게 곧바로 타입을 알리는 역할을 한다.
  • 어노테이션을 쓰지 않으면 타입스크립트가 알아서 타입을 추론한다 : 코드양을 줄인다는 측면에서 이점이 있다.
 
1const a: number = 10; // 타입을 명시 2 3const b = 10; // 타입을 추론하게 함

 


타입스크립트 vs 자바스크립트

다음 표는 타입스크립트와 자바스크립트의 타입 시스템을 비교한 표다.

다만, 스택 오버 플로/네트워크 연결 끊김/ 잘못된 사용자 입력 등 타입스크립트가 컴파일 타임에 검출할 수 없는 런타임 예외도 많다. 다만… 순수 자바스크립트 세계에서 런타임 에러로 발생했을 많은 에러를 타입스크립트는 컴파일 타임에 검출할 수 있다는 점이 핵심이다.

타입 결정

동적 타입 바인딩이란, 자바스크립트가 프로그램을 실행해야만 특정 데이터의 타입을 알 수 있음을 의미한다.

반대로 타입스크립트점진적으로 타입 확인한다. 컴파일 타임에 프로그램의 모든 타입을 알고 있을 때 최상의 결과를 보여줄 수 있지만, 프로그램을 컴파일하는 데 반드시 모든 타입을 알아야 하는 것은 아니다.(일부 타입 추론 가능하므로)

이런 점진적 컴파일은 코드의 일부만 고쳤을 때 전체 프로그램을 다시 컴파일할 필요가 없으므로 빨리 재컴파일된다는 장점도 있다.

점진적 타입 확인은 ‘타입을 지정하지 않은 기존 자바스크립트 코드’를 ‘타입을 사용하는 타입스크립트’로 마이그레이션할 때 매우 유용하다. → 11장 내용과 연계됨

하지만 코드를 마이그레이션하는 상황이 아니라면 모든 코드의 타입을 컴파일 타임에 지정하는 것을 목표로 해야 한다. 따로 언급하지 않는 한 이 책은 모든 코드의 타입이 컴파일 타임에 식별되도록 하는 방식을 추구할 것을 권장한다.

 

 

'JavaScript' 카테고리의 다른 글

[js] ...스프레드 문법, Object.assign, Object.create 차이  (0) 2022.06.07
[js] promise all  (0) 2022.06.07
[js] 동적 타이핑  (0) 2022.06.06
[js] 데이터 타입의 필요성  (0) 2022.06.06
[js] JavaScript에서 이중 느낌표  (0) 2022.06.06

스프레드 문법

let o1 = { M: 1 };
let o2 = { ...o1, L: 3 };
console.log(o2.L);
console.log(o2);         //{ M: 1, L: 3 }

스프레드(…)를 활용하여 자료구조를 불변으로 만들 수 있다.
위와같이 스프레드란 객체 o1이 있고 여기에 값이 3인 프로퍼티 L을 추가해야 한다면,
같이 새로운 객체를 만들어서 o1 대신 사용할 수 있는 문법을 뜻함

 

 

 

 

Object.assign()

 

let o1 = { M: 1 };
let o2 = Object.assign(Object.assign({}, o1), { L: 3 });
console.log(o2.L);

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

Object.assign() 메서드는 출처 객체들의 모든 열거 가능 자체 속성을 복사해 대상 객체에 붙여넣습니다. 그 후 대상 객체를 반환합니다.

const target = { a: 1, b: 2 };
const source = { c: 4, d: 5 };

const returnedTarget = Object.assign(target, source);

구문

Object.assign(target, ...sources)
Copy to Clipboard

매개변수

target

목표 객체. 출처 객체의 속성을 복사해 반영한 후 반환할 객체입니다.

sources

출처 객체. 목표 객체에 반영하고자 하는 속성들을 갖고 있는 객체들입니다.

반환 값

목표 객체.

Object.assign()은 속성을 단순히 복사하거나 새로 정의하는 것이 아니라, 할당(assign)하는 것입니다. 

 

객체 복제

const obj = { a: 1 };
const copy = Object.assign({}, obj);
console.log(copy); // { a: 1 }

Object.create()

Object.create() 메서드는 지정된 프로토타입 객체 및 속성(property)을 갖는 새 객체를 만듭니다.

구문

Object.create(proto[, propertiesObject])

매개변수

proto새로 만든 객체의 프로토타입이어야 할 객체.propertiesObject선택사항. 지정되고 undefined가 아니면, 자신의 속성(즉, 자체에 정의되어 그 프로토타입 체인에서 열거가능하지 않은 속성)이 열거가능한 객체는 해당 속성명으로 새로 만든 객체에 추가될 속성 설명자(descriptor)를 지정합니다. 이러한 속성은 Object.defineProperties()의 두 번째 인수에 해당합니다.

반환값

지정된 프로토타입 개체와 속성을 갖는 새로운 개체.

예외

proto 매개변수가 null 또는 객체가 아닌 경우 TypeError 예외가 발생(throw).

Object.create()를 사용한 고전적인 상속방법

아래는 고전적인 상속방법으로 사용된 Object.create() 사용 예입니다. 이는 단일 상속 용으로, JavaScript가 지원하는 전부입니다.

// Shape - 상위클래스
function Shape() {
  this.x = 0;
  this.y = 0;
}

// 상위클래스 메서드
Shape.prototype.move = function(x, y) {
  this.x += x;
  this.y += y;
  console.info('Shape moved.');
};

// Rectangle - 하위클래스
function Rectangle() {
  Shape.call(this); // super 생성자 호출.
}

// 하위클래스는 상위클래스를 확장
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

var rect = new Rectangle();

console.log('Is rect an instance of Rectangle?', rect instanceof Rectangle); // true
console.log('Is rect an instance of Shape?', rect instanceof Shape); // true
rect.move(1, 1); // Outputs, 'Shape moved.'

 

 

여러 객체에서 상속하고 싶은 경우엔 mixin이 사용가능합니다.
function MyClass() {
  SuperClass.call(this);
  OtherSuperClass.call(this);
}

MyClass.prototype = Object.create(SuperClass.prototype); // 상속
mixin(MyClass.prototype, OtherSuperClass.prototype); // mixin

MyClass.prototype.myMethod = function() {
  // 기능 수행
};
Copy to Clipboard

mixin 함수는 상위(super)클래스 프로토타입에서 하위(sub)클래스 프로토타입으로 함수를 복사하고, mixin 함수는 사용자에 의해 공급될 필요가 있습니다. mixin 같은 함수의 예는 jQuery.extend()입니다.

'JavaScript' 카테고리의 다른 글

[js] 브라우저 렌더링  (0) 2022.06.07
[js] promise all  (0) 2022.06.07
[js] 동적 타이핑  (0) 2022.06.06
[js] 데이터 타입의 필요성  (0) 2022.06.06
[js] JavaScript에서 이중 느낌표  (0) 2022.06.06

 

 

Promise.all() - JavaScript | MDN

Promise.all() 메서드는 순회 가능한 객체에 주어진 모든 프로미스가 이행한 후, 혹은 프로미스가 주어지지 않았을 때 이행하는 Promise를 반환합니다. 주어진 프로미스 중 하나가 거부하는 경우, 첫

developer.mozilla.org

 

 

프라미스 API

 

ko.javascript.info

 

동적 타입 언어와 정적 타입 언어 

자바스크립트이 모든 값은 데이터 타입을 갖는다. 그렇다면 변수 역시 데이터 타입을 가질 수 있는지 생각해보아야 한다.

 

먼저 요약하자면,

정적 타입 언어에서 변수는 데이터타입을 가지지만

자바스크립트같은 (동적타이핑을 수행하는)동적타입 언어에서 변수는 타입을 갖지 않는다.

 

 

C나 자바 같은 정적 타입 언어는 변수를 선언할 때 변수에 할당할 수 있는 값의 종류, 즉 데이터 타입을 사전에 선언해야 한다. 이를 명시적 타입 선언이라 한다. 가령 C에서 char c;라고 선언하면 c변수에는 1바이트 정수 타입의 값(-128~-127)만 할당할 수 있다.

int num; 이라고 선언하면 num 변수에는 4바이트 정수 타입의 값(-2,124,483,648~2,124,483,647) 만 할당할 수 있다.

 

정적 타입 언어는 변수의 타입을 변경할 수 없으며, 변수에 선언한 타입에 맞는 값만 할당할 수 있다. 정적 타입 언어는 컴파일 시점에 타입 체크(선언한 데이터 타입에 맞는 값을 할당했는지 검사하는 처리)를 수행한다. 만약 타입 체크를 통과하지 못했다면 에러를 발생시키고 프로그램의 실행 자체를 막는다. 이를 통해 타입의 일관성을 강제함으로써 더욱 안정적인 코드의 구현을 통해 런타임에 발생하는 에러를 줄인다. 

 

자바스크립트는 정적 타입 언어와 다르게 변수를 선언할 때 타입을 선언하지 않는다. 다만 var, let, const 키워드를 사용해 변수를 선언할 뿐이다. 자바스크립트의 변수는 정적 타입 언어와 같이 미리 선언한 데이터 타입의 값만 할당할 수 있는 것이 아니다. 어떠한 데이터 타입의 값이라도 자유롭게 할당할 수 있다.

 

변수 선언 후 다양한 데이터 값을 할당한 다음 typeof 연산자로 변수의 데이터 타입을 조사하면,

typeof 연산자는 연산자 뒤에 위치한 피연산자의 데이터 타입을 문자열로 반환한다.

가 아니라

정확히 말하면 변수의 데이터 타입을 반환하는 것이 아니라

변수에 할당된 값의 데이터 타입을 반환하는 것이다.

 

자바스크립트의 변수에는 어떤 데이터 타입의 값이라도 자유롭게 할당할 수 있으므로 정적 타입 언어에서 말하는 데이터 타입과 개념이 다르다. 정적 타입 언어는 변수 선언 시점에 변수의 타입이 결정되고 변수의 타입을 변경할 수 없다. 자바스크립트에서는 값을 할당하는 시점에 변수의 타입이 동적으로 결정되고 변수와 타입을 언제든지 자유롭게 변경할 수 있다.

 

다시 말해, 자바스크립트의 변수는 선언이 아닌 할당에 의해 타입이 결정(타입 추론)된다. 그리고 재할당에 의해 변수의 타입은 언제든지 동적으로 변할 수 있다(당연히 상수인 const 제외). 이러한 특징을 동적 타이핑이라고 하며, 자바스크립트를 정적 타입 언어와 구별하기 위해 동적 타입 언어라 한다. 대표적인 동적 타입 언어로는 자바스크립트, 파이썬, 루비 등이 있다.

 

정리하자면 자바스크립트의 변수는 타입을 가지지 않는다.

하지만 값이 타입을 갖는다.

따라서 현재 변수에 할당되어 있는 값에 의해 변수의 타입이 동적으로 결정된다고 표현하는 것이 바람직하다.

 

동적 타입 언어에서 변수 주의사항

- 변수는 꼭 필요한 경우에 제한적으로 사용한다. 변수 값은 재할당에 의해 언제든지 변경될 수 있다. 이로 인해 동적 타입 언어인 자바스크립트는 타입을 잘못 예측해 오류가 발생할 가능성이 크다. 변수의 개수가 ㅁ낳을수록 오류가 발생할 확률도 높아진다. 따라서 변수의 무분별한 남발은 금물이다. 필요한만큼 최소한으로 유지하도록 노력하자

- 변수 유효 범위(스코프)는 최대한 좁게 만들어 변수의 부작용을 억제하자. 변수의 유효범위가 넓을수록 변수로 인해 오류가 발생할 확률이 높아진다.

- 전역 변수는 최대한 사용하지 않도록 한다. 어디서든지 참조/변경 가능한 전역변수는 의도치 않게 값이 변경될 가능성이 높고 다른 코드에 영향을 줄 가능성도 높다. 

- 변수보다는 상수를 사용해 값의 변경을 억제하자.

- 변수 이름은 변수의 목적이나 의미를 파악할 수 있도록 네이밍하자.

출처 : 모던 자바스크립트 딥다이브

 

데이터 타입은 값의 종류를 말한다. 

자바스크립트의 모든 값은 데이터 타입을 갖는다.

자바스크립트의 데이터 타입은 크게 원시 타입과 객체 타입으로 분류한다.(근본적으로 다름)

자바스크립트는 객체 기반의 언어이며, 자바스크립트를 이루고 있는 거의 모든 것이 객체이다.

 

데이터 타입이 필요한 이유를 요약하면 다음과 같다.

 - 값을 저장할 때 확보해야 하는 메모리 공간의 크기를 결정하기 위해

 - 값을 참조할 때 한번에 읽어 들여야 할 메모리 공간의 크기를 결정하기 위해

 - 메모리에서 읽어 들인 2진수를 어떻게 해석할지 결정하기 위해

 

데이터 타입의 필요성

데이터타입에 의한 메모리 공간의 확보와 참조

값은 메모리에 저장하고 참조할 수 있어야 한다. 메모리에 값을 저장하려면 먼저 확보해야 할 메모리 공간의 크기를 결정해야 한다.

var score =100;

위 코드가 실행되면 컴퓨터는 숫자 값 100을 저장하기 위해 메모리 공간을 확보한 다음,

확보된 메모리에 숫자 값 100을 2진수로 저장한다.

자바스크립트가 데이터 타입, 즉 값의 종류에 따라 정해진 크기의 메모리 공간을 확보한다.

다시말해, 변수에 할당되는 값의 데이터 타입에 따라 확보해야 할 메모리 공간의 크기가 결정된다. 

 

값을 참조하는 경우에는 식별자 score을 통해 숫자 타입의 값 100이 저장되어 있는 메모리 공간의 주소를 찾아갈 수 있다. 정확히 말하면 숫자값 100이 저장되어 있는 메모리 공간의 선두 메모리셀의 주소를 찾아갈 수 있다.

이때 값을 참조하려면 한번에 읽어 들여야 할 메모리 공간의 크기, 즉 메모리 셀의 개수(바이트수)를 알아야 한다. score변수의 경우, 저장되어 있는 값이 숫자 타입이므로 8바이트 단위로 읽어 들이지 않으면 값이 훼손된다.

컴퓨터는 한번에 읽어 들여야 할 메모리 셀의 크기를 어떻게 알 수 있냐면, score 변수에는 숫자 타입의 값이 할당되어 있으므로 자바스크립트 엔진은 score 변수의 숫자 타입으로 인식한다. 숫자타입은 8바이트 단위로 저장되므로 score 변수를 참조하면 8바이트 단위로 메모리 공간에 저장된 값을 읽어 들인다.

 

 

* 심벌 테이블 : 컴파일러 또는 인터프리터는 심벌 테이블이라고 부르는 자료 구조를 통해 식별자를 키로 바인딩된 값의 메모리 주소, 데이터 타입, 스코프 등을 관리한다.

데이터 타입에 의한 값의 해석

문제가 하나 더 남아있다. 메모리에서 읽어 들인 2진수를 어떻게 해석해야 하느냐이다.

모든 값은 데이터 타입을 가지며, 메모리에 2진수, 즉 비트의 나열로 저장된다. 메모리에 저장된 값은 데이터타입에 따라 다르게 해석될 수 있다. 예를 들면 메모리에 저장된 값 0100 0001 을 숫자로 해석하면 65이지만, 문자열로 해석하면 'A'이다.

 

앞에서 살펴본 score 변수에 할당된 값은 숫자 타입의 값이다. 따라서 score 변수를 참조하면 메모리 공간의 주소에서 읽어 들인 2진수를 숫자로 해석한다. 

'JavaScript' 카테고리의 다른 글

[js] promise all  (0) 2022.06.07
[js] 동적 타이핑  (0) 2022.06.06
[js] JavaScript에서 이중 느낌표  (0) 2022.06.06
[js] Inheritance prototype chain  (0) 2022.06.03
[js] 함수 호이스팅  (0) 2022.06.02
 

What's the double exclamation mark for in JavaScript? | Brian Love

If you have ever noticed a double exclamation mark (!!) in someone's JavaScript code you may be curious what it's for and what it does. It's really simple: it's short way to cast a variable to be a boolean (true or false) value. Let me explain. typeof Java

brianflove.com

ㄴ개발자 선배님께 검색 방법 배우면서 찾았던 사이트

 

다음 값은 JavaScript에서 false 로 간주됩니다 .

  • 빈 문자열:""
  • 0
  • null
  • undefined
  • NaN

다음 값은 JavaScript에서 truthys로 간주됩니다.

  • 물체:{}
  • 정렬:[]
  • 비어 있지 않은 문자열:"anything"
  • 0이 아닌 숫자:3.14
  • 날짜:new Date();

코드를 실행하는 JavaScript 엔진은 if 문에서 평가되는 경우와 같이 필요할 때 값을 부울로 변환(또는 강제 변환)하려고 시도합니다.

 

 

느낌표가 이중인 이유는 무엇?

어떤 경우에는 변수를 명시적으로 부울 값으로 캐스팅할 수 있습니다. 왜요? 글쎄, 가장 큰 이유는 대부분의 개발자가 유형 안전 비교 연산자를 사용하지 않기 때문입니다.

유형 안전 비교 연산자는 다음과 같습니다.

  • 엄격하게 같음:===
  • 엄밀히 불평등:!==

유형 안전 비교 연산자를 사용할 때 값이 동일한지(또는 같지 않은지)와 해당 유형이 동일한지 모두 확인합니다. 유형 안전 비교 연산자가 없으면 JavaScript 엔진이 참/거짓 논리를 기반으로 변수를 참 또는 거짓으로 자유롭게 강제 변환할 수 있습니다.

JavaScript 변수를 부울로 변환하려면 두 개의 느낌표를 사용하면 됩니다.

function() {
  var name = 'Brian';

  //alert 'string'
  window.alert(typeof name);

  //cast to boolean
  var bool = !!name;

  //alert 'boolean'
  window.alert(typeof bool);
}

위의 예제 코드에서는 문자열 "Brian"을 boolean값으로 캐스팅합니다. 따라서 두 번째 경고는 변수가 이제 boolean값임을 나타냅니다.

 

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference

 

JavaScript 참고서 - JavaScript | MDN

이 페이지는 JavaScript 언어에 대한 정보 보관소입니다. 이 참고서에 대해 더 읽어보세요.

developer.mozilla.org

또한 기타 헷갈리는 연산자는 위의 페이지에서 하나하나 찾아보는 방법도 있다.

 

'JavaScript' 카테고리의 다른 글

[js] 동적 타이핑  (0) 2022.06.06
[js] 데이터 타입의 필요성  (0) 2022.06.06
[js] Inheritance prototype chain  (0) 2022.06.03
[js] 함수 호이스팅  (0) 2022.06.02
[js] Function  (0) 2022.06.02

 

 

상속과 프로토타입 - JavaScript | MDN

Java 나 C++ 같이 클래스 기반의 언어를 사용하던 프로그래머는 자바스크립트가 동적인 언어라는 점과 클래스가 없다는 것에서 혼란스러워 한다. (ES2015부터 class 키워드를 지원하기 시작했으나,

developer.mozilla.org

ES2015부터 class 키워드를 지원하기 시작했으나, 문법적인 양념일 뿐이며 자바스크립트는 여전히 프로토타입 기반의 언어다.

 

상속 관점에서 자바스크립트의 유일한 생성자는 객체뿐이다. 각각의 객체는 [[Prototype]]이라는 은닉(private) 속성을 가지는데 자신의 프로토타입이 되는 다른 객체를 가리킨다. 그 객체의 프로토타입 또한 프로토타입을 가지고 있고 이것이 반복되다, 결국 null을 프로토타입으로 가지는 오브젝트에서 끝난다. null은 더 이상의 프로토타입이 없다고 정의되며, 프로토타입 체인의 종점 역할을 한다.

 

 

프로토타입 체인을 이용한 상속

속성 상속

자바스크립트 객체는 속성을 저장하는 동적인 "가방"과 (자기만의 속성이라고 부른다) 프로토타입 객체에 대한 링크를 가진다. 객체의 어떤 속성에 접근하려할 때 그 객체 자체 속성 뿐만 아니라 객체의 프로토타입, 그 프로토타입의 프로토타입 등 프로토타입 체인의 종단에 이를 때까지 그 속성을 탐색한다.

 

 

아래 코드에는 어떤 속성에 접근 하려할 때 일어나는 상황이다.

 

// o라는 객체가 있고, 속성 'a' 와 'b'를 갖고 있다고 하자.
let f = function () {
    this.a = 1;
    this.b = 2;
}
let o = new f(); // {a: 1, b: 2}

// f 함수의 prototype 속성 값들을 추가 하자.
f.prototype.b = 3;
f.prototype.c = 4;

// f.prototype = {b: 3, c: 4}; 라고 하지 마라, 해당 코드는 prototype chain 을 망가뜨린다.
// o.[[Prototype]]은 속성 'b'와 'c'를 가지고 있다.
// o.[[Prototype]].[[Prototype]] 은 Object.prototype 이다.
// 마지막으로 o.[[Prototype]].[[Prototype]].[[Prototype]]은 null이다.
// null은 프로토타입의 종단을 말하며 정의에 의해서 추가 [[Prototype]]은 없다.
// {a: 1, b: 2} ---> {b: 3, c: 4} ---> Object.prototype ---> null

console.log(o.a); // 1
// o는 'a'라는 속성을 가지는가? 그렇다. 속성의 값은 1이다.

console.log(o.b); // 2
// o는 'b'라는 속성을 가지는가? 그렇다. 속성의 값은 2이다.
// 프로토타입 역시 'b'라는 속성을 가지지만 이 값은 쓰이지 않는다. 이것을 "속성의 가려짐(property shadowing)" 이라고 부른다.

console.log(o.c); // 4
// o는 'c'라는 속성을 가지는가? 아니다. 프로토타입을 확인해보자.
// o.[[Prototype]]은 'c'라는 속성을 가지는가? 가지고 값은 4이다.

console.log(o.d); // undefined
// o는 'd'라는 속성을 가지는가? 아니다. 프로토타입을 확인해보자.
// o.[[Prototype]]은 'd'라는 속성을 가지는가? 아니다. 다시 프로토타입을 확인해보자.
// o.[[Prototype]].[[Prototype]]은 null이다. 찾는 것을 그만두자.
// 속성이 발견되지 않았기 때문에 undefined를 반환한다.

 

메소드 상속

자바스크립트에 "메소드"라는건 없다. 하지만 자바스크립트는 객체의 속성으로 함수를 지정할 수 있고 속성 값을 사용하듯 쓸 수 있다. 속성 값으로 지정한 함수의 상속 역시 위에서 본 속성의 상속과 동일하다. (단 위에서 언급한 "속성의 가려짐" 대신 "메소드 오버라이딩, method overriding" 라는 용어를 사용한다)

사실상 자바스크립트의 상속은 셰도잉이라고 할 수 있음(한겹 더 가림의 느낌으로)

 

var o = {
  a: 2,
  m: function(b){
    return this.a + 1;
  }
};

console.log(o.m()); // 3
// o.m을 호출하면 'this' 는 o를 가리킨다.

var p = Object.create(o);
// p 는 프로토타입을 o로 가지는 오브젝트이다.

p.a = 12; // p 에 'a'라는 새로운 속성을 만들었다.
console.log(p.m()); // 13
// p.m이 호출 될 때 'this' 는 'p'를 가리킨다.
// 따라서 o의 함수 m을 상속 받으며,
// 'this.a'는 p.a를 나타내며 p의 개인 속성 'a'가 된다.

 

 

Object.create 이용

ECMAScript 5는 새로운 방법을 도입했다. Object.create라는 메소드를 호출하여 새로운 객체를 만들 수 있다. 생성된 객체의 프로토타입은 이 메소드의 첫 번째 인수로 지정된다.

 

var a = {a: 1};
// a ---> Object.prototype ---> null

var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (상속됨)

var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null

var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined이다. 왜냐하면 d는 Object.prototype을 상속받지 않기 때문이다.

class 키워드 이용

ECMAScript2015에는 몇 가지 키워드가 도입되어 class를 구현하였다. 이런 생성 방식은 클래서 기반 언어의 개발자들에게 친숙하게 다가오나 동작 방식이 같지는 않다. 자바스크립트는 여전히 프로토타입 기반으로 남아있다. 새로 도입된 키워드는 class, constructor, static, extends, 그리고 super가 있다.

'use strict';

class Polygon {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

class Square extends Polygon {
  constructor(sideLength) {
    super(sideLength, sideLength);
  }
  get area() {
    return this.height * this.width;
  }
  set sideLength(newLength) {
    this.height = newLength;
    this.width = newLength;
  }
}

var square = new Square(2);

 

 

 

function Graph() {
  this.vertexes = [];
  this.edges = [];
}

Graph.prototype = {
  addVertex: function(v){
    this.vertexes.push(v);
  }
};

var g = new Graph();
// g 'vertexes' 와 'edges'를 속성으로 가지는 객체이다.
// 생성시 g.[[Prototype]]은 Graph.prototype의 값과 같은 값을 가진다.

 

성능

프로토타입 체인에 걸친 속성 검색으로 성능에 나쁜 영향을 줄 수 있으며, 때때로 치명적일 수 있다. 또한 존재하지도 않는 속성에 접근하려는 시도는 항상 모든 프로토타입 체인인 전체를 탐색해서 확인하게 만든다.

객체의 속성에 걸쳐 루프를 수행 하는 경우 프로토타입 체인 전체의 모든 열거자 속성에 대하여 적용된다. 객체 개인 속성인지 프로토타입 체인상 어딘가에 있는지 확인하기 위해서는 Object.prototype에서 모든 오브젝트로 상속된 hasOwnProperty 메소드를 이용할 필요가 있다. 다음 코드를 통하여 구체적인 예를 확인하여 보자.

 

 

console.log(g.hasOwnProperty('vertices'));
// true

console.log(g.hasOwnProperty('nope'));
// false

console.log(g.hasOwnProperty('addVertex'));
// false

console.log(g.__proto__.hasOwnProperty('addVertex'));
// true

중요한 점은:

  • .prototype에 타입이 정의되어 있다.
  • Object.create()을 이용하여 상속한다.
function Animal(name, address) {
	this.name = name;
	this.address = address;
}

Animal.prototype.printName = function () {
	console.log(`${this.name} ${this.address}`);
}

function Dog(name, address, owner) {
	//아래줄은 클래스에서 super을 호출하는 것과 동일하다
	//super(name, address);
	Animal.call(this, name, address);
	this.owner = owner;
}

//Dog가 Animal을 상속하기 위해 
//Object.create(Animal.prototype) 인자로 전달한 프로토타입을 이용해서
//새로운 오브젝트를 만들어서 연결해줌
//원래 상태: Dog.prototype = Object.create(Object.prototype);
Dog.prototype = Object.create(Animal.prototype);

Dog.prototype.play = () => {
	console.log('play with me!')
}

const dog1 = new Dog('멍멍이', '동대문구', '김하늘');

dog1.play();

dog1.printName();

 

연결형 상속(Concatenative inheritance)

연결형 상속은 한 객체의 속성을 다른 객체에 모두 복사함으로써 상속을 구현하는 방법이다.

이 상속법은 Javascript 객체의 동적 확장성을 이용한 방법이다. 객체 복사는 속성의 초기값을 저장하기 위한 좋은 방법이다: 이 방식은 Object.assign()을 통해 구현하는 것이 보통이며 ES6 이전에 Lodash, Underscore, jQuery등의 라이브러리들이 .extend() 와 비슷한 메소드로 제공한 방법이다.

 

const proto = {
  hello: function hello() {
    return `Hello, my name is ${ this.name }`;
  }
};

const george = Object.assign({}, proto, {name: 'George'});
const msg = george.hello();
console.log(msg); // Hello, my name is George

 

 

Function | PoiemaWeb

함수란 어떤 특정 작업을 수행하기 위해 필요한 일련의 구문들을 그룹화하기 위한 개념이다. 만일 스크립트의 다른 부분에서도 동일한 작업을 반복적으로 수행해야 한다면 (동일한 구문을 계속

poiemaweb.com

 

https://rainbowdev.tistory.com/10

 

[js] Function

Function | PoiemaWeb 함수란 어떤 특정 작업을 수행하기 위해 필요한 일련의 구문들을 그룹화하기 위한 개념이다. 만일 스크립트의 다른 부분에서도 동일한 작업을 반복적으로 수행해야 한다면 (동

rainbowdev.tistory.com

위 포스팅에서 3가지의 함수 정의 방식을 알아보았다. 정의 방식은 달라도 결국 Function 생성자 함수를 통해 함수를 생성하는 것까지 확인하였다. 그런데 이 3가지 함수 정의 방식은 동작 방식에 약간의 차이가 있다.

 

var res = square(5);

function square(number) {
  return number * number;
}

 

위 코드를 보면 함수 선언문으로 함수가 정의되기 이전에 함수 호출이 가능하다. 함수 선언문의 경우, 함수 선언의 위치와는 상관없이 코드 내 어느 곳에서든지 호출이 가능한데 이것을 함수 호이스팅(Function Hoisting)이라 한다.

자바스크립트는 ES6의 let, const를 포함하여 모든 선언(var, let, const, function, function*, class)을 호이스팅(Hoisting)한다.

호이스팅이란 var 선언문이나 function 선언문 등 모든 선언문이 해당 Scope의 선두로 옮겨진 것처럼 동작하는 특성을 말한다. 즉, 자바스크립트는 모든 선언문(var, let, const, function, function*, class)이 선언되기 이전에 참조 가능하다.

함수 선언문으로 정의된 함수는 자바스크립트 엔진이 스크립트가 로딩되는 시점에 바로 초기화하고 이를 VO(variable object)에 저장한다. 즉, 함수 선언, 초기화, 할당이 한번에 이루어진다. 그렇기 때문에 함수 선언의 위치와는 상관없이 소스 내 어느 곳에서든지 호출이 가능하다.

다음은 함수 표현식으로 함수를 정의한 경우이다.

 

var res = square(5); // TypeError: square is not a function

var square = function(number) {
  return number * number;
}

함수 선언문의 경우와는 달리 TypeError가 발생하였다. 함수 표현식의 경우 함수 호이스팅이 아니라 변수 호이스팅이 발생한다.

변수 호이스팅은 변수 생성 및 초기화와 할당이 분리되어 진행된다. 호이스팅된 변수는 undefined로 초기화 되고 실제값의 할당은 할당문에서 이루어진다.

함수 표현식은 함수 선언문과는 달리 스크립트 로딩 시점에 변수 객체(VO)에 함수를 할당하지 않고 runtime에 해석되고 실행되므로 이 두가지를 구분하는 것은 중요하다.

JavaScript: The Good Parts의 저자이며 자바스크립트의 권위자인 더글러스 크락포드(Douglas Crockford)는 이와 같은 문제 때문에 함수 표현식만을 사용할 것을 권고하고 있다. 함수 호이스팅이 함수 호출 전 반드시 함수를 선언하여야 한다는 규칙을 무시하므로 코드의 구조를 엉성하게 만들 수 있다고 지적한다.

또한 함수 선언문으로 함수를 정의하면 사용하기에 쉽지만 대규모 애플리케이션을 개발하는 경우 인터프리터가 너무 많은 코드를 변수 객체(VO)에 저장하므로 애플리케이션의 응답속도는 현저히 떨어질 수 있으므로 주의해야 할 필요가 있다.

+ Recent posts