본문 바로가기

자료집

React 입문주차 S.A.

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

 

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

1. 느슨한 타입과 강력한 타입

1)느슨한 타입

느슨한 형식의 언어  변수 를 정의할 필요가 없는 프로그래밍 언어 입니다. 예를 들어 Perl(해석 형 무료 오픈 소스 프로그래밍 언어) 은 느슨한 형식의 언어이므로 변수를 선언 할 수 있지만 변수의 형식을 분류할 필요는 없습니다. 아래 예에서 첫 번째 줄은 정수 또는 문자열 로 사용할 수 있는 $test 변수를 선언합니다 .

내 $test;
$ 테스트 = 1; # 테스트 변수는 이제 정수입니다.
$test = "안녕하세요"; # 테스트 변수는 이제 문자열입니다.

2)강력한 타입

변수 를 정의 해야 하는 프로그래밍 언어 와 그 변수입니다. 예를 들어 C  강력한 형식의 언어 입니다. 변수를 선언 할 때 변수 유형도 지정해야 합니다.

 

2. 동적인 언어와 정적인 언어

1)동적인 언어

동적으로 유형이 지정된 언어에서 유형 검사는 런타임 또는 실행 시간 에 발생 합니다. 이것은 프로그램이 실행 중일 때만 변수가 유형에 대해 검사된다는 것을 의미합니다. 이 범주에 속하는 프로그래밍 언어의 몇 가지 예는 Python, JavaScript, Lisp, PHP, Ruby, Perl, Lua 및 Tcl입니다.

 

2)정적인 언어

정적으로 유형이 지정된 프로그래밍 언어에서 유형 검사는 컴파일 시간 에 발생합니다 . 컴파일 타임에 특정 프로그래밍 언어의 소스 코드는 기계가 읽을 수 있는 형식으로 변환됩니다. 이것은 소스 코드가 컴파일되기 전에 각각의 모든 단일 변수와 관련된 유형을 알아야 함을 의미합니다.

이 범주에 속하는 프로그래밍 언어의 몇 가지 일반적인 예는 Java, Haskell, C, C++, C#, Scala, Kotlin, Fortran, Go, Pascal 및 Swift입니다.

 

  • JavaScript 형변환

JavaScript의 형변환은 두가지로 나눌 수 있다. 그리고 이는 아주 유연한 언어의 특징이라고 볼 수 있는데 JavaScript가 필요에 따라 '암시적 변환' 또는 '명시적 변환'을 실행하기 때문이다.

 

-형변환의 종류-

먼저 명시적 변환은 개발자가 의도를 가지고 데이터를 변환시키는 것을 말한다.

1. 타입을 변경하는 기본적인 방법은 'Object(), Number(), String(), Boolean()'과 같은
함수를 이용한다. 이 함수들은 새로운 연산자가 없다면 사용한 함수의 타입을 변환하는 함수로 사용한다.


2. 암시적 변환은 JavaScript 엔진이 필요에 따라 자동으로 데이터 타입을 변환시키는 것을 말한다.

예를 들어 산술 연산자(+), 기타 다른 연산자(-, *, /, %), 동치비교 를 들 수있다.

 

  • ==, === 의 차이

==(동등연산자)와 ===(일치연산자)의 가장 큰 차이는 타입변환의 시도 여부라고 볼 수 있다. 일치 연산자는 다른 타입을 가진 피연산자는 다르다고 판단한다.

예를들어

동등 연산자는

1 == 1; -> ture
"hello" == "hello"; -> ture

1 == "1"; -> ture 모두 참 값을 가지게 되고

 

일치 연산자는

1 === 1; -> ture
"hello" === "hello"; -> ture

1 === "1"; -> false 값을 가지게 되는 차이 점이 있다.

 

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

1. 문제점

실행 도중에 변수에 예상치 못한 타입이 들어와 타입에러가 발생할 수 있음

동적타입 언어는 런타임 시 확인할 수 밖에 없기 때문에, 코드가 길고 복잡해질 경우 타입 에러를 찾기가 어려워 집니다.

 

2. 보완 방법

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

 

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

undefined은 변수를 선언하고 값을 할당하지 않은 상태, null은 변수를 선언하고 빈 값을 할당한 상태(빈 객체)이다.

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

 

따라서 typeof를 통해 자료형을 확인해보면 null은 object로, undefined는 undefined가 출력되는 것을 확인할 수 있다.

 

 🐤 JavaScript 객체와 불변성이란 ?

 

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

-기본형 데이터-

1. Boolean

boolean은 논리적 요소를 나타내며 true, false 두 가지 값을 가진다.
true, false를 작성할 때는 쌍따옴표( "" )없이 작성해야 한다.

2. null 과 undefined

null은 변수를 선언한 뒤에 null이라는 빈 값을 할당한 것이고,
undefined는 변수를 선언한 뒤에 값을 할당하지 않아 자료형이 정해지지 않은 상태이다.

