TIL

10일차 프로그래밍 기초 - 과제

김영재0412 2022. 11. 18. 15:20

🐤 JavaScript의 자료형과 JavaScript만의 특성은 무엇일까 ?

 

느슨한 타입(loosely typed)의 동적(dynamic) 언어

 

JavaScript는 느슨한 타입(loosely typed)의 동적(dynamic)언어이며, JavaScript의 변수는 어떤 특정 타입과 연결되지 않으며, 모든 타입의 값으로 할당 (및 재할당) 가능하다.

 

 

느슨한 타입은 뭘까?

 

느슨한 타입(loosely typing은 타입 없이 변수를 선언하는 것이며, 강력한 타입(strong typing)은 타입과 함께 변수를 선언해야만 한다. 

 

/* JavaScript(loose typing) */
let a = 13; // Number 선언
let b = "thirteen"; // String 선언

/* Java(strong typing) */
int a = 13; // 숫자 선언시 int가 필요
String b = "thirteen"; // 문자열 선언시 String이 필요

 

동적언어는 뭘까?

동적언어에 대해 알아보기 전에 정적언어에 대해 알아보자

 

 

정적 언어 (Statically Typed Language)

컴파일 시간에 변수의 타입이 결정되는 언어  즉, 자료형(타입)을 컴파일 시에 결정하는 언어이다. 대표적인 언어로 C, C++, Java가 있으며 정적 언어는 변수에 들어갈 값의 형태에 따라 자료형을 지정해주어야한다. 그렇지않을시 컴파일 에러가 발생하지만 컴파일 시간에 변수의 타입을 체크하므로 사소한 버그들을 쉽게 체크할 수 있어 타입 에러로 인한 문제점을 초기에 발견할 수 있어 타입의 안정성이 올라간다.

 

 

 

동적 언어 (Statically Typed Language)

컴파일이 아닌 런타임에 타입이 결정되는 언어 즉, 소스가 빌드될 때 자료형을 결정하는 것이 아닌 실행 시에 결정된다. 대표적인 언어로 JavaScript, Rubym Python이 있다. 매번 타입을 써줄 필요가 없기에 빠른 코드작성에 유리하지만 실행 도중에 변수에 예상치 못한 타입이 들어와 Type Error가 발생할 경우가 있다. 

 

 

더보기

 

 

JavaScript 형변환 (Type Casting)

 

JS는 타입이 매우 유연한  언어이기에 자바스크립트 엔진이 필요에 따라 암시적변환 혹은 개발자의 의도에 따라 명시적변환을 실행한다.

 

 

 

암시적 형 변환(Implicit Type Conversion)

 

 

산술연산자

 

더하기(+) 연산자는 숫자보다 문자열이 우선시 되기에 숫자형이 문자형을 만나면 문자형으로 변환하여 연산된다.

 

다른 연산자( - , * , /, %)는 숫자형이 문자형보다 우선시되기에 문자형으로 변환이 일어나지않는다.

 

 

동치 비교도 있지만 다음 ==, ===에서 설명하겠다.

 

 

명시적 형 변환(Explicit Type Conversion)

 

명시적 변환은 개발자가 의도를 가지고 데이터를 변환시키는 것이며 타입을 변경하는 방법은 아래와 같은 함수를 이용한다.

Object(), Number(), toString(), Boolean()

//new 연산자가 없을 시 타입을 변환하는 함수로 사용가능!

 

 

==, ===

==는 추상적같은 비교 , ===은 엄격한 비교이다.

==는 두 값을 공통형으로 반환하고 두 값이 같다면 true를 반환한다. (Only 값만 비교)

===은 ==와 다르게 공통형으로 반환하지않으며 둘이 서로 다른 형이면, false를 반환한다. (자료형 && 값 둘다 true여야함) 

 

let num = 0;
let obj = new String("0");
let str = "0";
let b = false;

console.log(num === num); // true
console.log(obj === obj); // true
console.log(str === str); // true

console.log(num === obj); // false
console.log(num === str); // false
console.log(obj === str); // false
console.log(null === undefined); // false
console.log(obj === null); // false
console.log(obj === undefined); // false

그러므로 ==는 버그를 유발하기 쉽기에 ===를 쓰는 것을 권장한다.

 

 

 

 

느슨한 타입의 동적언어의 문제점은 무엇이고 보완할 수 있는 방법에는 무엇이 있을지 생각해보세요.

 

실행 도중에 변수에 예상치 못한 타입이 들어와 타입에러가 발생할 수 있으며 동적언어는 런타임 시 확인할 수 밖에 없기 때문에, 코드가 길고 복잡해질 경우 타입 에러를 찾기가 어려워진다.


이러한 불편함을 해소하기 위해 TypeScript나 Flow 등을 사용할 수 있다.

 

 

타입스크립트란?

타입스크립트는 자바스크립트에 타입을 부여한 언어이며 자바스크립트의 확장된 언어라고 볼 수 있다. 타입스크립트는 자바스크립트와 달리 브라우저에서 실행하려면 파일을 한번 변환해주어야하며 이것을 컴파일(complile) 이라고 부른다.

 

타입스크립트를 사용하면 에러의 사전 방지를 할 수 있고 코드 가이드 및 자동완성을 해주기에 생산성이 향상된다.

 

//Javascript
function sum(a, b) {
  return a + b;
}

sum("10", "20") // "1020"

//TypeScript

function sum(a: number, b: number) {
  return a + b;
}

sum('10', '20'); // Error: '10'은 number에 할당될 수 없습니다.

 

플로우란?

Flow는 JavaScript 코드의 정적 유형 검사기이며 생산성을 높이기 위해 많은 작업을 수행한다.

 

// @flow
function square(n: number): number {
  return n * n;
}

square("2"); // Error!

 

 

undefined와 null의 미세한 차이들을 비교해보세요.

 

undefined

undefined는 원시값(Primitive Type)으로, 선언한 후에 값을 할당하지 않은 변수나 값이 주어지지 않은 인수에 자동으로 할당된다. 이 값은 전역 객체의 속성 중 하나로, 전역 스코프에서의 변수이기도 하기에 undefined 변수의 초기 값은 undefined 원시 값이다.

 

즉, 자료형이 없는 상태이다.

 

null

null은 원시값(Primitive Type) 중 하나로, 어떤 값이 의도적으로 비어있음을 표현한다. undefined는 값이 지정되지 않은 경우를 의미하지만, null의 경우에는 해당 변수가 어떤 객체도 가리키고 있지 않다는 것을 의미한다.

 

null == undefined // true
null === undefined // false

typeof null // 'object'
typeof undefined // 'undefined'

isNaN(1 + null) // false
isNaN(1 + undefined) // true

null == null // true
null === null // true
!null // true

 

 

 


 

 

🐤 JavaScript 객체와 불변성이란 ?

 

기본형 데이터와 참조형 데이터

 

기본형(Primitive Type)

String, Number, BigInt, Boolean, undefined, Symbol, null

기본형 데이터는 원시형 데이터라고도 불리며 총 7종류이며 변경 불가능한 값(immutable value)이다. 변경이 불가능하다는 뜻은 메모리 영역에서 변경이 불가능하다는 뜻이며 재할당은 가능하다. 재할당 시 기존 값이 변하는 것처럼 보일지라도 새로운 메모리에 재할당한 값이 저장되고 변수가 가리키는 메모리가 달라졌을 뿐이다.

 

참조형(Reference Type)

기본형 타입 이외의 모든 값은 참조형 객체 타입이며 대표적으로 배열, 함수, 객체가 있으며 변경 가능한 값(mutable value)이다. 즉 변수의 크기가 동적으로 변한다. 

 

 

불변 객체를 만드는 방법


기본형 데이터를 변경하면 데이터는 변하지 않으며 참조형 데이터도 데이터 자체를 변경하고자 하면 즉, 새로운 데이터를 할당한다면 기본형 데이터와 같이 기존 데이터는 변경되지 않는다. 그러나, 참조형 데이터인 객체를 복사해서 그 객체안의 내부 프로퍼티를 변경 할 일이 생긴다면 복사한 객체를 변경한다하더라도 기존의 객체의 프로퍼티는 변하지 않아야 하는 경우가 있다. 그러나 자바스크립트에서는 참조형 데이터인 내부 프로퍼티를 수정했을 때는 가변성이라는 성질로 인하여 기존의 객체도 변하게 된다. 이럴 때 불변 객체를 만들어 쓰면 원본 객체를 유지할 수 있다.

 

불변 객체 (Immutable Object)

 

불변객체는 한 번 객체가 생생되면, 변하지않는 객체를 의미한다.

 

const 키워드를 사용하면 상수 선언을 할 수 있다.  하지만 다른 언어의 상수 취급과 동일하지않기에 const는 할당된 값잉 상수가 되는 것이 아니고, 바인딩된 값이 상수가 된다.

 

위와 같이 const로 선언된 객체의 속성 변경이 가능한 이유는 실제 객체가 변경되는 것은 맞지만 const로 선언한 변수와 객체 사이의 바인딩은 변경되지 않기 때문이다.  따라서 변수 자체를 재할당하려는 경우에는 변수와 값 사이의 바인딩이 변경되어 오류가 발생하는 것이다.

프로그래밍에서 상수는 코드 내에서 개발자의 실수로 인해 값이 변경되지 않도록 변수를 보호하거나 다른 코드에서 실수로 이미 할당된 변수를 재할당하지 않도록 하는데 유용하기 때문에 많이 사용된다. 하지만 자바스크립트의 const로 객체를 선언할 경우에 객체의 속성은 언제든지 변경이 가능하기 때문에 immutable한 상수로 사용된다고 보기 어렵다. 그래서. const와 같이 유용하게 사용되는 것이 Object.freeze()다.

 

 

Object.freeze()

Object.freeze()는 동결된 객체를 만들 수 있으며 속성을 추가하거나 제거하는 동작이 불가능한 불변 객체를 만들 수 있다.

 

이렇게 객체의 속성을 변경하는 시도는 무시된다. 하지만 let으로 선언되었기에 재할당을 할 수 있다.

 

서로의 단점을 보완하기위해 const 와 Object.freeze를 같이 사용한다.

 

const와 Object.freeze()를 같이 사용해 속성 변경이 불가능한 immutable한 객체가 되었다.

 

 

얕은 복사와 깊은 복사

 

얕은 복사(shallow copy)

 

얕은 복사는 객체를 복사할 때 해당 객체만 복사하여 새 객체를 생성하고 변수는 원본 변수와 같은 참조(주소)값을 공유한다.

자바스크립트의 참조형은 얕은 복사가 된다. 즉, 데이터가 그대로 복사생성된 것이 아닌 해당 데이터의 참조값(메모리 주소)만 전달하여 한 데이터를 공유하는 것이다.

 

 

 

깊은 복사(deep copy)

 

깊은 복사는  객체를 복사할 때 변수까지 복사하는 방식이다.

 

변수 a를 새로운 b에 할당하였고 b값을 변경해도 기존의 a 값은 변경되지 않으며 두 값을 비교했을 때 false가 출력되는것을 보아 서로의 값은 단독으로 존재한다는 것을 알 수 있다.

 

즉, 자바스크립트의 기본 타입은 깊은 복사가 되며, 독립적인 메모리에 값 자체를 할당하여 생성한다.

 

 

 

 


 

 

 

 

🐤 호이스팅과 TDZ는 무엇일까 ? 

 

스코프(Scope)

 

스코프는 변수나 함수, 클래스가 접근할 수 있는 유효 범위를 말한다.

function foo() {
 let x;
}

// x의 스코프는 함수foo() 이다.

 

 

 

호이스팅(Hoisting)

 

호이스팅은 함수 안에 있는 선언들을 모두 끌어올려 해당 함수의 유효 스코프의 최상단에 선언하는 것을 말한다. var로 선언한 변수는 호이스팅 시 초기화하여 undefined가 되며, let과 const도 호이스팅 대상이지만 초기화하지는 않기에 에러가 일어난다. 

 

호이스팅은 JavaScript 인터프리터가 코드를 해석할 때 변수 및 함수의 선언 처리, 실제 코드 실행의 단계로 나눠 처리하기때문에 발생하는 현상이다.

 

console.log(name)

var name = '영재'

// undeinfed


//돌아가는 방식

var name

console.log(name)

var name = '영재'

 

이런식으로 호이스팅을 하기때문에 name의 값이 undefined가 된다.

 

 

함수 레벨 스코프

 

대부분의 프로그래밍 언어는 블록 레벨 스코프를 사용하지만 JavaScript는 var 키워드로 선언된 변수는 함수 레벨 스코프 내에서만 인정된다.

(function () {
 var local = 1;
 })();
 
 console.log(local)
 
 // Uncaught ReferenceError: local is not defined
 
 for ( var i = 0; i < 10; i++) {
	}
    
    console.log(i)
    
// 10

함수 스코프만 인정되기에 ReferenceError가 뜨고 for문은 참조 가능하다.

 

 

 

 

TDZ( Temporal Dead Zone)

 

TDZ는 한글로 직역하면 사각지대라는 뜻이며, 스코프의 시작시점부터 초기화 시작시점까지의 구간이다.

 

 

위 그림을 보면 TDZ는 변수 선언 및 초기화 하기 전 사이의 사각지대인걸 알 수 있다.

 

const, let, class는 TDZ에 영향을 받지만 var, function, import 구문은 영향을 받지않는다.

 

 

함수 선언문과 함수 표현식에서 호이스팅 방식의 차이

 

함수 선언문은 var와 같이 함수 스코프를 가지고 let과 const 변수는 블록 스코프를 갖는다. 또한 함수 선언식은 코드가 실행되기 전에 로드가 되지만, 함수 표현식은 인터프리터가 해당 코드 줄에 도달할 때 로드된다. 함수 선언식은 호이스팅 되지만, 함수 표현식은 호이스팅이 되지않으므로 정의된 범위에서 로컬 변수 복사본을 유지할 수 있다.

 

 

함수 선언식

console.log(hello())// Hello World

function hello() {
 return "Hello World"
}

함수 표현식

 

console.log(Hello()) // Uncaught ReferenceError: Hello is not defined

const Hello = function () {
 return "Hello world"
}

 

let, const, var, function 이 어떤 원리로 실행될까

var과 let과 const를 구분하는 가장 중요한 점은 값 변경 가능유무와 스코프 범위, 호이스팅 가능 유무이다.

 

우선 순의는 const -> let -> var 순이며 , const 쓰는걸 권장하고있다.

 

값 변경 가능 유무

 

var, let은 변수로 선언하기에 이후에도 값 변경이 가능하지만, const은 상수로 선언하기에 초기값을 변경 할 수 없다.

 

함수스코프 vs 블록스코프

 

var은 함수스코프를 가지며, let과 const는 블록 스코프를 가진다.

 

호이스팅 가능 유무

 

모두 호이스팅이 되지만 const, let은 TDZ의 영향을 받는다.

 

또한, JavaScript 변수는 인터프리터 내부에서 총 3단계에 걸쳐 생성된다. 

 

선언 (Declaration) : 스코프와 변수 객체가 생성되고 스코프가 변수 객체를 참조한다.
초기화 (Initialization): 변수 객체가 가질 값을 위해 메모리에 공간을 할당한다. 이때 초기화되는 값은 undefined이다.
할당 (Assignment): 변수 객체에 값을 할당한다.

 

var 키워드를 사용하면 선언한 객체의 경우 선언과 초기화가 동시에 이뤄지기에 undefined로 값이 초기화된다. 하지만 let과 const는 선언과 동시에 초기화가 되지않고 TDZ 구간에 들어가 선언은 되었지만 초기화가 되지 않아 변수에 담길 값을 위한 공간이 메모리에 할당되지않은 상태이다.

 

 

함수의 동작원리

 

함수를 실행하기 위해서는 스코프에서 값을 참조하기위해 이름(식별자)가 필요하다. 엔진이 함수 선언문과 만나면 식별자를 관리하는 특별한 집합인(EnviromentRecord)에 함수의 이름을 식별자로 넣고 함수 객체를 생성하여 참조한다. 그리고 함수 실행 구문 중 foo를 만나면 값을 스코프를 통해 가져온다. 그 다음 구문이 () 이므로 실행가능하다면 실행한다. 

 

스코프에서 식별자를 찾지 못 했면 ReferenceError를 출력하고, 식별자는 찾았지만 실행할 수 없는 타입이라면 TypeError를 출력한다.

 

실행 컨텍스트와 콜 스택

 

실행컨텍스트 (Execution Context)

 

자바스크립트 엔진이 코드를 실행하기 위해선 코드에 대한 정보들이 필요하다. 코드에 선언된 변수와 함수, 스코프, this, arguments 등을 묶어 코드가 실행되는 위치를 설명하는 것을 실행컨텍스트라 부른다. 자바스크립트 엔진은 Execution Context를 객체로 관리하며 코드를 실행컨텍스트로 실행한다.

 

실행컨텍스트의 종류

 

1. Global Execution Context
코드를 실행하며 단 한 개만 정의되는 전역 컨텍스트이다. global object를 생성하며 this 값에 global object를 참조한다. 전역 실행 컨텍스트는 Call Stack에 가장 먼저 추가되며 앱이 종료 될 때 삭제된다.

 

2. Functional Execution Context
함수가 실행 될 때 마다 정의되는 Context이다. 전역 실행 컨텍스트가 단 한 번만 정의되는 것과 달리, 함수 실행 컨텍스트는 매 실행시마다 정의되며 함수 실행이 종료되면 Call Stack에서 제거된다.

 

3. Eval Context
eval 함수로 실행한 코드의 Context이며 보안상 취약한 점이 있어 비권장 함수이다.

 

 

 

콜 스택(Call Stack)

Call Stack은 코드가 실행되면서 생성되는 Excution Context를 저장하는 자료구조이다.  엔진이 처음 script를 실행할 때, 글로벌 실행 컨텍스트를 생성하고 이를 Call Stack에 푸쉬한다. 그 엔진이 함수를 호출할 때 마다 함수를 위한 실행 컨텍스트를 생성하고 이를 콜스택에 푸쉬한다. 자바스크립트 엔진은 Call Stack의 Top에 위치한 함수를 실행하며 함수가 종료되면 스택에서 제거하고 제어를 한 다음 Top에 위치한 함수로 이동한다.

 

function first() {
    console.log('first')
    second();
}

function second() {
    console.log('second');
}

first();

 

실행 흐름

  • step1: 엔진이 처음 자바스크립트 파일을 실행시키며 Global Execution context 를 생성하고 Call Stack에 추가합니다.
  • step2: 엔진이 first 함수 호출코드를 발견하고 first 함수를 위한 Execution context 를 생성하고 Call Stack에 추가합니다.
  • step3: first 함수 내부에서 console.log 함수를 발견하고 이를 위한 Execution context 를 생성하고 Call Stack에 추가합니다.
  • step4: console.log(‘first’) 함수가 종료된 후 Call Stack에서 제거 됩니다.
  • step5: second 함수 호출시에 second 함수를 위한 Execution context 를 생성하고 Call Stack에 추가합니다.
  • step6: second 함수 내부에서 console.log 함수를 발견하고 이를 위한 Execution context 를 생성하고 Call Stack에 추가합니다.
  • step7: console.log(‘second’) 함수가 종료된 후 Call Stack에서 제거 됩니다.
  • step8: second 함수가 종료된 후 Call Stack에서 제거 됩니다.
  • step9: first 함수가 종료된 후 Call Stack에서 제거 됩니다.
  • step10: 모든 코드의 실행이 끝난 뒤에 자바스크립트 엔진은 Call Stack에서 Global Execution context를 제거합니다.

 

 

 

 

 

 

 

스코프 체인, 변수 은닉화

 

 

스코프체인(Scope Chain)

 

스코프는 함수의 중처벵 의해 계층적 구조를 가진다. 변수를 참조할 때, 자바스크립트 엔진은 스코프 체인을 통해 변수를 참조하는 코드의 스코프에서 시작하여 상위 스코프로 이동하면서 선언된 변수를 검색한다. 이러한 스코프를 안에서부터 바깥으로 차례차례 검색해 나는 것을 스코프체인이라한다.

 

 

변수 은닉화(Variable shadowing)

 

외부 객체로부터 속성 값(데이터, 멤버 변수값)을 감추는 특성이며 접근 제어자 private로 감춘다. 

 

Private를 사용하는 이유는 외부로부터 데이터를 보호(캡슐화)하기 위해서 인스턴스 변수는 private로 하여 외부에서 접근하지 못하도록 하고, 메서드는 public으로 하여 직접 접근은 막고 메서드를 통한 간접 접근 하용하게한다. 

 

 

 


 

🐤 실습 과제

 

콘솔에 찍힐 b 값을 예상해보고, 어디에서 선언된 “b”가 몇번째 라인에서 호출한 console.log에 찍혔는지, 왜 그런지 설명해보세요. 주석을 풀어보고 오류가 난다면 왜 오류가 나는 지 설명하고 오류를 수정해보세요.

 

let b = 1;

function hi () {

const a = 1;

let b = 100;

b++;

console.log(a,b);

}

//console.log(a);

console.log(b);

hi();

console.log(b);

 

 

console.log(b)는 전역 변수로 let b = 1; 이라고 선언했기에 1이다.

hi() 함수에 있는 console.log(a,b)는 함수 내에서 a와 b가 선언되었기에 a는 1, b는 101이다.

그 다음 console.log(b)는 마찬가지로 1이다.

 

console.log(a)는 함수 내부에서 a를 선언했기에 에러가 나기에 let b = 1; 처럼 전역변수로 선언해줘야한다.

 

 

 

 

 

두 값이 다른 이유를 설명해보세요.

 

1 == "1";
1 === "1";

== 는 값만 비교하기에  1 == "1"; 은 true가 나온다.

===는 자료형, 값 둘다 비교하기에 1은 number, "1"은 String이기에 false가 나온다. 

'TIL' 카테고리의 다른 글

11일차 - Application Programming Interface (API)  (0) 2022.11.20
11일차 - JSON Web Token  (0) 2022.11.20
10일차 프로그래밍 기초  (0) 2022.11.18
5일차 JavaScript 챕터 5 - 함수 고급  (0) 2022.11.12
4일차 JavaScript 챕터 5 - 함수  (0) 2022.11.11