node.js 과제

황수민·2022년 7월 21일

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

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

JavaScript는 느슨한 타입(loosely typed)의 동적(dynamic) 언어이다.

JavaScript의 변수는 어떤 특정 타입과 연결되지 않으며,

모든 타입의 값으로 할당 및 재할당 가능하다.

동적 언어이기 때문에 변수 생성시 원시변수의 타입을 미리 선언하지 않아도 된다는 장점이 있다.

2) JavaScript 형변환

뒤에서 설명

3) ==, ===

==연산자는 값을 비교하기 전에 타입이 다를 경우 타입을 변환 후 값을 비교한다.

하지만, === 연산자는 타입을 변환하지 않으므로 == 연산자에 비해 비교하는 방식이 엄격하다.

자바스크립트는 엄격한 비교와 유형변환 비교를 모두 지원하므로, 어떤 연산자가 어떤 비교조건에 사용되는지가 중요하다.

===는 변수 유형을 고려하는 반면, ==는 변수 값을 기반으로 유형을 수정한다.

4) 느슨한 타입(loosely typed)의 동적(dynamic) 언어의 문제점과 보완방법

느슨한 타입(loosely typed)의 동적(dynamic)언어의 문제점은 실행도중 변수에 예상치 못한 타입이 들어와 타입에러가 발생할수 있고,

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

자바스크립트의 불편함을 해소하기 위해 정적 타입 체크와 강력한 문법을 추가한 TypeScript를 사용하여 보완 가능 하다.

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

undefined는 '값이 할당되지 않은 상태’를 나타낼 때 사용한다.

변수는 선언했지만, 값을 할당하지 않았다면 해당 변수에 undefined가 자동으로 할당된다.

원시 값을 나타내며, JavaScript의 원시자료형 중 하나이다.

let age
console.log(age)

'undefined'가 출력된다.

나이(age)에 변수가 할당되지않아 자동으로 undefined가 할당된다.

null 은 어떤 값이 의도적으로 비어있음을 표현하는 것이다.

다른 언어에서는 null을 '존재하지 않는 객체에 대한 참조'나 '널 포인터(null pointer)'를 나타낼 때 사용한다. 하지만 자바스크립트에선 null을 ‘존재하지 않는(nothing)’ 값, ‘비어 있는(empty)’ 값, ‘알 수 없는(unknown)’ 값 이렇게 의도적으로 나타내는 데 사용한다.

let age = null
console.log(age)

'null'이 출력된다.

나이(age)를 알 수 없거나 그 값이 비어있음을 보여준다.

null은 let age = null 처럼 비어있다는것을 표현하기위해 사용하고, undefined는 let age 처럼 값이 할당되지 않을 경우 자동으로 할당되는것 이라고 볼수 있다.

JavaScript 객체와 불변성이란?

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

참고) https://hwb0218.tistory.com/41

💡 데이터의 두 가지 타입

원시 자료형(primitive type)과 참조 자료형(reference type)이 있다.
▶ 원시 자료형이 할당될 때에는 변수에 값(value) 자체가 담긴다.
▶ 참조 자료형이 할당될 때는 보관함의 주소(reference)가 담긴다.

💡 원시 자료형 (Primitive data type) = 원시타입, 기본형 데이터

▶ 기본형 데이터가 할당된 변수는 자기 자신만의 고유한 값을 가진다.
▶ 기본형 데이터는 변경 불가능한 값. 한 번 생성된 기본형 데이터는 변경할 수 없다(불변성)
▶ 기본형 데이터를 할당한 변수는 재할당 이외에는 변수값을 변경할 수 없다.
▶ 객체가 아니면서 method를 가지지 않는 6가지의 기본형 데이터 타입이 있다.
string, number, boolean, undefined, symbol, (null은 원시 타입과 거의 같게 사용 되지만 엄밀히 따지면 객체이다. 빈 참조를 나타내는 데 자주 사용된다.)

  • number : 3.141592
  • string : ‘3.141592’
  • boolean : true & false
  • undefined : 변수가 정의되지 않았거나 값이 없다.
  • null : 의도적으로 비어있음을 표현하기 위해 null 이라는 것이 들어있다.
  • symbol(ES6부터 추가됨, 객체 속성을 만드는 데이터 타입)

💡 참조 자료형 (Reference data type) = 참조타입

