Javascript 탐구

Olivia·2022년 7월 20일
0

항해99

목록 보기
2/3
post-thumbnail

The goals

자바스크립트의 주요 특징에 대해 알아보고 이해하기

😎이해를 바탕으로 실습과제 해결하기

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

자바스크립트에서 값은 항상 문자열이나 숫자형 같은 특정한 자료형에 속합니다.
자바스크립트의 자료형은 8가지 종류가 있으며 다음과 같습니다.

  • 숫자형 : 정수, 부동 소수점 숫자 등 수를 나타낼 때 사용합니다.
  • BigInt : 길이 제약 없이 정수를 나타낼 수 있습니다.
  • 문자형 : 빈 문자열이나 글자로 이루어진 문자열, 단일 문자를 나타내는 별도의 자료형은 없습니다.
  • 불린형 : true, false 참과 거짓을 나타낼 때 사용합니다.
  • null : null 값만을 위한 독립 자료형입니다. null은 미지의 값을 의미합니다.
  • undefined : undefined 값만을 위한 독립 자료형입니다. undefined는 할당되지 않은 값을 의미합니다.
  • 객체 : 복잡한 데이터 구조를 표현할 때 사용합니다.
  • 심볼 : 객체의 고유 식별자를 만들 때 사용합니다.

숫자(Number) 자료형

let n = 123;
n = 123.456;

숫자형은 정수 및 부동소수점 숫자를 표현할 수 있습니다.
숫자형과 관련된 연산에는 대표적으로 곱셈, 나눗셈, 덧셈, 뺄셈이 있습니다.
숫자형에는 일반적인 정수와 부동소수점 숫자 이외에도 Infinity, -Infinity, NaN과 같은 특수 숫자 값이 포함됩니다.

Infinity는 무한대를 의미합니다.
어떤 숫자던 0으로 나누면 무한대를 얻을 수 있습니다.

// 아래 두 코드는 결과적으로 동일한 값을 가집니다.
inf = Infinity;
inf = 1/0;

NaN은 계산 중에 에러가 발생했다는 것을 나타냅니다. 부정확하거나 정의되지 않는 수학 연산을 사용하면 계산 중에 에러가 발생하는데, 이때 NaN이 반환됩니다.

BigInt 자료형

내부 표현 방식 때문에 자바스크립트에서는 (2^53-1) (9007199254740991)보다 큰 값 혹은 -(2^53-1)보다 작은 정수는 숫자형으로 나타낼 수 없습니다.
BigInt형은 표준으로 체택된 지 얼마 안 된 자료형으로, 길이에 상관 없이 정수를 나타낼 수 있습니다.
BigInt형 값은 점수 리터럴 끝에 n을 붙이면 만들 수 있습니다.

const bigInt = 1234567890123456789012345678901234567890n;

문자(String) 자료형

자바스크립트에서 문자열을 표현할 때는 따옴표를 사용합니다.

let str = "Apple"
let str2 = "Banana"
let res = `${str} and ${str2}`;

자바스크립트에서 문자열을 나타내는 따옴표의 종류는 3가지가 있으며 다음과 같습니다.

  • 큰 따옴표 "
  • 작은 따옴표 '
  • 역 따옴표(백틱)

큰 따옴표와 작은 따옴표는 문자열을 나타내는 기본적인 따옴표입니다.
자바스크립트에서는 큰 따옴표와 작은 따옴표 둘 사이에 별다른 차이를 두지 않습니다.

역 따옴표(백틱)으로 변수나 표현식을 감싼 후 ${...} 안에 넣어주면, 아래와 같이 원하는 변수나 표현식을 문자열 중간에 Formatting 할 수 있습니다.

let name = "Xeros";
console.log(`Hello, ${name}`); // Hello, Xeros
console.log(`2+3 = ${2+3}`); //2+3 = 5

${...} 안에는 변수나 수학 관련 표현식 혹은 이보다 복잡한 코드 표현식을 넣을 수도 있습니다.
이렇게 문자열 중간에 들어간 변수나 표현식은 연산 처리과정이 끝난 후 문자열로 치환됩니다.
큰 따옴표나 작은 따옴표를 사용하면 중간에 표현식을 넣을 수 없습니다. 표현식을 사용하기 위해서는 반드시 문자열을 역 따옴표(백틱)을 사용하여 감싸주어야합니다.

JavaScript에 Char 자료형은 없습니다.
Java나 C언어같은 일부 프로그래밍 언어는 한 글자를 저장할 때 사용하는 자료형인 Char형이 존재합니다.
다만 JavaScript에는 이러한 Char 데이터형이 존재하지 않습니다.

불린(Boolean) 자료형

불린형(논리 타입)은 오직 truefalse 두 가지 값만 존재하는 자료형입니다.
불린형은 긍정이나 부정을 나타내는 값을 저장할 때 사용합니다. true는 긍정, false는 부정을 의미합니다.

let nameFieldChecked = true;
let ageFieldChecked = false;

또는 비교 결과를 저장할 때도 사용합니다.

let isGreater = 4 > 1; // (4가 1보다 크기 때문에 true가 반환되어 저장됨)
console.log(isGreater); // true

null

null은 오로지 null 값만을 포함하는 별도의 자료형을 의미합니다.

let name = null; // name을 알 수 없거나, name이 비어있음을 의미함

자바스크립트의 null은 자바스크립트 이외 언어의 null과는 특성이 다릅니다. 다른 언어에서의 null은 “존재하지 않는 객체에 대한 참조”나 “널 포인터(null pointer)”를 나타낼 때 사용합니다.
하지만 자바스크립트의 null은 “존재하지 않는 (nothing) 값”, “비어 있는(empty) 값”, “알 수 없는(unknown) 값”을 의미합니다.

undefined