null의 자료형은 primitive 타입인데 typeof null을 찍어보면 object가 나오게 된다.
이는 수정될 수 없는 버그인데, 보다 자세한 설명은 링크로 대체함

3. Number

정수형을 표현하는 자료형은 따로 존재하지 않고,
모든 숫자는 IEEE 754 값 ( 유효 범위: -(2^53 - 1) ~ (2^53 -1) ) 단 하나만 존재한다.
즉 64비트로 표현되며, 1비트는 부호, 11비트는 지수부, 52비트는 가수부를 나타낸다.
이외에도 특수값으로는 Infinity, -Infinity, NaN ( Not a Number ), -0 이 있고,
매우 작은 수를 나타내는 Number.EPSILON이 있다.

4. String

텍스트 데이터를 나타내는데 사용된다. 16비트 부호없는 정수 값 요소들의 집합
배열과 같이 index를 활용한 사용이 가능하다.

5. Symbol

생성시 마다 새로운 메모리 공간에 생성하기 때문에 고유한 값을 나타낼 때 사용된다.
사실 개발을 하면서 언제써야할지 잘 모르겠다.

 

-참조형 데이터-

-기본형 데이터 이외 모든 데이터

1. Normal Object

키(key)와 값(value)의 쌍으로 이루어져 있다.
키는 문자열 또는 심볼이고 값은 어떤 값이나 들어갈 수 있다.

2. Array

정수키를 가지는 일련의 값들을 표현하기 위한 Object이다.
리스트나 집합을 표현하는데 적합한 자료형이다.

3. Map

Object와 비슷하나, 다양한 타입으로 key를 정의할 수 있고,
순서가 보장된다.

4. Set

Array와 비슷하나, 중복값은 제거되는 특성이 있다.

5. function

함수는 일반 오브젝트에서 호출 가능한 특성을 추가한 오브젝트이다.

 

그외 기타 등등

 

  • JavaScript 형변환

문자열로 변환

-숫자를 문자열로 변환(number to string)-

1. (숫자).toString()

: Object.prototype.toString 메서드를 활용하는 방법

(111).toString() // "111"
(NaN).toString() // "NaN"
(Infinity).toString() // "Infinity"

2. String(숫자)

: String 생성자 함수를 new 없이 활용하는 방법

String(111) // "111"
String(NaN) // "NaN"
String(Infinity) // "Infinity"

3. 숫자 + ""

: 문자열 연결 연산자(+) 활용하는 방법. 따옴표로 감싼 빈 문자열을 더해주면 형변환이 발생한다.

111 + "" // "111"
NaN + "" // "NaN"
"" + Infinity // "Infinity"

"" + 숫자 형식, 숫자 + "" 형식 모두 동일하다.

 

-불리언을 문자열로 변환(boolean to string)-

1. toString(불리언)

: Object.prototype.toString 메서드를 활용하는 방법

(true).toString() // "true"
(false).toString() // "false"

2. String(불리언)

: String 생성자 함수를 new 없이 활용하는 방법

String(true) // "true"
String(false) // "false"

3. "" + 불리언

: 문자열 연결 연산자(+) 활용하는 방법. 따옴표로 감싼 빈 문자열을 더해주면 형변환이 발생한다.

true + "" // "true"
"" + false // "false"

("" + 불리언 형식, 불리언 + "" 형식 모두 동일하다.)

숫자열로 변환

-문자열를 숫자로 변환(string to number)-

("111" 처럼 숫자로 된 문자열만 변환 가능하다. 숫자가 아닌 경우, NaN 반환)

 

1. parseInt(정수 문자열) || parseFloat(실수 문자열)
: 소수가 없는 정수는 parseInt(), 소수가 있는 실수는 parseFloat()로 형변환.

parseInt("11") // 11
parseFloat("11.55") // 11.55

parseInt("NaN") // NaN
parseFloat("NaN") // NaN

parseInt("Infinity") // Infinity
parseFloat("Infinity") // Infinity

parseInt("Hello") // NaN

(NaN, Infinity 는 parseInt(), parseFlaot() 둘다 적용 가능)

2. Number(문자열)
: Number 생성자 함수를 new 없이 활용하는 방법

Number("11"); // 11

Number("11.55"); // 11.55

Number("Hello"); // NaN

3. +(문자열)
: 문자열 앞에 + 연산자를 붙여주면, 숫자로 형변환이 된다.

("11") // 11

"11.55" // 11.55

"-11" // -11

"Hello" // NaN

(괄호는 상황에 따라 넣으면 된다.)

4. (문자열) * 1

: 문자열에 *연산을 하면, 숫자로 형변환이 된다.

"11" * 1 // 11

"11.55" * 1 // 11.55

"-11" * 1 // -11

"Hello" * 1 // NaN

 

-불리언을 숫자로 변환(boolean to number)-

( true → 1, false → 0 으로 변환되며, parseInt(), parseFloat()는 적용 불가하다.)

 