▶ 자바스크립트에선 원시 자료형이 아닌 모든 것은 참조 자료형.
▶ 참조 자료형은 기존에 고정된 크기의 보관함이 아니다.
▶ 참조 자료형을 변수에 할당할 때는 변수에 값이 아닌 주소를 저장한다.
▶ 동적으로 크기가 변하는 데이터를 보관하기 위해 변수가 아닌 다른 곳에 데이터를 저장하고 변수에는 그 주소만 할당한다.
▶ 참조형은 원시형 데이터의 집합. 배열([])과 객체({}), 함수(function(){})가 대표적

  • 배열 Array : [0,1,2,3,4]
  • 객체 Object {name : “John”, age : 16}
  • 함수 function
  • 정규표현식 RegExp

2) JavaScript 형 변환

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

💡 암시적 형 변환(Implicit type conversion)

자바스크립트 엔진이 필요에 따라 자동으로 데이터 타입을 변환시키는 것이다.

1) 산술연산자

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

number + number // number
number + string // string
string + string // string
string + boolean // string
number + boolean // number
50 + 50; //100
100 + “점”; //”100점”
“100” + “점”; //”100점”
“10” + false; //”100"
99 + true; //100

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

//다른 연산자(-,,/,%)
string
number // number
string string // number
number
number // number
string boolean //number
number
boolean //number
“2” false; //0
2
true; //2

2) 동치비교

아래 예제는 엄격하지 않은 동치(==)비교이며, 아래의 결과값은 좌우항 변환할 경우 모두 '0==0이기 때문에' 'true'이다.

null == undefined // true 0 == 0
“0” == 0 // true 0 == 0
0 == false // true 0 == 0
“0” == false // true 0 == 0

여기서 유의해야할 점은 위의 비교는 엄격하지 않은 동치 비교일 경우이기 때문에, 두 값을 비교할때 데이터타입을 변환하지 않는 엄격한 동치(===)비교와 혼동되지 않도록 한다.

💡 명시적 형 변환(Explicit Type Conversion)

명시적 변환이란 개발자가 의도를 가지고 데이터 타입을 변환시키는 것이다.

타입을 변경하는 기본적인 방법은 Object(), Number(), toString(), Boolean() 와 같은 함수를 이용하는데 new 연산자가 없다면 사용한 함수는 타입을 변환하는 함수로써 사용된다.

var trans = 100; //Number
Object(trans); //100
console.log(typeof trans); //Number
toString(trans); //”100"
console.log(typeof trans); //String
Boolean(trans); //true
console.log(typeof trans); //Bolean

1) A Type → Number Type

다른 자료형을 숫자타입으로 변형하는 방법은 아래와 같다.

Number()
Number()는 정수형과 실수형의 숫자로 변환한다. 보통 문자형을 숫자형으로 변경할때 사용한다. 숫자로 변환되지 않는 경우에는 NaN(Not a Number)을 반환한다.

Number(“12345”); //12345
Number(“2”*2); //4

펄시한 값(falsy values : null, false,"빈문자열")에 대해서는 0으로 표현.
트루시한 값(truthy values)에 대해서는 1로 표현.
array의 경우는 Number()함수에서 사용하는 0을 반환.

const falsy1 = null;
Number(falsy1); // 0;

const falsy2 = '';
Number(falsy2); // 0;

const falsy3 = false;
Number(falsy3); // 0;

const truthy1 = [];
Number(truthy1); // 0;

const truthy2 = true;
Number(truthy2); // 1;

const truthy3 = {};
Number({}); // NaN;

parseInt()
parseInt()는 정수형의 숫자로 변환한다. 만약 문자열이 '숫자0'으로 시작하면 8진수로 인식하고, '0x, OX'로 시작한다면 해당 문자열을 16진수 숫자로 인식한다. 또한 앞부분 빈 공백을 두고 나오는 문자는 모두 무시되어 NaN을 반환한다.

parseInt(“27”) //27
parseInt(0033); //27
parseInt(0x1b); //27
parseInt(“ 2”); //2
parseInt(“ 2ㅎ”); //2
parseInt(“ ㅎ2”); //NaN

parseFloat()
parseFloat()는 부동 소수점의 숫자로 변환한다. parseInt()와는 달리 parseFloat()는 항상 10진수를 사용하며 parseFloat() 또한 앞부분 빈 공백을 두고 나오는 문자는 모두 무시되어 NaN을 반환한다.

parseFloat(“!123”); //NaN
parseFloat(“123.123456”); //123.123456
parseInt(“123.123456”); //123
parseFloat(“ 123.123456”); //123.123456
parseFloat(“ a123.123456”); //NaN

