🎨JavaScript의 자료형과 JavaScript만의 특성은 무엇일까 ?
1. 느슨한 타입(loosely typed)의 동적(dynamic) 언어
동적 타입
JavaScript는 느슨한 타입(loosely typed)의 동적(dynamic) 언어입니다. JavaScript의 변수는 어떤 특정 타입과 연결되지 않으며, 모든 타입의 값으로 할당 (및 재할당) 가능합니다.
타입 없이 변수 선언하는 것을 느슨한 타입이라고 하며, 강력한 타입(strong typing)을 사용하는 언어는 타입과 함께 변수를 선언해야만 한다. 타입 없이 변수를 선언한다고해서 타입이 존재하지 않는 것이 아니고 컴퓨터가 해석하고 알아서 타입을 지정해준다고 볼 수 있다. 타입에 따라서 자료형에 맞는 메모리를 지정해주는 과정이 존재하기 때문에 모든 언어에서 타입은 필수적이다. 다만, 사용자가 직접 지정해주는지, 언어 자체적으로 타입을 파악하는지의 차이가 존재한다.
// C 언어
int age = 26
// Python
age = 26
// Javascript
let age = 26
※ 각 언어의 변수 선언 차이를 알아볼 수 있다.
Reference
2. JavaScript 형변환
자바스크립트의 변수는 자료형에 관계없이 모든 데이터일 수 있습니다. 따라서 변수는 어떤 순간에 문자열일 수 있고 다른 순간엔 숫자가 될 수도 있습니다.
// no error
let message = "hello";
message = 123456;
이처럼 자료의 타입은 있지만 변수에 저장되는 값의 타입은 언제든지 바꿀 수 있는 언어를 ‘동적 타입(dynamically typed)’ 언어라고 부릅니다.
reference
3. ==, ===
== : Equality operator.
=== : Strict equality operator.
mdn을 살펴보면 차이점은 “Strict”에 있다.
일치연산자 (===)와의 가장 두드러지는 차이점은 일치 연산자는 타입변환을 시도하지 않는다는 것입니다. 일치 연산자는 다른 타입을 가진 피연산자는 다르다고 판단합니다.
"1" == 1; // true
1 == "1"; // true
0 == false; // true
0 == null; // false
0 == undefined; // false
0 == !!null; // true, look at Logical NOT operator
0 == !!undefined; // true, look at Logical NOT operator
null == undefined; // true
const number1 = new Number(3);
const number2 = new Number(3);
number1 == 3; // true
number1 == number2; // false
== 연산자는 형변환시킨 후 비교하기 때문에 논리적으로 같지 않은데 같다고 판단하는 경우가 너무 많다. 예상하기 힘든 경우가 많기 때문에 ==을 사용하지 말고 ===연산자를 사용해야 한다.
동일하게 != inequality 연산자도 strict 버전인 !== 을 사용하는 것이 좋다.
개인적인 생각으로는 컴퓨터 연산이라는 게 맞으면 맞는 거고 아니면 아닌게 당연한건데 그 역할을 못하니까 Strict 버전이라는 기가 차는 연산자 설명이 나온게 아닌가 싶다.
reference
4. 느슨한 타입(loosely typed)의 동적(dynamic) 언어의 문제점과 보완할 수 있는 방법
느슨한 타입의 동적언어의 문제점은 런타임 도중에 프로그래머 조차도 예상하지 못하는 타입으로 형변환되어서 타입 에러를 발생시킬 수도 있다는 점이다.
타입 때문에 하루종일 고생해보면 javascipt 혐오가 안 생길래야 안생길 수가 없다…
javascript에서 타입을 추가한 버전인 typescript가 해결책으로 떠오르고 있다. typescript로 컴파일하면 컴파일러가 코드를 읽어서 javascipt 코드로 변환해주는 식으로 작동한다.
5. undefined와 null의 미세한 차이
null은 변수 값이 비어있는 상태를 나타내며 undefined는 변수의 값이 할당되지 않은 상태를 나타낸다.
let age;
console.log(age)
// undefined
※ undefined도 할당할 수 있지만 직접 할당하는 것은 권장하지 않는다. 값이 할당되지 않은 변수의 초기값을 위해 예약어로 남겨두자
💎JavaScript 객체와 불변성이란 ?
1. 기본형 데이터와 참조형 데이터
원시 타입 이외의 모든 값은 객체(Object) 타입이며 객체 타입은 변경 가능한 값(mutable value)이다. 즉, 객체는 새로운 값을 다시 만들 필요없이 직접 변경이 가능하다는 것이다.
예를 들어 살펴보자. C 언어와는 다르게 Javascript의 문자열은 변경 불가능한 값(immutable value) 이다. 이런 값을 “primitive values” 라 한다. (변경이 불가능하다는 뜻은 메모리 영역에서의 변경이 불가능하다는 뜻이다. 재할당은 가능하다)
var str = 'Hello';
str = 'world';
첫번째 구문이 실행되면 메모리에 문자열 ‘Hello’가 생성되고 식별자 str은 메모리에 생성된 문자열 ‘Hello’의 메모리 주소를 가리킨다. 그리고 두번째 구문이 실행되면 이전에 생성된 문자열 ‘Hello’을 수정하는 것이 아니라 새로운 문자열 ‘world’를 메모리에 생성하고 식별자 str은 이것을 가리킨다. 이때 문자열 ‘Hello’와 ‘world’는 모두 메모리에 존재하고 있다. 변수 str은 문자열 ‘Hello’를 가리키고 있다가 문자열 ‘world’를 가리키도록 변경되었을 뿐이다.
var statement = 'I am an immutable value'; // string은 immutable value
var otherStr = statement.slice(8, 17);
console.log(otherStr); // 'immutable'
console.log(statement); // 'I am an immutable value'
2행에서 Stirng 객체의 slice() 메소드는 statement 변수에 저장된 문자열을 변경하는 것이 아니라 사실은 새로운 문자열을 생성하여 반환하고 있다. 그 이유는 문자열은 변경할 수 없는 immutable value이기 때문이다.
기본형 데이터(원시 타입)는 변경 불가능한 값이며 참조형 데이터(객체)는 변경 가능하다고 정리할 수 있다. 이 내용을 이해하기 위해서는 메모리적인 측면에서 변수를 이해하는 것이 중요하다.
2. 불변 객체를 만드는 방법
Object.freeze()를 사용하여 불변(immutable) 객체로 만들수 있다.
const user1 = {
name: 'Lee',
address: {
city: 'Seoul'
}
};
// Object.assign은 완전한 deep copy를 지원하지 않는다.
const user2 = Object.assign({}, user1, {name: 'Kim'});
console.log(user1.name); // Lee
console.log(user2.name); // Kim
Object.freeze(user1);
user1.name = 'Kim'; // 무시된다!
console.log(user1); // { name: 'Lee', address: { city: 'Seoul' } }
console.log(Object.isFrozen(user1)); // true
하지만 객체 내부의 객체(Nested Object)는 변경가능하다.
const user = {
name: 'Lee',
address: {
city: 'Seoul'
}
};
Object.freeze(user);
user.address.city = 'Busan'; // 변경된다!
console.log(user); // { name: 'Lee', address: { city: 'Busan' } }
내부 객체까지 변경 불가능하게 만들려면 Deep freeze를 하여야 한다.
function deepFreeze(obj) {
const props = Object.getOwnPropertyNames(obj);
props.forEach((name) => {
const prop = obj[name];
if(typeof prop === 'object' && prop !== null) {
deepFreeze(prop);
}
});
return Object.freeze(obj);
}
const user = {
name: 'Lee',
address: {
city: 'Seoul'
}
};
deepFreeze(user);
user.name = 'Kim'; // 무시된다
user.address.city = 'Busan'; // 무시된다
console.log(user); // { name: 'Lee', address: { city: 'Seoul' } }
3.얕은 복사와 깊은 복사
얕은 복사는 주소 값을 복사하는 것이며 깊은 복사는 실제 값을 새로운 메모리 공간에 복사하는 것이다. 깊은 복사는 원본과 참조가 끊겨있는 것이 특징이다.
Object.assign
// Copy
const obj = { a: 1 };
const copy = Object.assign({}, obj);
console.log(copy); // { a: 1 }
console.log(obj == copy); // false
// Merge
const o1 = { a: 1 };
const o2 = { b: 2 };
const o3 = { c: 3 };
const merge1 = Object.assign(o1, o2, o3);
console.log(merge1); // { a: 1, b: 2, c: 3 }
console.log(o1); // { a: 1, b: 2, c: 3 }, 타겟 객체가 변경된다!
// Merge
const o4 = { a: 1 };
const o5 = { b: 2 };
const o6 = { c: 3 };
const merge2 = Object.assign({}, o4, o5, o6);
console.log(merge2); // { a: 1, b: 2, c: 3 }
console.log(o4); // { a: 1 }
두 가지 경우를 잘 구분해야한다.
Object.assign(o1, o2, o3) : shallow copy가 된다.
Object.assign({}, o4, o5, o6) : 새로운 객체를 타겟으로 deep copy가 된다. 하지만 완벽한 deep copy라고 할 수 없는 점은 객체 내부의 객체는 shallow copy가 된다.
… operater (전개연산자)
const obj = {
a: 1,
b: {
c: 2,
},
};
const copiedObj = {...obj}
copiedObj.b.c = 3
console.log(obj.b.c); // 3
비슷하게 call by reference, call by value의 개념도 중요하다. 함수에서 파라미터를 받아올 때 메모리 주소값으로 받아와 파라미터의 원본을 변경가능하다면 call by reference이며, 변수 값만 받아오면 call by value이다.
reference
🍳호이스팅과 TDZ는 무엇일까?
1. 스코프, 호이스팅, TDZ
스코프(scope) : 변수에 접근할 수 있는 범위를 말한다. 스코프에는 전역 스코프, 함수 스코프, 블록 스코프가 있다.
호이스팅(hoisting) : 인터프리터가 변수와 함수의 메모리 공간을 선언 전에 미리 할당하는 것을 의미합니다.
TDZ(Temporal Dead Zone) : 선언 전에 변수에 접근하는 것을 금지한다.
TDZ에 영향을 받는 구문들은 const, let, class, super, 기본함수 매개변수가 있다.
Reference
2. 함수 선언문과 함수 표현식에서 호이스팅 방식의 차이
함수 선언문은 호이스팅에 영향을 받지만 함수 표현식은 호이스팅에 영향을 받지 않는다.
함수 선언문은 호이스팅되어 블록문 밖에서 호출이 가능하다는 특징이 있으며,
함수 표현식은 호이스팅되지 않으므로 정의된 범위에서 로컬 변수의 복사본을 유지할 수 있다.
함수 선언문
bear();
function bear(){
console.log("bear");
}
함수 표현식
// error 발생!
bear();
const bear = function(){
console.log("bear");
}
3. let, const, var, function이 실행되는 원리
let, const는 호이스팅되지 않아 TDZ가 존재하는 반면 var, function은 호이스팅되어 TDZ에 영향을 받지 않는다.
자바스크립트의 엔진은 소스코드의 평가 과정을 거치면서 소스코드를 실행하기 위한 준비를 한다.
이때 자바스크립트 엔진은 변수 선언을 포함한 모든 선언문을 소스코드에서 찾아내 먼저 실행한다.
그리고 소스코드의 평가 과정이 끝나면 비로소 변수 선언을 포함한 모든 선언문을 제외하고 소스코드를 한 줄씩 순차적으로 실행한다.
자바스크립트 엔진은 변수 선언이 소스코드의 어디에 있든 상관없이 다른 코드보다 먼저 실행한다. 따라서 변수 선언이 소스코드의 어디에 위치하는지와 상관없이 어디서든지 변수를 참조할 수 있다.
변수 선언문이 코드의 선두로 끌어 올려진 것처럼 동작하는 자바스크립트 고유의 특징을 변수 호이스팅이라 한다.
- 모던 자바스크립트 42-43p
4. 실행 컨텍스트와 콜 스택
실행 컨텍스트는 자바스크립트의 동작 원리를 담고 있는 핵심 개념이다. 자바스크립트가 실행되기 위해 필요한 정보들을 담고 있다.
각각의 함수들은 각각의 컨텍스트를 가지며 실행될 때마다 새로운 실행 컨텍스트가 생성된다. 생성된 컨텍스트들은 콜스택에 쌓이게 된다.
콜스택은 실행 컨텍스트들이 쌓여 어느 코드가 실행중인지 여부를 판단할 수 있는 공간이다.
실행이 완료되면 콜 스택에 쌓여있던 실행 컨텍스트는 제거되고, 이전 단계의 실행 컨텍스트가 호출된다.
일반적으로 전역 실행 컨텍스트가 가장 먼저 콜 스택에 쌓이게 되고, 그 뒤로 호출되는 함수들의 실행 컨텍스트 순서대로 쌓이게 된다.
실행 컨텍스트에 대해서는 더 자세하게 정리해봐야겠다.
Reference
5. 스코프 체인, 변수 은닉화
스코프체인 : 스코프가 어떻게 연결(chain)되고 있는지 보여주는 것
변수 은닉화 : 직접적으로 변경되면 안 되는 변수에 대한 접근을 막는 것
'항해99' 카테고리의 다른 글
[항해99] 4주차 React 숙련 회고 (0) | 2022.12.11 |
---|---|
[항해99] 3주차 React 입문 회고 (2) | 2022.12.04 |
[항해99] 2주차 알고리즘 회고 (2) | 2022.11.27 |
[항해99] 버킷리스트에 취소/삭제기능 추가하기 (1) | 2022.11.20 |
[항해99] 1주차 미니프로젝트 회고 (5) | 2022.11.20 |