1. Number(불리언)

: Number 생성자 함수를 new 없이 활용하는 방법

Number(true) // 1

Number(false) // 0

2. +(불리언)
: 불리언 앞에 + 연산자를 붙여주면, 숫자로 형변환이 된다.

(true) // 1

false // 0

(괄호는 상황에 따라 넣으면 된다.)

3. (불리언) * 1

: 불리언에 * 연산을 하면, 숫자로 형변환이 된다.

(true) * 1 // 1

false * 1 // 0

(괄호는 상황에 따라 넣으면 된다.)

 

불리언으로 변환

-불리언으로 타입변환 방법 2가지-

(숫자, 문자열, 객체 등은 불리언 타입으로 변환 가능하다.)

 

1. Boolean( 숫자 || 문자열 || 객체 || undefined || null )

: Boolean() 생성자 함수를 new 연산자 없이 호출하여 불리언 타입으로 변환하는 방법

2. !! ( 숫자 || 문자열 || 객체 || undefined || null )

: 부정 논리연산자(!)를 연달아 두번 사용해서 불리언 타입으로 변환하는 방법

1) number to boolean

: 0, NaN은 false, 나머지는 모두 true.

Boolean(0); // false
Boolean(1); // true
Boolean(-11.55); // true

Boolean(NaN); // false

Boolean(Infinity); // true
Boolean(-Infinity); // true

!!(0); // false
!!(1); // true
!!(-11.55); // true

!!(NaN); // false

!!(Infinity); // true
!!(-Infinity); // true

(!! 사용시, 괄호는 상황에 맞게 사용하면 된다.)

2) string to boolean

: 빈문자열('')은 false, 나머지는 모두 true.

Boolean(''); // false
Boolean(' '); // true

Boolean('hello'); // true

Boolean('true'); // true
Boolean('false'); // true

!! (''); // false
!! (' '); // true

!! ('hello'); // true

!! ('true'); // true
!! ('false'); // true

(공백도 true인 점 주의!)

3) object to boolean

: 배열, 객체는 모두 true

Boolean({}); // true (객체)

Boolean([]); // true (배열)

!!({}); // true (객체)

!!([]); // true (배열)

(중괄호 {} 는 객체를 생성할 때, 대괄호 []는 배열을 생성할 때 사용한다.)

4) undefined, null to boolean

: undefined, null은 모두 false

Boolean(undefined); // false

Boolean(null); // false

!!(undefined); // false

!!(null); // false

 

  • 불변 객체를 만드는 방법

(할당된 객체가 변하지 않는 객체를 불변 객체라 한다)

const

자바스크립트 키워드 중 하나인 const이다. ES6문법부터 let과 const를 지원한다.

const 키워드는 변수를 상수로 선언할 수 있다, 일반적으로 상수로 선언된 변수는 값을 바꾸지 못하는 것으로 알려져 있다.

그렇다면 상수로 선언한 객체는 불변 객체일까?

const test = {};
test.name = "ginam";

console.log(test); // {"ginam"}

ES6에서의 const는 할당된 값이 상수가 되는 것이 아닌 바인딩된 값이 상수가 되는, 즉 test변수가 상수가 되기 때문에 const 키워드로 선언된 test변수에는 객체 재할당은 불가능하지만 객체의 속성은 변경 가능하다.

재할당이 불가능 한 이유는 변수와 값(객체) 사이의 바인딩 자체가 변경이 되기 때문에 상수인 test변수는 재할당이 불가능한 것이고

객체의 속성이 변경가능 한 이유는 실제 객체가 변경은 되지만 ( {} -> name : "mingyo" ) 객체와 변수(test)사이의 바인딩은 변경이 되지 않기 때문에 객체의 속성은 변경가능한 것이다.

그래서 비록 재할당은 불가능하지만 객체의 속성을 변경함으로 인해 변수에 바인딩된 객체의 내용까지 변경이 되기 때문에 불변객체라고 하기는 힘들다.

Object.freeze() 

자바스크립트에서 기본적으로 제공하는 메소드인 Object.freeze() 메소드이다. 공식 문서에 "객체를 동결하기 위한 메소드" 라고 적혀있다.

그렇다면 이 메소드를 사용하면 불변 객체를 만들 수 있을까?먼저 이 메소드의 사용법부터 알아보면,

let test = {
name : 'yoon'
}

Object.freeze(test);
사용법은 간단하다. test 변수에 key value를 가진 객체를 바인딩 후 Object.freeze(test)를 사용해 바인딩된 변수를 동결 객체로 만들었다. 때문에 test 객체는 객체의 속성을 변경하는 시도는 불가능하다.

test.name = 'Gi';
console.log(test) // {name: 'yoon'}
위와 같이 객체의 속성을 변경하는 시도는 무시된다.

그러나 Object.freeze()는 동결된 객체를 반환하지만 객체의 재할당은 가능하다.