단항연산자(unary-operators)로 숫자형 타입 변경

+'1000'; // 1000
+'-1000'; // -1000
+'Infinity'; // Infinity
-'1000'; // -1000
+true; // 1
+[]; /// 0
+false; // 0
+null; // 0
+'';// 0

위 예제를 보면 단일 연산자를 이용한 숫자형 변환은 Number와 동일한 동작을 하는 것을 볼 수 있다. 자바스크립트 함수를 사용하지 않고 타입변환을 할 수 있는 쉽고 효율적인 방법.

2) A Type → String Type

다른 자료형을 문자타입으로 변형하는 방법은 아래와 같다.

String()

String(123); //”123"
String(123.456); //”123.456"

toString()
주어진 값을 문자열로 변환 후 반환. toString()는 인자로 기수를 선택할 수 있다. 인자를 전달하지 않으면 10진수로 변환한다.

var trans = 100;
trans.toString(); //”100"
trans.toString(2); //”1100100"
trans.toString(8); //”144"
var boolT = true;
var boolF = false;
boolT.toString(); //”true”
boolF.toString(); //”false”

toFixed()
toFixed()의 인자를 넣으면 인자값만큼 반올림하여 소수점을 표현하며 소수점을 넘치는 값이 인자로 들어오며 '0'으로 길이를 맞춘 문자열을 반환한다.

var trans = 123.456789;
var roundOff = 99.987654;
trans.toFixed(); //”123"
trans.toFixed(0); //”123"
trans.toFixed(2); //”123.46"
trans.toFixed(8); //”123.45678900"
roundOff.toFixed(2); //”99.99"
roundOff.toFixed(0); //”100"

3) A Type → Boolean Type

자바스크립트에서는 Boolean타입으로 변경은 Boolean 또는 부정연산자(!)를 사용하여 Boolean값을 만들어낸다. 부정연산자는 의미그대로 !을 사용하면 Boolean() 반대의 값을 리턴한다.

다른 자료형을 불린타입으로 변형하는 방법은 아래와 같다.

Boolean()

Boolean(100); //true
Boolean(“1”); //true
Boolean(true); //true
Boolean(Object); //true
Boolean([]); //true
Boolean(0); //false
Boolean(NaN); //false
Boolean(null); //false
Boolean(undefined); //false
Boolean( ); //false
const numb1 = 0;
Boolean(numb1); // false
!!numb1; // false
!numb1; // true

3) 불변 객체를 만드는 방법

💡 불변 객체 (Immutable Object)

객체 내부 프로퍼티를 변경할 때마다 새로운 객체를 만들어 재할당하기로 정하거나
자동으로 새로운 객체를 만드는 도구를 활용하여 (ex. immutable.js, immer.js 등의 라이브러리) ➡불변성 확보

💡 불변 객체가 필요한 경우

객체에 변화를 가해도 원본이 그대로 남아있어야 하는 경우
ex) 정보가 바뀌었으면 알림 전송하는 경우, 바뀌기 전의 정보와 바뀐 후의 정보를 보여줘야하는 경우 등

var user = {
name: "namju",
gender: "male",
};
var changeName = function (user, newName) {
var newUser = user;
newUser.name = newName;
return newUser;
};
var user2 = changeName(user, "yun");
// 아래의 if문은 무시되어 지나침
if (user !== user2) {
console.log("유저 정보가 변경되었습니다.");
}
console.log(user.name, user2.name); // yun yun
console.log(user === user2); // true

💡 불변 객체 만들기

새 객체를 하드코딩

var user = {
name: "namju",
gender: "male",
};
var changeName = function (user, newName) {
return {
name: newName,
gender: user.gender,
};
};
var user2 = changeName(user, "yun");
if (user !== user2) {
console.log("유저 정보가 변경되었습니다."); // 유저 정보가 변경되었습니다.
}
console.log(user.name, user2.name); // namju yun
console.log(user === user2); // false

➡ 객체에 프로퍼티가 많을수록 하드코딩해야하는 수고가 너무 늘어남
프로퍼티 개수에 상관 없이 모든 프로퍼티를 복사하는 함수를 만드는 것이 좋음

4) 얕은 복사와 깊은 복사

💡 얕은 복사

바로 아래 단계의 값들만 복사하는 방법
: for in 반복문으로 새 객체에 원래 객체의 프로퍼티들을 복사하는 함수