undefined 역시 null과 같이 undefined 자신만의 자료형을 의미합니다.
undefined는 “값이 할당되지 않은 상태”를 의미합니다.
변수는 선언했지만, 값을 할당하지 않았다면 해당 변수에 undefined가 자동 할당됩니다.

let name;
console.log(name); // undefined

하지만 undefined를 수동으로 직접 할당할 수도 있습니다.

let name = "Xeros";
console.log(name); // Xeros
name = undefined;
console.log(name); // undefined

하지만 이러한 방식은 권장하지 않습니다. 변수가 비어있거나 알 수 없는 상태라는것을 나타내기 위해서는 undefined보다 null을 사용하는것이 바람직합니다.

undefined와 null의 차이
undefined와 null은 얼핏보면 비슷해보여서 혼동할 수 있습니다.
undefined는 값이 아예 할당되지 않은 아무것도 없는 공허한 상태를 의미하고 null은 무언가 존재하지 않는다는 것을 나타냅니다.
아래 이미지를 살펴보면 보다 이해하기 수월합니다.

객체(Object) 자료형과 심볼(Symbol) 자료형

객체(Object) 자료형은 특수한 자료형입니다.
객체형을 제외한 다른 자료형은 문자열이든 숫자든 한 가지만 표현할 수 있기 때문에 원시(primitive) 자료형이라고 부르는 반면 객체는 데이터 컬렉션이나 복잡한 개체(entity)를 표현할 수 있습니다.
이러한 특징 때문에 자바스크립트에서 객체는 조금 더 특별한 취급을 받습니다.

아래는 객체에 대한 예시입니다.

var person = {
  name: ['Bob', 'Smith'],
  age: 32,
  gender: 'male',
  interests: ['music', 'skiing'],
  bio: function() {
    alert(this.name[0] + ' ' + this.name[1] + ' is ' + this.age + ' years old. He likes ' + this.interests[0] + ' and ' + this.interests[1] + '.');
  },
  greeting: function() {
    alert('Hi! I\'m ' + this.name[0] + '.');
  }
};

심볼(Symbol) 자료형은 객체의 고유한 식별자(unique indentifier)를 만들 때 사용합니다.

typeof 연산자

typeof 연산자는 인수로 받은 값의 자료형을 반환합니다. 자료형에 따라 처리 방식을 다르게 하고 싶거나 변수의 자료형을 빠르게 알아내고자 할 때 유용합니다.

typeof 연산자는 두 가지 형태의 문법을 지원합니다.

연산자 : typeof x
함수 : typeof(x)
연산자 형태를 사용하던, 함수 형태를 사용하던 반환되는 결과는 동일합니다.

console.log(typeof (undefined)); // undefined
console.log(typeof (0)); // number
console.log(typeof (10n)); // bigint
console.log(typeof (true)); // boolean
console.log(typeof ("xeros")); // string
console.log(typeof (Symbol("id"))); // symbol

console.log(typeof (Math)); // object           (1)
console.log(typeof (null)); // object           (2)
console.log(typeof (console.log)); // function  (3)

위 코드에서 마지막 3줄은 특수한 값을 반환합니다. 이유는 다음과 같습니다.

  1. Math는 수학 연산을 제공하는 내장 객체이므로 object가 출력됩니다. 내장 객체는 객체(Object)형으로 인식됩니다.
  2. typeof null의 결과는 object입니다. null은 별도의 고유한 자료형을 가지는 특수 값으로 객체가 아니지만, 이미 작성된 코드들, 하위 호환성을 유지하기 위해 이런 오류를 수정하지 않고 남겨둔 상황입니다. JavaScript 언어 자체의 오류이므로 null은 객체가 아님에 유의하시기 바랍니다.
  3. typeof는 피연산자가 함수일 경우 function을 반환합니다. 그러므로 typeof console.logfunction을 출력합니다.
    하지만 사실 “함수형”은 따로 없습니다. 함수는 객체형에 속합니다. 이런 동작 방식이 형식적으로는 잘못되었지만, 아주 오래전에 만들어진 규칙이었기 때문에 하위 호환성 유지를 위해 남겨진 상태입니다. 하지만 이러한 특징이 반대로 유용하게 사용되기도 합니다.

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

정적타입 언어 (Statically typed language)

  • 정의 : 컴파일 할 때 변수의 타입을 체크하는 언어 Java, C, C++의 언어는 각각의 변수의 타입을 반드시 지정해야 합니다. Caml, HAskell, Scala, Kotlin과 같은 정적타입 언어들은 타입추론이 가능한 형태를 제공해주기만 해도 가능합니다.
  • 장점 : 정적타입 언어의 장점은 컴파일러에 의해서 사전에 변수의 타입에 맞게 코드를 잘 작성했는지 여부가 드러난다는 것으로, 사소한 버그도 사전에 미리 발견이 가능합니다.
  • 예시: C, C++, Java, Rust, Go, Scala

동적타입 언어 (Dynamically typed language)

  • 정의 : 런타임의 변수나 필드, 기타 등등이 선언될 때가 아닌 변수의 값에 따라서 타입이 결정되며, 타입 체크도 이 때 이루어집니다.
  • 장점 : 프로그래머들이 타입을 고민할 필요 없이 빠르게 코드를 작성할 수있다는 장점이 있고 유연합니다.
  • 단점 : 사전에 버그를 미리 간파하기 어렵습니다.
  • 예시: Perl, Ruby, Python, PHP, JavaScript
    => 동적 타입의 언어의 단점을 보완하기 위해 TypeScript가 등장했습니다. 타입스크립트는 변수를 선언할 때, 해당 변수의 타입을 함께 선언해주어 컴파일 단계에서 타입 오류를 파악할 수 있게 해줍니다.

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