test = {
age : 30
};
console.log(test); // {age: 30}
위와 같이 객체의 재할당은 가능하기 때문에 Object.freeze()도 불변 객체라고 할 수는 없다.

 

const와 Object.freeze()를 조합

(const의 재할당불가 + Object.freeze()의 객체속성 변경불가)

 

그렇다면 아래 코드와 같이 사용하면 된다.

const test = {
'name' : 'gi'
};

Object.freeze(test);
먼저 const키워드로 바인딩 된 변수를 상수화 시킨 다음, Object.freeze()로 해당 변수를 동결 객체를 만들면

객체의 재할당과 객체의 속성 둘 다 변경불가능한 불변 객체가 된다.

 

  • 얕은 복사와 깊은 복사

얕은 복사(shallow copy)란?

(객체의 참조값(주소 값)을 복사)

 

const obj1 = { a: 1, b: 2};
const obj2 = obj1;
console.log( obj1 === obj2 ); // true

위의 예시처럼 객체를 직접 대입하는 경우 참조에 의한 할당이 이루어지므로
둘은 같은 데이터(주소)를 가지고 있다. 이것이 얕은 복사이다.

const obj1 = { a:1, b:2 };
const obj2 = obj1;
obj2.a = 100;
console.log( obj1.a ); // 100

위 두 객체는 같은 데이터(주소)를 가지고 있고, 그래서 같은 주소를 참조하고 있다.
때문에 obj2의 property를 수정하고, obj1를 출력해도 obj2 값과 동일하다.

깊은 복사(deep copy)란?

(객체의 실제 값을 복사)

 

1. JSON.parse && JSON.stringify

1)JSON.parse() 메서드는 JSON 문자열의 구문을 분석하고, 그 결과에서 JavaScript 값이나 객체를 생성합니다. 선택적으로, reviver 함수를 인수로 전달할 경우, 결과를 반환하기 전에 변형할 수 있습니다.

2)JSON.stringify() 메서드는 JavaScript 값이나 객체를 JSON 문자열로 변환합니다. 선택적으로, replacer를 함수로 전달할 경우 변환 전 값을 변형할 수 있고, 배열로 전달할 경우 지정한 속성만 결과에 포함합니다.

3)JSON.stringify() 메서드는 JavaScript 값이나 객체를 JSON 문자열로 변환합니다. 선택적으로, replacer를 함수로 전달할 경우 변환 전 값을 변형할 수 있고, 배열로 전달할 경우 지정한 속성만 결과에 포함합니다.

 

const object = {

  a: "a",

  number: {

    one: 1,

    two: 2,

  },

  arr: [1, 2, [3, 4]],

};

 

const copy = JSON.parse(JSON.stringify(object));

copy.number.one = 3;

copy.arr[2].push(5);

 

console.log(object === copy); // false

console.log(object.number.one === copy.number.one); // false

console.log(object.arr === copy.arr); // false

 

console.log(object); // { a: 'a', number: { one: 1, two: 2 }, arr: [ 1, 2, [ 3, 4 ] ] }

console.log(copy); // { a: 'a', number: { one: 3, two: 2 }, arr: [ 1, 2, [ 3, 4, 5 ] ] }

 

2. 재귀 함수를 구현한 복사

함수가 자신을 다시 호출하는 구조로 만들어진 함수이다. 재귀함수는 종료조건이 있어야 하며, 종료조건을 설정해주지 않으면 무한 반복을 하게된다. 재귀함수로 작성이 되는 코드는 반복문으로도 작성할 수 있다.

 

const object = {

  a: "a",

  number: {

    one: 1,

    two: 2,

  },

  arr: [1, 2, [3, 4]],

};

 

function deepCopy(object) {

  if (object === null || typeof object !== "object") {

    return object;

  }// 객체인지 배열인지 판단

  const copy = Array.isArray(object) ? [] : {};

 

  for (let key of Object.keys(object)) {

    copy[key] = deepCopy(object[key]);

  }

 

  return copy;

}

 

const copy = deepCopy(object);

 

copy.number.one = 3;

copy.arr[2].push(5);

 

console.log(object === copy); // false

console.log(object.number.one === copy.number.one); // false

console.log(object.arr === copy.arr); // false

 

console.log(object); // { a: 'a', number: { one: 1, two: 2 }, arr: [ 1, 2, [ 3, 4 ] ] }

console.log(copy); // { a: 'a', number: { one: 3, two: 2 }, arr: [ 1, 2, [ 3, 4, 5 ] ] }

 

3. Lodash 라이브러리 사용

1)Lodash 란? 자바스크립트 유틸리티 라이브러리

array, collection, date, number, object 등이 있으며 데이터를 쉽게 다루 수 있도록 도와준다.

자바스크립트에서 배열 안의 객체들의 값을 핸들링할 때 유용

 

Array: 배열에만 사용 가능한 함수