var copyObject = function (target) {
var result = {};
for (var prop in target) {
result[prop] = target[prop];
}
return result;
};

이 함수를 이용해 새로운 객체를 만들어 프로퍼티를 변경할 수 있음

var user = {
name: "namju",
gender: "male",
};
var user2 = copyObject(user);
user2.name = "yun";
if (user !== user2) {
console.log("유저 정보가 변경되었습니다."); // 유저 정보가 변경되었습니다.
}
console.log(user.name, user2.name); // namju yun
console.log(user === user2); // false

이 방법의 단점

  • 프로토타입 체이닝 상의 모든 프로퍼티를 복사
  • getter / setter는 복사하지 않음
  • 얕은 복사만 수행함
    ➡ 협업하는 모든 개발자들에게 무조건 copyObject 함수를 사용하기로 합의시키는 것이 어려움!

프로토타입 체이닝
모든 객체(함수 포함)에는 프로토타입 객체가 포함되어 있음
그렇기 때문에 얕은 복사를 한 객체는 부모(원본) 객체의 프로토타입에도 접근할 수 있음 (스코프 체이닝처럼 계속 상위로 가서 탐색을 하는 식)

💡 깊은 복사

내부의 모든 값들을 하나하나 찾아서 전부 복사하는 방법

얕은 복사만으로는 중첩된 객체를 제대로 복사할 수 없음 (바로 아래 단계의 값들만 새로운 데이터 주소로 복사시키는 것)

한 단계 더 nesting 된 info 객체의 프로퍼티들에는 기존의 데이터를 그대로 참조하고 있는 것! ➡이런 nested 된 모든 프로퍼티들에 대한 복사를 재귀적으로 수행해야 깊은 복사가 됨

var user = {
name: "namju",
info: {
hobby: "bike",
location: "seoul",
happy: true,
},
};
var user2 = copyObject(user);
user.name = "yun namju";
user.info.hobby = "read";
user.info.location = "busan";
console.log(user.name === user2.name); // false
console.log(user.info.hobby === user2.info.hobby); // true : 두 객체가 모두 변경됨
console.log(user.info.location === user2.info.location); // true : 두 객체가 모두 변경됨

객체의 깊은 복사를 수행하는 함수

var copyObjectDeep = function (target) {
var result = {};
if (typeof target === "object" && target !== null) {
for (var prop in target) {
result[prop] = copyObjectDeep(target[prop]); // 재귀적 호출
}
} else {
result = target;
}
return result;
};

➕ hasOwnProperty 메서드를 통해 프로토타입 체이닝을 통해 상속된 프로퍼티는 복사하지 않도록 할 수 있음
➕ ES6, ES2017의 경우 Object.getOwnPropertyDescriptor, Object.getOwnPropertyDescriptors를 통해 getter/setter를 복사할 수 있음

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

1) 스코프, 호이스팅, TDZ

💡 스코프

식별자 접근 규칙에 따른 유효범위로, 선언 시점에 따라 전역/지역 변수로 나뉜다.

  • Global Scope : 전역에 선언되어 있어 어디서든 변수에 접근 가능
  • Local Scope : 해당 지역에서만 접근 가능
    ** Local Scope은 Global Scope 보다 높은 우선순위

자바스크립트에서 모든 코드 블록(if, for, while, try/catch 등)이 지역 스코프를 만들며, 이러한 특성을 블록 레벨 스코프라 한다.
하지만 var 키워드로 선언된 변수는 오로지 함수의 코드 블록만을 지역 스코프로 인정한다. 이를 함수 스코프라 한다.

  • Var : function -scope
    즉, var는 함수(){} 내에서 유효하다, for 문 끝난 다음에 i를 호출하면 값이 출력된다.
for(var j=0; j<10; j++) {
  		console.log('j', j)}
	console.log('after loop j is ', j) // after loop j is 10
function counter () {
	for(var i=0; i<10; i++) {console.log('i', i)}}
counter()
console.log('after loop i is', i) // ReferenceError: i is not defined
  • Let & Const : block - scope
    let은 변수에 재할당 가능/ const는 변수 재선언 및 재할당 불가능

💡 호이스팅

  • 함수가 실행되기 전에 함수 안의 변수값을 모두 모아서 유효 범위 최상단에 선언
  • var변수 선언과 함수 선언문에서만 호이스팅이 일어난다.
  • Let/const 변수 선언과 함수 표현식에서는 호이스팅이 발생하지만, 할당되기 전까지 변수는 TDZ에 들어가있어 ReferenceError를 출력한다.