JavaScript는 동적 타입 또는 느슨한 타입(Loosely Typed)의 언어입니다. 이것은 변수에 값이 할당되는 과정에서 동적으로 타입을 추론한다는 의미입니다. 그렇기 때문에 동적 타이핑의 경우 같은 변수에 여러 타입을 교차하여 할당하는 것이 가능합니다. 동적 타이핑은 사용하기에 간편하지만 코드 예측이 어려워 예상치 못한 오류가 발생할 가능성이 높습니다.

이를 보완하기 위해 등장한 것이 바로 TypeScript입니다. TypeScript는 기본적으로 정적 타이핑을 지원합니다. 정적 타이핑은 타입을 명시적으로 선언하고 타입이 선언된 이후에는 변경할 수 없습니다. 그렇기 때문에 선언된 타입과 다른 잘못된 값이 할당되면 컴파일시 에러를 발생합니다. 이러한 정적 타이핑의 특징으로 인하여 코드 가독성, 예측성, 안정성에서 장점을 가질 수 있습니다.

JavaScript 형변환

함수와 연산자에 전달되는 값은 대부분 적절한 자료형으로 자동 변환됩니다. 이런 과정을 "형 변환(type conversion)"이라고 합니다.
alert가 전달받은 값의 자료형과 관계없이 이를 문자열로 자동 변환하여 보여주는 것이나, 수학 관련 연산자가 전달받은 값을 숫자로 변환하는 경우가 형 변환의 대표적인 예시입니다.
이 외에, 전달받은 값을 의도를 갖고 원하는 타입으로 변환(명시적 변환)해 주는 경우도 형 변환이라고 할 수 있습니다.

문자형으로 변환

문자형으로의 형 변환은 문자형의 값이 필요할 때 일어납니다.
alert메서드는 매개변수로 문자형을 받기 때문에, alert(value)에서 value는 문자형이어야 합니다. 만약, 다른 형의 값을 전달받으면 이 값은 문자형으로 자동 변환됩니다.
String(value) 함수를 호출해 전달받은 값을 문자열로 변환 할 수도 있습니다.

let value = true;
alert(typeof value); // boolean

value = String(value); // 변수 value엔 문자열 "true"가 저장됩니다.
alert(typeof value); // string

false는 문자열 "false"로, null은 문자열 "null"로 변환되는 것과 같이, 문자형으로의 변환은 대부분 예측 가능한 방식으로 일어납니다.

숫자형으로 변환
숫자형으로의 변환은 수학과 관련된 함수와 표현식에서 자동으로 일어납니다.

숫자형이 아닌 값에 나누기 /를 적용한 경우와 같이 말이죠.

alert( "6" / "2" ); // 3, 문자열이 숫자형으로 자동변환된 후 연산이 수행됩니다.

Number(value) 함수를 사용하면 주어진 값(value)을 숫자형으로 명시해서 변환할 수 있습니다.

let str = "123";
alert(typeof str); // string

let num = Number(str); // 문자열 "123"이 숫자 123으로 변환됩니다.

alert(typeof num); // number

숫자형 값을 사용해 무언가를 하려고 하는데 그 값을 문자 기반 폼(form)을 통해 입력받는 경우엔, 이런 명시적 형 변환이 필수입니다.

한편, 숫자 이외의 글자가 들어가 있는 문자열을 숫자형으로 변환하려고 하면, 그 결과는 NaN이 됩니다. 예시를 살펴봅시다.

let age = Number("임의의 문자열 123");

alert(age); // NaN, 형 변환이 실패합니다.

아래는 숫자형으로 변환 시 적용되는 규칙입니다.

예시:

alert( Number("   123   ") ); // 123
alert( Number("123z") );      // NaN ("z"를 숫자로 변환하는 데 실패함)
alert( Number(true) );        // 1
alert( Number(false) );       // 0

nullundefined은 숫자형으로 변환 시 결과가 다르다는 점에 유의하시기 바랍니다. null0이 되고 undefinedNaN이 됩니다.

불린형으로 변환

이 형 변환은 논리 연산을 수행할 때 발생합니다(논리 연산에 관한 내용은 뒤 챕터에서 다루고 있습니다). Boolean(value)를 호출하면 명시적으로 불리언으로의 형 변환을 수행할 수 있습니다.

불린형으로 변환 시 적용되는 규칙은 다음과 같습니다.

  • 숫자 0, 빈 문자열, null, undefined, NaN과 같이 직관적으로도 “비어있다고” 느껴지는 값들은 false가 됩니다.
  • 그 외의 값은 true로 변환됩니다.
    예시:
alert( Boolean(1) ); // 숫자 1(true)
alert( Boolean(0) ); // 숫자 0(false)

alert( Boolean("hello") ); // 문자열(true)
alert( Boolean("") ); // 빈 문자열(false)

주의: 문자열 "0"은 true입니다.
PHP 등의 일부 언어에선 문자열 "0"을 false로 취급합니다. 그러나 자바스크립트에선 비어 있지 않은 문자열은 언제나 true입니다.

alert( Boolean("0") ); // true
alert( Boolean(" ") ); // 공백이 있는 문자열도 비어있지 않은 문자열이기 때문에 true로 변환됩니다.

요약
문자, 숫자, 논리형으로의 형 변환은 자주 일어나는 형 변환입니다.

문자형으로 변환 은 무언가를 출력할 때 주로 일어납니다. String(value)을 사용하면 문자형으로 명시적 변환이 가능합니다. 원시 자료형을 문자형으로 변환할 땐, 대부분 그 결과를 예상할 수 있을 정도로 명시적인 방식으로 일어납니다.

숫자형으로 변환 은 수학 관련 연산시 주로 일어납니다. Number(value)로도 형 변환을 할 수 있습니다.

숫자형으로의 변환은 다음 규칙을 따릅니다.

불린형으로 변환 은 논리 연산 시 발생합니다. Boolean(value)으로도 변환할 수 있습니다.

불린형으로의 형 변환은 다음 규칙을 따릅니다.