Object: 객체에만 사용 가능한 함수

Collection: 배열과 객체 모두에 사용 가능한 함수

Util: 유틸리티 관련 함수

Seq: jquery의 메서드 체인처럼 여러 개의 펑션들을 연달아서 사용 가능

 

2) 라이브러리를 사용하면 더 쉽고 안전하게 깊은 복사를 할 수 있습니다. 설치를 해야 한다는 점과 일반적인 개발에는 효율적이겠지만, 코딩 테스트에는 사용할 수 없다는 것이 단점입니다.

const deepCopy = require("lodash.clonedeep")

 

const object = {

  a: "a",

  number: {

    one: 1,

    two: 2,

  },

  arr: [1, 2, [3, 4]],

};

 

const copy = deepCopy(object);

 

copy.number.one = 3;

copy.arr[2].push(5);

 

console.log(object === copy); // false

console.log(object.number.one === copy.number.one); // false

console.log(object.arr === copy.arr); // false

 

console.log(object); // { a: 'a', number: { one: 1, two: 2 }, arr: [ 1, 2, [ 3, 4 ] ] }

console.log(copy); // { a: 'a', number: { one: 3, two: 2 }, arr: [ 1, 2, [ 3, 4, 5 ] ] }

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

 

 

  • 스코프, 호이스팅, TDZ

1. 스코프

(변수에 접근할 수 있는 범위)
자바스크립트에선 스코프는 2가지 타입이 있습니다. 바로 global(전역)과 local(지역) 인데요.


전역 스코프(Global Scope)는 말 그대로 전역에 선언되어있어 어느 곳에서든지 해당 변수에 접근할 수 있다는 의미이고 지역 스코프(Local Scope)는 해당 지역에서만 접근할 수 있어 지역을 벗어난 곳에선 접근할 수 없다는 의미입니다.

 

자바스크립트에서 함수를 선언하면 함수를 선언할 때마다 새로운 스코프를 생성하게 됩니다. 그러므로 함수 몸체에 선언한 변수는 해당 함수 몸체 안에서만 접근할 수 있는데요. 이걸 함수 스코프(function-scoped)라고 합니다. 함수 스코프가 바로 지역 스코프의 예라고 할 수 있습니다.

 

2. 호이스팅

1)호이스팅의 뜻

자바스크립트 함수는 실행되기 전에 함수 안에 필요한 변수값들을 모두 모아서 유효 범위의 최상단에 선언한다.
자바스크립트 Parser가 함수 실행 전 해당 함수를 한 번 훑는다.
함수 안에 존재하는 변수/함수선언에 대한 정보를 기억하고 있다가 실행시킨다.
유효 범위: 함수 블록 {} 안에서 유효
즉, 함수 내에서 아래쪽에 존재하는 내용 중 필요한 값들을 끌어올리는 것이다.
실제로 코드가 끌어올려지는 건 아니며, 자바스크립트 Parser 내부적으로 끌어올려서 처리하는 것이다.
실제 메모리에서는 변화가 없다.

2)호이스팅의 대상

var 변수 선언과 함수선언문에서만 호이스팅이 일어난다.
var 변수/함수의 선언만 위로 끌어 올려지며, 할당은 끌어 올려지지 않는다.
let/const 변수 선언과 함수표현식에서는 호이스팅이 발생하지 않는다.

 

3. TDZ(Temporal Dead Zone)

(일시적 사각지대-스코프의 시작 부터 초기화 시작 지검까지의 구간)

 

 

 

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

1. 함수 선언문과 함수 표현식의 차이

1)함수선언문(Function Declaration)

일반적인 프로그래밍 언어에서의 함수 선언과 비슷한 형식

function 함수명() {
  구현 로직
}
// 함수의 호출.
function printName(firstname) {
  var myname = "HEEE";
  return myname + " " +  firstname;
}

2)함수표현식 (Function Expression)
변수값에 함수 표현을 담아 놓은 형태
유연한 자바스크립트 언어의 특징을 활용한 선언 방식
함수표현식은 익명 함수표현식과 기명 함수표현식으로 나눌 수 있다.
일반적으로 함수표현식이라고 부르면 앞에 익명이 생략된 형태라고 볼 수 있다.
익명 함수표현식: 함수에 식별자가 주어지지 않는다.
기명 함수표현식: 함수의 식별자가 존재한다.
함수표현식의 장점
클로져로 사용
콜백으로 사용(다른 함수의 인자로 넘길 수 있음)

var test1 = function() { // (익명) 함수표현식
  return '익명 함수표현식';
}

var test2 = function test2() { // 기명 함수표현식 
  return '기명 함수표현식';
}

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