💡 TDZ (Temporal Dead Zone)

Function{ 변수 선언 -> 변수 초기화 -> 변수 값 할당 }
변수가 선언되고, 초기화가 이루어지기 전까지의 구간

  • TDZ 에 영향을 받는 것 - let, const, class, class의 construction() 내부의 super(), 함수 매개변수
  • TDZ에 영향 받지 않는 것 - var, function (함수 선언식), import

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

함수 선언문은 function 함수명() {구현 로직} 으로 표현하고,
함수 표현식은 var test1 = 1 function() {return ‘익명/기명 함수표현식’ } 표현할 수 있다.

function getName() {
    console.log('name');
} // 함수 선언문 -> 호이스팅 대상

var name = function() {
   console.log('name');
};// 함수 표현식 -> 호이스팅 X
  • 함수 선언문에서는 아래 두 예제에서 에러 없이 값이 호출된다.
count();

function count() {
    console.log('count는 2이다.');
}
function count() {
    console.log('count는 2이다.');
}
count();
  • 함수 표현문에서는 에러/정상/에러가 발생한다.

1)

count();

var count = function() {
    console.log('count는 1이다.');
} //error : count is not function

2)

var count = function() {
    console.log('count는 1이다.');
}

count();

3)

count();

let count = function() {
    console.log('count는 1이다.');
} //Referrence Error

1) var 는 호이스팅의 영향을 받으므로 위로 끌어올려진다.
그래서 var count; 가 가장 먼저 실행되지만 변수에 아무 값도 담지 않았으므로 undefined 상태이다.
그 후로 count()가 호출되면 위에 선언한 count가 호출되므로 변수를 호출하는 격이된다.
그러므로 not function 이라는 에러 메시지가 나타난다.

2) var count가 호이스팅으로 인해 위로 끌어올려지지만, count() 호출 전 count에 함수를 담으므로
count()를 호출하였을 때 정상 작동한다.

3) let 은 호이스팅의 영향을 받지 않기 때문에, 예시에 작성한 코드 순서대로 실행된다.
그러므로 count()라는 함수가 정의되지 않았는데 호출하였기 때문에 에러가 발생한다.

3) let, const, var, function의 작동 원리

images

4) 실행 컨텍스트와 콜 스택

💡 실행 콘텍스트 (Record/outer)

코드를 실행하는데 필요한 환경을 제공하는 객체

💡 콜 스택

콜 스택은 프로그램을 실행할 때 현재 실행 중인 함수가 가장 위에 쌓이고 그 함수의 실행이 끝나면 사라지는 스택 자료구조
프로그램이 시작되면 콜 스택에 함수들이 쌓이고, 가장 최근에 호출되어 쌓인 순서대로 함수들이 종료되면서 스택이 모두 비어지면 프로그램이 끝난다.

5) 스코프 체인, 변수 은닉화

💡 스코프 체인

변수를 참조할 때 js는 스코프 체인을 통해 변수를 참조.
변수가 해당 지역에 없다면, 상위 스코프로 올라가면서 변수를 참조한다.

스코프체인

💡 변수 은닉화

외부에서 임의로 해당 속성의 값을 변경하지 못하도록 하는 것
변수명 앞에 underscore( )를 포함하면 그 변수는 Private variable이 되지만 접근 가능하기 때문에 클로저를 사용한다.
클로저는 외부 변수를 기억하고 이 외부 변수에 접근할 수 있는 함수를 의미하며, 외부 함수와 연결되어 있기 때문에 값이 동적으로 바뀌어도 반영된다_

var click = (function () {
  // 클릭한 횟수를 기억
  var count = 0;
  return function () {
    ++count;
    return count;
  };
})();

// 버튼을 누를 때 마다 누른 횟수 출력
function print() {
  console.log(click());
}

print 함수 안에 있는 click 함수는 이름이 없는 클로저를 반환하고 호출한다. (변수 은닉)
클로저는 click 함수의 count변수 값을 계속 기억하고 있으므로 버튼을 누른 횟수를 출력이 가능하다.

실습과제

let b = 1;

function hi() {
    const a = 1;
    let b = 100;
    b ++;
    console.log(a,b); //1 101
}
//console.log(a)
console.log(b); //1
hi();
console.log(b); //1
profile
인생 2막은 28살부터!

0개의 댓글