형 변환 시 적용되는 규칙 대부분은 이해하고 기억하기 쉬운 편에 속합니다. 다만 아래는 예외적인 경우이기 때문에 실수를 방지하기 위해 따로 기억해 두도록 합시다.

  • 숫자형으로 변환 시 undefined는 0이 아니라 NaN이 됩니다.
  • 문자열 "0"과 " "같은 공백은 불린형으로 변환 시 true가 됩니다.

==, === in JavaScript

What is = in JavaScript?
Equal to (=) 는 할당연산자로, 변수를 '='를 기준으로 왼쪽에 설정하고 그에 해당하는 값을 오른쪽에 적어줍니다.

예를 들자면,
a=10 과 같이 작성하는 경우입니다.
10=10, ‘a’ = 10 or ‘a’ = ‘a’ 와 같이 작성하는 경우에는 reference error가 발생합니다.

What is == in JavaScript?
Double equals (==) 는 비교연산자로 피연산자를 비교 전에 같은 타입으로 변화시킵니다.

그래서 만약에 우리가 string과 number를 double equals를 사용하여 비교하게 된다면, 자바스크립트는 string을 number타입으로 변화시킬 것 입니다.
그 예로 빈 string은 항상 0으로 변하며, 숫자 값이 없는 문자열은 항상 NaN(Not a Number, false)으로 변화합니다.

What is === in JavaScript?
=== (Triple equals) 는 엄격한 비교 연산자로 비교하는 두 대상의 타입이 같지 않을 때에는 무조건 False를 return합니다.
예를 들어 우리가 숫자 2와 문자 "2"를 비교한다면 triple equals는 False를 값으로 return합니다.

Example of =

<!DOCTYPE html>
<html>
<body>

<h2>JavaScript Operators</h2>

 <p>a = 2, b = 5, calculate c = a + b, and display c:</p> 

<p id="demonstration"></p>

<script>
var a = 2;
var b = 5;
var c= a + b;
document.getElementById("demonstration").innerHTML = c;
</script>

</body>
</html>

Output:
a = 2, b = 5, calculate c = a + b, and display c:

7

Example of ==

<!DOCTYPE html>
<html>
<body>

<p id="demonstration"></p>

<script>
  var a = 10;
  document.getElementById("demonstration").innerHTML = (a == 20);
</script>

</body>
</html>
Output: false

Example of ===

<!DOCTYPE html>
<html>
<body>

<p id="demo"></p>

<script>

  var x = 10;
  document.getElementById("demo").innerHTML = (x === "10");

</script>

</body>
</html>

Output: false

undefined와 null

undefined
undefined는 선언은 되었지만 값이 할당된 적이 없는 변수에 접근하거나, 존재하지않는 객체 프로퍼티에 접근할경우 반환되는 값입니다. undefined와 null은 엄밀히 달라 서로 구별되는 값이지만 동등 연산자 (==) 는 둘을 같은 것으로 간주합니다. 즉, undefined == null은 true가 리턴됩니다. 만약 null 과 undefined 의 구분이 필요하다면 일치연산자(===)나 typeof을 사용해야 합니다. undefined 는 boolean문맥에서 사용시 false로 변환됩니다. number문맥에서는 NaN으로 변환되고 string문맥에서 사용될 경우에는 "undefined"로 변환됩니다.

null
자바스크립트의 null은 아무런 값도 나타내지 않는 특수한 값입니다. null값은 임의로 어떠한 객체도 나타내지않는 값으로 취급됩니다. null은 boolean문맥에서 사용시 false로 변환됩니다. number문맥에서는 0으로 변환되고, string문맥에서 사용하게되면 "null"로 변환됩니다.

JavaScript 객체와 불변성이란?

JavaScript는 객체기반의 스크립트 언어이며, JavaScript를 이루고 있는 거의 모든 것은 객체입니다. 객체란 여러 속성을 하나의 변수에 저장할 수 있도록 해주는 데이터 타입으로 Key / Value Pair를 저장할 수 있는 구조를 가지고 있습니다.

객체구문

var user = new Object(); 	// "object constructor" syntax
var user = {};  		 	// "object literal" syntax

객체의 특징

  • 객체는 변수이면서도 많은 값을 포함할 수 있습니다.
    (자바스크립트 변수처럼 단일 값을 포함 가능.)
  • 객체는 중괄호 표기를 이용하여 만들 수 있습니다.
  • 객체는 각각의 key/value에 대한 정보를 나열할 수 있습니다.
  • Key는 문자열 또는 기호여야 합니다.
  • Value는 모든 유형이 될 수 있습니다.
  • 객체는 한 쌍의 key/value 뒤에 쉼표를 이용하여 그 뒤에 오는 key/value와 구분해주어야 합니다.
  • 객체에서 명명된 값을 Properties라고 합니다.
  • 변수는 예약어의 이름을 가질 수 없지만 객체는 어떠한 이름이어도 상관없습니다.
  • 객체 변수를 복사하면 참조가 복사되고 객체가 복제되지 않습니다.

Javascript 불변성
자바스크립트에서 불변성이란 객체가 생성된 이후 그 상태를 변경할 수 없는 것을 의미합니다.
여기서 상태를 변경할 수 있는 것과 값을 재할당하는 것은 다르다는 것을 알아야합니다.

let a = 10;
let b = a;
a = 20;
console.log(a,b); //20 10

위의 코드는 a에 10을 할당하고 b를 a가 가리키는 주소를 가리킵니다.
이때 a의 값을 20으로 변경시켜줍니다.
만약 값을 직접 변경하는 것이라면 a와 b가 둘다 20을 출력해야합니다.

하지만 자바스크립트에서 Number값은 불변성을 유지하기 때문에 새롭게 20이라는 값을 가지는 주소를 a에 할당하게 되기 때문에 위와 같은 결과가 나오게 됩니다.

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