함수선언문은 호이스팅에 영향을 받지만, 함수표현식은 호이스팅에 영향을 받지 않는다.
함수선언문은 코드를 구현한 위치와 관계없이 자바스크립트의 특징인 호이스팅에 따라 브라우저가 자바스크립트를 해석할 때 맨 위로 끌어 올려진다.
함수표현식은 함수선언문과 달리 선언과 호출 순서에 따라서 정상적으로 함수가 실행되지 않을 수 있다.
함수표현식보다 함수선언문을 더 자주 사용하지만, 어떤 코딩컨벤션에서는 함수표현식을 권장하기도 한다.
즉, 어떤 컨벤션을 갖던지 한가지만 정해서 사용하는 게 좋다.

/* 정상 */
function printName(firstname) { // 함수선언문
    var inner = function() { // 함수표현식
        return "inner value";
    }

    var result = inner();
    console.log("name is " + result);
}

printName(); // "name is inner value"
/* 오류 */
function printName(firstname) { // 함수선언문
    console.log(inner); // "undefined": 선언은 되어 있지만 값이 할당되어있지 않은 경우
    var result = inner(); // ERROR!!
    console.log("name is " + result);

    var inner = function() { // 함수표현식 
        return "inner value";
    }
}
printName(); // TypeError: inner is not a function

/** --- JS Parser 내부의 호이스팅(Hoisting)의 결과 --- */
/* 오류 */
function printName(firstname) { // 함수선언문
    var inner; // Hoisting - 변수값을 끌어올린다. (선언은 되어 있지만 값이 할당되어있지 않은 경우)
    console.log(inner); // "undefined"
    var result = inner(); // ERROR!!
    console.log("name is " + result);

    inner = function() { // 함수표현식 
        return "inner value";
    }
}
printName(); // TypeError: inner is not a function

(Q. “printName이 is not defined” 이라고 오류가 나오지 않고, function이 아니라는 TypeError가 나오는 이유?
 A. printName이 실행되는 순간 (Hoisting에 의해) inner는 ‘undefined’으로 지정되기 때문
 -inner가 undefined라는 것은 즉, 아직은 함수로 인식이 되지 않고 있다는 것을 의미한다.)

 

  • 여러분이 많이 작성해온 let, const, var, function 이 어떤 원리로 실행되는지 알 수 있어요.

변수 선언의 3단계

javascript에서의 변수는 위의 사진처럼 선언, 초기화, 할당이라는 3가지 단계의 걸쳐서 생성됩니다.

  • 선언 단계(Declaration phase) : 변수를 실행 컨텍스트의 변수 객체에 등록하는 단계를 의미합니다. 이 변수 객체는 스코프가 참조하는 대상이 됩니다.
  • 초기화 단계(Initialization phase) : 실행 컨텍스트에 존재 하는 변수 객체에 선언 단계의 변수를 위한 메모리를 만드는 단계 입니다. 이 단계에서 할당된 메모리에는 undefined로 초기화 됩니다.
  • 할당 단계(Assignment phase) : 사용자가 undefined로 초기화된 메모리의 다른 값을 할당하는 단계 입니다. 

여태까지 저희는 var혹은 let/const를 그냥 사용하였지만

사실은 위 3가지 단계를 거쳐서 생성되는 것 이었습니다.

그리고 var와 let/const의 차이는 이 3가지 단계의 순서에 차이가 존재합니다.

 

1. 먼저 var 변수의 라이프사이클을 알아보도록하겠습니다.

 

위 사진은 var 키워드 변수의 라이프 사이클 입니다.

var 키워드 변수는 변수 선언전에 선언 단계와 초기화 단계를 동시에 진행합니다.

그래서 javascript는 실행 컨텍스트 변수 객체의 변수를 등록하고 메모리를 undefined로 만들어 버립니다.

그렇기 때문에 변수를 선언하기 전에 호출을 해도 undefined로 호출이 되는 호이스팅이 발생하는 것 입니다.

 

2. 그렇다면 let의 라이프 사이클을 한번 봐보도록 하겠습니다.

let으로 선언된 변수는 var 키워드와는 다르게 선언단계와 초기화 단계가 분리되어서 진행이 됩니다.

그렇기 때문에 실행 컨텍스트에 변수를 등록했지만,

메모리가 할당이 되질 않아 접근할 수 없어 참조 에러(ReferenceError)가 발생하는 것 이고,

이것을 보고 우리가 호이스팅이 되질 않는다!! 라고 오해할 수 밖에 없었던 것 입니다.

 

그럼 우리가 처음 말했던 TDZ에 대해서 다시한번 알아보도록 하겠습니다.

TDZ는 스코프의 시작 지점부터 초기화 시작 지점까지의 구간을 말합니다.

어때요 이제는 위 말이 잘 이해가 가실거라 생각이 듭니다.

즉, let 또한 선언전, 실행 컨텍스트 변수 객체에 등록이 되어 호이스팅이 되지만,

이 TDZ 구간에 의해 메모리가 할당이 되질 않아 참조 에러(ReferenceError) 발생하는 것 입니다.

 

3. 참고로 function 키워드 함수는 아래 사진과 같이 변수선언 3단계를 동시에 진행해 버립니다.

  • 실행 컨텍스트와 콜 스택