기본형과 참조형을 구분하는 기준
데이터 타입의 종류는 크게 기본형과 참조형 2가지로 나뉩니다.
기본형과 참조형을 구분 짓는 가장 큰 다른 점은 실제 값 전체 복사 vs 실제 값이 들어있는 주소 값 복사를 통해 알아볼 수 있습니다.

기본형 (copy value of a)

let a = 1;
let b = a;

a += 10;
console.log(a) //11
console.log(b) //1

위의 코드를 살펴보면 a의 값이 변하더라도 b의 값은 그대로 인 것을 볼 수 있습니다.
let b = a; 코드를 통해 a값 전체가 복사되어 b라는 변수에 담겼으나, a, b는 서로 영향을 받지 않고 독립적으로 존재하는 것을 볼 수 있습니다.

기본형 데이터가 저장되는 순서는 다음과 같습니다.
1. 변수 영역에서 빈 공간(@1003 : 임의의 주소 값)을 확보합니다.
2. 확보한 공간의 식별자(변수명)를 a로 지정합니다.
3. 일단 데이터 영역에서 1을 찾고, 없으면 데이터 공간을 하나 만들어(@5004)에 숫자 1을 저장합니다.
4. 변수 영역에서 a라는 식별자를 검색합니다.(@1003).
5. 앞서 저장한 문자열의 주소(@5004)를 @1003의 공간에 연결합니다.

a += 10 코드 처리

  1. 1이 저장된 공간에 11을 할당하는 대신 데이터 영역에서 11을 찾고, 없으면 새로 만들어 별도의 공간에 저장합니다.
  2. 그 주소를 1003의 공간에 연결합니다.

참조형 ( a has address of "{ x : 1}" )

let a = { x : 1 };
let b = a;

a.x += 10;
console.log(a.x); //11
console.log(b.x) //11

기본형과 다르게 let b = a; 코드를 통해 {x : 1} 의 주소 값이 b라는 변수에 담겼습니다.
a, b를 콘솔로 찍어보면 a값만 변화시켰지만, b값도 +10이 된 것을 알 수 있습니다.
즉 a, b는 서로 영향을 받고 있는 것을 볼 수 있습니다.

참조형 데이터가 저장될 때는 아래와 같은 순서로 저장됩니다.
1. 변수 영역에서 빈 공간(@1002 : 임의의 주소 값)을 확보합니다.
2. 확보한 공간의 식별자(변수명)를 a로 지정합니다.
3. 임의의 데이터 저장 공간(@5001) 에 데이터를 저장하려고 보니 여러 개의 프로퍼티로 이뤄진 데이터 그룹인 관계로, 이 그룹 내부의 프로퍼티(x) 들을 저장하기 위해 별도의 변수 영역을 마련하고, 그 영역의 주소(@7103~ ?)를 @5001에 저장합니다.
4. @7103 에 x라는 프로퍼티 이름을 지정합니다.
5. 데이터 영역에서 숫자 1을 검색하고 없으면, 임의로 @5003에 저장하고, 이 주소를 다시 @7103에 저장합니다.

a.x += 10 코드 처리

  1. 데이터 영역에서 숫자 11을 검색합니다. 검색 결과가 없으면 빈 공간인 @5005에 저장하고 그 주소를 @7103에 저장합니다.

불변 객체를 만드는 방법

JavaScript에서 객체의 속성을 변경하지 못하도록 불변 객체를 만드는 세 가지 방법을 소개합니다.

Object.freeze() 함수

Object.freeze() 함수를 사용하여 객체를 동결시켜 수정되는 것을 방지할 수 있습니다.
다음은 Object.freeze() 함수의 기능입니다.

  • 속성 제거 불가능
  • 새로운 속성 추가 불가능
  • 기존 속성 값 변경 불가능

객체의 속성은 delete 키워드로 제거할 수 있습니다. 하지만, Object.freeze() 함수를 사용하여 동결시킨 객체의 속성은 제거되지 않습니다.

const userInfo = {
  name: 'Kang',
  age: 30
};

Object.freeze(userInfo);

delete userInfo.age;

console.log(userInfo);

Object.freeze() 함수는 객체뿐만 아니라 배열도 동결시킬 수 있습니다. 다음은 배열을 동결시킨 후 배열의 요소를 변경하는 예제입니다.

const arrInfo = [1, 2, 3, 4, 5];

Object.freeze(arrInfo);

arrInfo[0] = 10;

console.log(arrInfo);

Object.seal() 함수
Object.seal() 함수는 객체를 봉인합니다. Object.freeze() 함수와는 다르게 기존 속성의 값을 변경할 수 있습니다.

다음은 Object.seal() 함수의 기능입니다.

  • 속성 제거 불가능
  • 새로운 속성 추가 불가능

다음 예제는 Object.seal() 함수로 봉인한 객체 속성의 값을 변경하는 예제입니다.

const userInfo = {
  name: 'Kang',
  age: 30
};

Object.seal(userInfo);

userInfo.name = 'Kim';

console.log(userInfo);

Object.preventExtensions() 함수
Object.preventExtension() 함수는 객체를 확장 불가능한 상태로 변환합니다.

다음은 Object.preventExtension() 함수의 기능입니다.

  • 새로운 속성 추가 불가능

Object.preventExtension() 함수는 새로운 속성을 추가할 수 없지만, 기존 속성의 값을 변경할 수 있으며, 이미 존재하는 속성을 제거할 수 있습니다.

다음은 기존 속성의 값을 변경하며, 속성을 제거하는 예제입니다.

얕은 복사와 깊은 복사

얕은 복사(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)란?
얇은 복사 처럼 주소를 복사해서 공유하는 것이 아니라, 아예 새로운 객체안 속성(property)만 복사 해서 사용할 수 없을까?
👉 방법 1. …(spread) 연산자를 통한 복사 (과연 깊은 복사가 될까?)

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

...(spread) 연산자를 통해 { }안에 obj1의 속성을 복사하여 obj2에 할당하였습니다.
이제 obj1과 obj2는 다른 주소를 갖게됩니다. (그러나 딱, 1 depth 까지만)

👉 방법 2. Object.assign() 메소드를 통한 복사 (과연 깊은 복사가 될까?)
먼저 Object.assign() 메소드의 사용법은 MDN을 참고합니다.

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

Object.assign() 메소드를 통해 첫 번째 인자로 빈 { } 객체를, 두 번째 인자로 obj1 넣고 obj2 에 할당하였습니다.
이제 obj1과 obj2는 다른 주소를 갖게되었습니다. (그러나 딱, 1 depth 까지만)

❗️주의, 깊은 복사의 함정
👉 MDN의 전개 구문

참고: Spread 문법은 배열을 복사할 때 1 레벨 깊이에서 효과적으로 동작합니다. 그러므로, 다음 예제와 같이 다차원 배열을 복사하는것에는 적합하지 않을 수 있습니다. (Object.assign() 과 전개 구문이 동일합니다)

  • Object.assign() 메소드도 spread 연산자 둘 다 완벽한 Deep copy 되지 않습니다
  • 객체가 서로 다르다고 깊은 복사가 이루어진건 아닙니다.
  • 1 depth 까지는 확실하게 Deep copy
  • 2 depth 이상이면 Shallow copy

👉 …(spread) 연산자를 이용해 depth 2까지 복사하는 방법

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

❗️완벽한 Deep copy를 위한 다른 방법

  • 재귀적으로 깊은 복사를 수행
  • Lodash의 cloneDeep 함수 사용
  • JSON.parse()와 JSON.stringify()함수 사용

스코프, 호이스팅, TDZ

스코프

  • 자바스크립트의 스코프는 함수 레벨 스코프를 따릅니다.
  • 같은 함수 레벨에 존재하면 값을 참조할 수 있음
  • ES6에서 let 키워드가 도입되면서 블록 레벨 스코프를 사용할 수 있게 됐다.

전역 스코프

  • 어디서든 참조 가능

전역 변수

  • 전역 스코프를 갖는 전역 변수
  • 어디서든 참조 가능

지역 스코프

  • 함수 자신과 하위 함수에서만 참조 가능

지역 변수

  • 지역 스코프를 갖는 지역 변수
  • 함수 내에서 선언된 변수로 해당 함수와 해당 함수의 하위 함수에서 참조 가능

암묵적 전역 변수

  • 선언하지 않은 변수에 값을 할당하면 전역 객체의 프로퍼티가 되어 전역 변수처럼 동작
  • 하지만 변수 선언이 없었기 때문에 호이스팅은 발생하지 않음
(variable = 1) === (window.variable = 1)

//////////////////////////////////////

console.log('test', test);

function temp () {
  test = 10;
};

temp(); // test is not defined

호이스팅

  • 함수의 코드를 실행하기 전에 변수와 함수의 메모리 공간을 선언 전에 미리 할당하는 것
  • 초기화를 제외한 선언만 호이스팅
  • 그렇기 때문에 선언, 정의된 코드보다 호출하는 코드를 먼저 배치할 수 있음
  • 변수의 선언과 초기화를 분리
  • 변수의 선언을 코드의 최상단으로 끌어올림
catName("조미료");

function catName(name) {
  console.log("제 고양이의 이름은 " + name + "입니다");
}

// "제 고양이의 이름은 조미료 입니다"

변수 선언 형식에 따른 초기화
var : 호이스팅 시 undefined로 변수를 초기화
function : 선언된 위치와 상관없이 동일하게 호출
let, const : 호이스팅 시 변수를 초기화하지 않음. (호이스팅 대상은 맞음)

console.log(num); // 호이스팅한 var 선언으로 인해 undefined 출력
var num; // 선언
num = 6; // 초기화

console.log(num2); // ReferenceError: num2 is not defined
let num2 = 2;

//-----------------------------------------------

catName("조미료"); // "제 고양이의 이름은 조미료 입니다"

function catName(name) {
  console.log("제 고양이의 이름은 " + name + "입니다");
}

catName("조미료"); // "제 고양이의 이름은 조미료 입니다"

TDZ(Temporal Dead Zone, 일시적 사각지대)

  • TDZ의 영항을 받는 구문 const, let, class
  • 변수 스코프의 맨 위에서부터 ~ 변수의 초기화 완료 시점까지의 변수는 TDZ에 들어간 변수
  • 코드의 작성 순서(위치)가 아니라 코드의 실행 순서(시간)에 의해 형성

let, const

{
    // <----- TDZ가 스코프 맨 위에서부터 시작
    const func = () => console.log(letVar); // OK

    // TDZ 안에서 letVar에 접근하면 ReferenceError

    let letVar = 3; // letVar의 TDZ 종료 ------->

    func(); // TDZ 밖에서 호출함
}

let 변수 선언 코드가 그 변수에 접근하는 함수보다 아래에 위치하지만 함수의 호출 시점이 사각지대 밖이므로
정상 동작

class

  • 부모 클래스를 상속받을 경우, 생성자 안에서 super()호출을 하기 전까지 this바인딩은 TDZ안에 있다.
// (X)
class User extends Member {
  constructor(phone) {
    this.phone = phone;
    super(phone);
  }
}

// (O)
class User extends Member {
  constructor(phone) {
    super(phone);
    this.phone = phone;
  }
}

결론

  • TDZ는 선언 전에 변수를 사용하는 것을 허용하지 않는다.
  • var의 사용은 의도치 않은 중복선언과 재할당으로 문제가 생길 수 있기 때문에 사용하지 않는편이 좋다.

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

👉함수 표현식?
함수 표현식은 변수로 저장할 수 있습니다.
변수를 먼저 할당하고, 할당 된 변수에 함수를 선언합니다.