1. 실행 컨텍스트

실행할 코드에 제공할 환경 정보들을 모아놓은 객체
자바스크립트의 동적 언어로서의 성격을 가장 잘 파악할 수 있는 개념
자바스크립트는 실행 컨텍스트가 활성화되는 시점에 다음과 같은 현상이 발생한다.

호이스팅이 발생한다(선언된 변수를 위로 끌어올린다)
외부 환경 정보를 구성한다
this 값을 설정한다.
이로 인해 다른 언어에서 발견할 수 없는 특이한 현상들이 발생한다.

#2. 실행 컨텍스트 구성
실행 컨텍스트는 다음과 같은 것들을 이용하면 call stack에 쌓이게 된다.

전역공간은 자동으로 컨텍스트로 구성된다.
함수를 실행한다.
eval()함수를 실행한다.
block을 만든다 (ES6+)
일반적으로 함수를 이용한 실행 컨텍스트를 사용한다.

var a = 1; // 전역 컨텍스트
function outer () { // outer 컨텍스트
  function inner () { // inner 컨텍스트
    console.log(a); // undefined
    var a = 3;
    console.log(a); // 3
  }
  inner();
  console.log(a); // 1
}
outer();
console.log(a); // 1

위와 같이 코드를 구성했을 때 실행 컨텍스트의 스택은 다음과 같은 순서로 실행된다.

프로그램 실행: [전역컨텍스트]
outer 실행: [전역컨텍스트, outer]
inner 실행: [전역컨텍스트, outer, inner]
inner 종료: [전역컨텍스트, outer]
outer 종료: [전역컨텍스트]
그리고 이러한 실행컨텍스트를 구성할 때 생기는 것들이 있다.

VariableEnvironment
현재 컨텍스트 내의 식별자(변수)들에 대한 정보
외부 환경 정보
선언 시점의 LexicalEnvironment의 스냅샷(변경사항 반영 X)
LexicalEnvironment
처음에는 VariableEnvironment와 같음
변경 사항이 실시간으로 반영됨
ThisBinding
식별자가 바라봐야 할 대상 객체

 

2. 콜 스텍

(콜스택 (call stack) 다른말로 호출스택)

-메서드가 실행될때 필요한 메모리가 제공되는 공간
-메서드가 호출되면 호출스택에 메모리가 할당되고 종료되면 해제된다. (생성과 소멸)

메인 메소드에서 println()메소드가 실행되어 'callstack' 을 출력하게 된다. 이를 순차적으로 보면

main 메소드가 실행되고 이후에 println 메소드를 호출하게 되어 println 메소드가 실행되게 된다. 순서 2번에서 메인 메소드가 실행되면서 순서 3번에서 다음 메소드인 println 메소드를 호출하게되고 ,println메소드가 실행되는 동안 대기를 하게 된다. println메소드가 실행되어 끝이 나면 소멸( 메모리에서 삭제)된다. 순서 4번에서는 메인 메서드가 실행되면서 5번으로 마무리되어 프로그램이 종료가 된다.

콜스택 즉 호출 스택은 결과적으로 아래 있는 메소드가 위 메소드를 호출하여 실행하게 하는 것이라고 볼 수 있다. 그리고 위 올라온 메서드가 실행되어 마무리가 되어야 아래 있는 메소드가 실행되는 구조를 말한다.

아래 매소드가 위 매소드를 호출하면, 위에 호출된 메소드는 실행되고 해제되어야 다시 아래에 있는 메소드가 실행되면서 종료되면 메모리에서 해제가 된다.

package Ex01_03IfSwich;

public class ex01_callstack {

public static void main(String[] args) {
 System.out.println(" callstack");
}

}

 

  • 스코프 체인, 변수 은닉화

1. 스코프 체인

자바스크립트에서 전역 변수는 전역 객체의 프로퍼티이다. (ECMAScript 명세에 정의되어 있음)

지역 변수는 그런 규정이 없지만, 변수를 각 함수 호출과 연관된 객체(call object)의 프로퍼티로 생각할 수 있다.

지역 변수를 어떤 객체의 프로퍼티로 생각한다면, 자바스크립트의 모든 코드는 스코프 체인을 갖고 있다. 스코프 체인은 해당 코드의 유효 범위(in scope) 안에 있는 변수를 정의하는 객체의 체인, 리스트다.

자바스크립트가 변수 값을 얻으려고 할 때(variable resolution, 변수 해석) 스코프 체인에서 변수를 찾는다. 스코프 체인은 위에서 말했다시피 객체의 리스트이므로, 첫 번째 객체에서 해당 변수를 찾고, 없으면 그 다음 객체에서 해당 변수를 찾고, 여기도 없으면 그 다음 객체에서 찾는 식이다. 리스트의 끝까지 탐색했는데도 그 변수가 없다면 reference error가 발생하는 것이다.