const expressionFunction = () => console.log(“Expression”);

👉함수 선언식?
함수 선언식은 일반적인 함수 표현식으로, 함수를 선언과 동시에 내용을 함께 정의합니다.

declarationFunction = () => console.log(“Declaration”);

👊함수 표현식 vs 함수 선언식
이 둘의 차이는 호이스팅(Hoisting)입니다.
즉, 함수 선언식은 코드가 실행되기 전에 로드 되지만, 함수 표현식은 해당 코드 줄에 도달 할 때만 로드됩니다.(그전에는 선언한 변수명만 로드)

let, const, var, function

보통 자바스크립트를 배울 때는 Var로 변수를 선언하는 방법부터 배웁니다. 하지만 Var은 이제 const와 let이 대체합니다. 먼저 const와 let이 공통적으로 가지는 특징인 블록 스코프 (범위)에 대해 알아보자면,

if (true) {
	var x = 3;
}
console.log(x); //3

if (true) {
const y = 3;
}
console.log(y); // Uncaught ReferenceError: y is not defined

위와 같은 코드를 사례로 들어 설명할 수 있습니다.

x는 정상적으로 출력되는데 y는 에러가 발생합니다. var을 const로 바꿨을 뿐인데 차이가 발생하는 것입니다. var은 함수 스코프를 가지므로 if문의 블록과 관계없이 접근할 수 있습니다. 하지만 const와 let은 블록스코프를 가지므로 블록 밖에서는 변수에 접근할 수 없습니다. 블록의 범위는 if, while, for, function등에서 볼 수 있는 중괄호({와 }사이) 입니다. 함수 스코프 대신 블록 스코프를 사용함으로써 호이스팅 같은 문제도 해결되고 코드관리도 수월해졌습니다.

const, let과 var은 스코프 종류가 다릅니다. const는 한 번 값을 할당하면 다른 값을 할당할 수 없습니다. 다른 값을 할당하려고 하면 에러가 발생합니다. 또한, 초기화할 때 값을 할당하지 않으면 에러가 발생합니다. 따라서 const로 선언한 변수를 상수라고 부르기도 합니다.

const a = 0;
a = 1; Uncaught TypeError: Assignment to constant variable.

let b = 0;
b = 1; // 1

const c; // Uncaught SyntaxError: Missing initializer in const declaration

const와 let 중에 어느 것을 써야할까?
자바스크립트를 사용할 때 한 번 초기화했던 변수에 다른 값을 할당하는 경우는 의외로 적습니다. 따라서 변수 선언시에는 기본적으로 const를 사용하고, 다른 값을 할당해야 하는 상황이 생겼을 때 let을 사용하면 됩니다.

자바스크립트에서 function은 한 코드 블록으로 특정한 업무를 수행하기 위해 디자인된 형태입니다. 이러한 자바스크립트 function은 다른 곳에서 불려질 때 수행됩니다.

function myFunction(p1, p2) {
  return p1 * p2;   // The function returns the product of p1 and p2
}

자바스크립트 함수는 function keyword와 같이 정의되며, 그 외 이름과 ()를 동반합니다.
함수의 이름은 문자와 숫자, _, $ 를 동반할 수 있습니다. (변수와 동일한 규칙을 가짐)
The parentheses ()는 ,를 이용하여 여러개의 파라미터를 동반할 수 있습니다. 그리고 이 함수에 의해 수행될 액션은 curly brackets: {} 사이에 적습니다.

function name(parameter1, parameter2, parameter3) {
  // code to be executed
}

실행 컨텍스트와 콜 스택

실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체입니다.
자바스크립트는 동일한 환경에 있는 환경 정보들을 모은 실행 컨텍스트를 콜스택에 쌓아올린 후 실행하여 코드의 환경과 순서를 보장할 수 있게 됩니다.

풀어서 설명하면 스택의 경우 FILO (First In, Last Out) 의 구조이기에 순서를 보장, 콜스택 내부에 쌓인 실행 컨텍스트의 정보를 통해 환경을 보장할 수 있는 것이라 생각합니다.

여기서 환경이란 전역공간이 될 수 있고, 함수 즉 함수 내부의 환경이 될 수 있습니다.

var temp = 'temp';

function b (){
  console.log('안녕하세용');
}

function a (){
  b();
}

a();

  • (1) 콜스택엔 전역 컨텍스트를 제외하곤 다른 컨텍스트가 없기에 전역 컨텍스트와 관련된 코드를 진행합니다.
  • (2) 전역 컨텍스트와 관련된 코드를 진행 중 a함수를 실행하였기에 a 함수의 환경 정보들을 수집하여 a 실행 컨텍스트를 생성, 콜스택에 담습니다.
    콜스택 최상단에 a 실행 컨텍스트가 있기에 기존의 전역 컨텍스트와 관련된 코드의 실행을 일시적으로 중단한 후 a 실행 컨텍스트의 코드를 실행합니다.
  • (3) a 함수 내부에서 b 함수를 실행하였기에 b 함수의 환경 정보들을 수집, 실행 컨텍스트를 생성, 콜스택에 담습니다. 이전과 똑같이 콜스택 최상단에 b 실행 컨텍스트가 있기에 기존 a 실행 컨텍스트와 관련된 코드의 실행을 일시적 중단합니다.
  • (4) b 함수가 종료된 후 b 실행 컨텍스트가 콜스택에서 제거됩니다. 제거 후 콜스택 최상단에는 a 실행 컨텍스트가 있기에 이전에 중단된 지점부터 코드 진행이 재개됩니다.
  • (5) a 함수 또한 종료된 후 실행 컨텍스트가 콜스택에서 제거됩니다.
    이후엔 전역 공간에 실행할 코드가 남아있지 않다면 콜스택에서 전역 컨텍스트 또한 제거되며 콜스택에 아무 것도 남지 않은 상태로 종료됩니다.

콜 스택(Call Stack)

자바스크립트는 단일 스레드 프로그래밍 언어이므로, 하나의 콜 스택만 존재합니다. 즉, 하나의 일만 처리할 수 있다는 뜻입니다. 콜 스택은 여러 함수들을 호출하는 스크립트에서 해당 위치를 추적하는 엔진을 위한 매커니즘이며 현재 어떤 함수가 동작하고 있는지, 그 함수 내에서 어떤 함수가 동작하는지, 다음에 어떤 함수가 호출되어야 되는지를 제어합니다.

스크립트가 함수를 호출하면 엔진은 이를 콜 스택에 추가하고 함수를 수행합니다.
해당 함수에 의해 호출되는 모든 함수들도 호출 스택에 추가되고 호출이 도달하는 위치에서 실행됩니다.
메인함수가 끝나면 엔진은 스택을 제거하고 메인 코드 목록에서 중단된 실행을 다시 시작합니다.
스택에 할당된 공간보다 많은 공간을 차지하면 stack overflow 에러가 발생하게 됩니다.

스코프 체인, 변수 은닉화

스코프(Scope)
식별자의 유효 범위 ⇒ 모든 식별자는 자신이 선언된 위치에 의하여 다른 코드가 식별자 자신을 참조할 수 있는 유효 범위가 결정됨.
한 스코프 내에서는 식별자가 유일해야 하지만, 다른 스코프에는 동명의 식별자를 사용할 수 있음.
⇒ 네임스페이스
식별자 결정(identifier resolution) : 자바스크립트 엔진은 스코프를 통해서 어떤 변수를 참조할 것인지 결정함.

스코프의 종류
전역 스코프
전역은 코드의 가장 바깥 영역으로, 여기서 선언된 변수는 전역 변수가 됨.
전역 변수는 어디서든 참조 가능

지역 스코프
지역은 함수 코드의 내부 영역으로, 여기서 선언된 변수는 지역 변수가 됨.
지역 변수는 자신의 지역 스코프 및 하위 지역 스코프에서 참조 가능

⭐️ 함수 레벨 스코프 VS 블록 레벨 스코프
지역 스코프는 함수에 의해서 생성되는가, 코드 블록에 의해서 생성되는가에 따라 레벨이 나뉨.

블록 레벨 스코프 : if, for, while, try/catch 등 코드 블록이 지역 스코프 생성 ⇒ let, const 키워드로 선언된 변수는 모든 코드 블록을 지역 스코프로 인정함.

함수 레벨 스코프 : 함수가 지역 스코프 생성 ⇒ var 키워드로 선언된 변수는 오직 '함수'만을 지역 스코프로 인정함.

렉시컬 스코프 or 정적 스코프
자바스크립트에서는 함수 정의가 평가되는 시점에 상위 스코프가 정적으로 결정됨.
즉, 함수를 어디서 호출했는지가 아니라 '어디에 정의했는지'에 따라서 상위 스코프가 결정됨.
함수의 상위 스코프는 함수 정의가 실행될 때 정적으로 결정되며, 함수 정의가 실행되어 생성된 함수 객체는 상위 스코프를 기억함. ⇒ 클로저와 깊은 연관

스코프 체인(Scope Chain)
스코프는 함수의 중첩에 의해 계층적 구조를 가짐. ⇒ 외부 함수(outer function) / 중첩 함수(nested function)

변수를 참조할 때, 자바스크립트 엔진은 스코프 체인을 통해 변수를 참조하는 코드의 스코프에서 시작하여 상위 스코프로 이동하면서 선언된 변수를 검색함.

여러 스코프에서 동일한 식별자를 선언한 경우, 무조건 스코프 체인 상에서 가장 먼저 검색된 식별자에만 접근 가능 ⇒ 변수 은닉화(variable shadowing)

스코프 체인은 outerEnvironmentReference와 밀접한 관계를 가짐.

💡 스코프 체인은 실행 컨텍스트의 렉시컬 환경을 '단방향'으로 연결한 링크드 리스트

🐤 실습 과제

  • 콘솔에 찍힐 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)는 1로 출력
-> 전역변수로 let b = 1로 선언을 했기 때문
두번째 hi()로 찍은 console.log(a,b)는 각각 1과 101로 출력
-> 함수 내에서 a와 b를 선언하였고, 그 값을 출력
세번째 console.log(b)는 1을 출력
-> 전역변수로 let b = 1로 선언했던 값 출력

주석의 오류원인
a의 선언을 function hi() 내부에서 했기 때문에, 값을 불러올 수 없음, 전역변수로 선언을 해주면 오류가 발생하지 않음

Sources
https://freestrokes.tistory.com/98
https://ko.javascript.info/type-conversions
https://www.guru99.com/difference-equality-strict-operator-javascript.html
https://devsh.tistory.com/entry/null-%EA%B3%BC-undefined-%EC%9D%98-%EC%B0%A8%EC%9D%B4
https://velog.io/@surim014/%EC%9B%B9%EC%9D%84-%EC%9B%80%EC%A7%81%EC%9D%B4%EB%8A%94-%EA%B7%BC%EC%9C%A1-JavaScript%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-part-7-Object-35k01xmdfp
https://velog.io/@co_mong/JS-%EB%B6%88%EB%B3%80%EC%84%B1Immutability
https://developer-talk.tistory.com/276
https://intrepidgeeks.com/tutorial/function-expression-vs-function-declaration
https://gamguma.dev/post/2022/04/js_execution_context
https://ryuhojin.tistory.com/3
https://velog.io/@solseye/JS-%EC%9E%98-%EB%B4%90-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%8A%A4%EC%BD%94%ED%94%84Scope-%EC%8B%B8%EC%9B%80%EC%9D%B4%EB%8B%A4

profile
No special aptitude, but persistent effort

0개의 댓글