최상위 자바스크립트 코드(어떠한 함수에도 속하지 않는 코드)의 스코프 체인에는 하나의 객체만 있고, 그것이 전역 객체이다. 중첩되지 않은 함수의 스코프 체인은 2개의 객체로 이루어진다. 하나는 함수의 매개변수와 지역 변수를 정의하는 객체고, 다른 하나는 전역 객체다.

함수가 정의될 때, 함수는 스코프 체인을 저장한다.

함수가 호출될 때, 함수는 지역 변수를 보관하는 새로운 객체를 만들고 그 객체를 기존에 만들어둔 스코프 체인에 추가한다.

 

2. 클로저의 은닉화

(function () {
  var a = 'a';
})();

console.log(a); // a is not defined

함수 외부에서 a를 출력해보면, 아직 정의되지 않았다(a is not defined)는 에러메세지를 확인할 수 있다. 이러한 방식과 같이 직접적으로 변경되면 안 되는 변수에 대한 접근을 막는 것을 은닉화라고 한다.

 

클로저를 통한 은닉화
자바스크립트에서 일반적인 객체지향 프로그래밍 방식으로 prototype를 사용하는데 객체 안에서 사용할 속성을 생성할 때 this 키워드를 사용하게 된다.

하지만 이러한 Prototype을 통한 객체를 만들 때의 주요한 문제가 하나 있다. 바로 Private variables에 대한 접근 권한 문제다.

function Hello(name) {
  this._name = name;
}

Hello.prototype.say = function() {
  console.log('Hello, ' + this._name);
}

let a = new Hello('영서');
let b = new Hello('아름');

a.say() //Hello, 영서
b.say() //Hello, 아름

a.name = 'anonymous'
a.say() // Hello, anonymous
현재 Hello() 함수를 통해 생성된 객체들은 모두 _name이라는 변수를 가지게 된다. 변수명 앞에 underscore()를 포함했기 때문에 일반적인 JavaScript 네이밍 컨벤션을 생각해 봤을때 이 변수는 Private variable으로 쓰고싶다는 의도를 알 수 있다.

하지만 실제로는 여전히 외부에서도 쉽게 접근가능한 것을 확인할 수 있다.

이 경우 클로저를 사용하여 외부에서 직접적으로 변수에 접근할 수 있도록 캡슐화(은닉화)할 수 있다.

function hello(name) {
  let _name = name;
  return function () {
    console.log('Hello, ' + _name);
  };
}

let a = new hello('영서');
let b = new hello('아름');

a() //Hello, 영서
b() //Hello, 아름

이렇게 a와 b라는 클로저를 생성함으로써 함수 내부적으로 접근이 가능하도록 만들 수 있다.

function a(){
  let temp = 'a' 
  
  return temp;
} 

// console.log(temp)  error: temp is not defined
const result = a()
console.log(result); //a

현재 위 함수 내부적으로 선언된 temp에는 직접적으로 접근을 할 수 없다. 함수 a를 실행시켜 그 값을 result라는 변수에 담아 클로저를 생성함으로써 temp의 값에 접근이 가능하다.

이렇게 함수 안에 값을 숨기고 싶은 경우 클로저를 활용해 볼 수 있다.

 🐤 실습 과제

 

  • 콘솔에 찍힐 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);

 

콘솔 값은

1

1 101

1

로 출력이 되며

 

첫번째 console.lg(b); -> 첫번째 1

hi(); - > 1 101

두번째 consol.elog(b); -> 마지막 1

이렇게 값이 나오게 되는데 console.log(a);의 주석을 해제하면 바로 오류가 떠서 콘솔값이 나오지 않는다.

이 이유는 a가 위쪽에서 const a = 1; 로써 해당 할당값이 영향을 미치는 블럭안에서 console.log를 찍을경우 그 값이 나오지만

지금 consol.log(a)는 주석을 풀경우 범위에서 벗어남으로써 값을 나타내지 못한다

 

해결법 1. let a = 1; 로 a역시 전역변수 설정

 

let a = 1;

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);

 

해결법 2. console.log(a) 값의 위치 변경

 

let b = 1;

function hi () {

const a = 1;

let b = 100;

b++;

console.log(a);

console.log(a,b);

}

console.log(b);

hi();

console.log(b);

 

(이때의 콘솔 값은

1       (첫번째 console.log(b);)

1       (function hi 안의 consol.log(a);)     2~3번째 줄 console값 = hi();

1 101 (function hi 안의 consol.log(a,b);)

1       (마지막 console.log(b);)

)

 

'자료집' 카테고리의 다른 글

라이프 사이클(클래스형 vs 함수형)  (0) 2022.06.05
javascript의 ES  (0) 2022.05.22
메소스 모음집/최종 update 22-05-18  (0) 2022.05.18
JWT에 대한 정의  (0) 2022.05.15
API에 대한 정의  (0) 2022.05.15