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

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

loosely typed:
느슨한 타입의 언어의 경우 관련되지 않은 타입의 개체들 간의 conversation이 가능하다. strongly-typed 형태에서는 불가능하다.

아래 예시를 보면, 파이썬의 경우 int와 string의 조합은 에러가 발생하지만 자바스크립트에서는 잘 작동되는 것을 확인할 수 있다.

var = 21;            #type assigned as int at runtime.
var = var + "dot";   #type-error, string and int cannot be concatenated.
print(var);
value = 21;             
value = value + "dot";
console.log(value);
/*
This code will run without any error. As Javascript
is a weakly-typed language, it allows implicit conversion
between unrelated types.
*/

dynamically typed:
런타임에 비로소 타입이 결정되는 언어입니다. 변수를 생성할 때 마다 매번 타입을 써줄 필요가 없기 때문에 기본적으로는 편하고 빠르게 코드를 작성하기 좋습니다. 주의할 점은 실행되는 시점에서 오류를 출력한다는 것이 있다.

statically-typed:
컴파일 시간에 변수의 타입이 결정되는 언어이며 자료의 type은 런타임 이전에 결정된다. 변수에 들어갈 값의 형태에 따라 type을 지정해 주어야 하며 이에 맞지 않을 시 컴파일 에러가 발생한다. 컴파일 시간에 변수의 타입을 체크하므로 사소한 버그를 쉽게 체크할 수 있다는 장점

타입을 지정해주는 예 (C언어):
타입을 변경할 수 없으며 타입이 맞는 값만 할당할 수 있다.

char c;
int num;


이미지에서는 strong-weak으로 표현했는데 weak=loosed이다.

Javascript는 loosely typed dynamic language이다. Javascript의 변수는 어떤 특정한 타입과 연결되지 않으며, 모든 타입의 값으로 할당 및 재할당이 가능하다. (여기서 타입이라는 건, 숫자인지 문자열인지 등등) 즉, 변수는 선언이 아닌 할당에 의해 타입이 결정 (타입추론 type inference)된다.

예)

let foo = 42 // foo가 숫자
foo = 'bar' // foo가 이제 문자열
foo = true // foo가 이제 불리언

참고링크

- JavaScript 형변환

***자바스크립트 deep dive 108쪽 참고

1. 암묵적 타입 변환 (implicit coercion)/타입 강제 변환 (type coersion)

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

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

// 더하기(+)
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 //number2* false; //0
2 * true; //2

2. 명시적변환(explicit coersion)/ 타입 캐스팅(type casting)

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

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

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

변환하는 방법도 숫자의 경우 parseInt(), parseFloat(), Number(), 문자의 경우 toString() 등등 상황에 따라 여러가지가 있으며 이것은 javascript 중급 tutorial에서 다룬 적이 있다.

참고링크

- ==, ===

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

null == undefined0== 0
0 == false0== false

엄격한 동치비교 (===)
데이터타입을 변환하지 않고 비교하기 때문에 데이터타입이 동일해야 한다. 실무에서는 예상치 못한 에러를 방지하기 위해 엄격한 동치비교를 많이 쓴다.

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

***자바스크립트 deep dive 70쪽 참고

동적 타입 언어가 가지고 있는 구조적인 단점:

  • 변수값이 언제든지 바뀔 수 있기 때문에 복잡한 셋팅에서 변화하는 변수 값 추적이 어려울 수 있다.
  • 자바스크립트 엔진이 개발자의 의도와 다르게 강제로 타입을 변환시키기 때문에 유연성은 있지만신뢰성이 떨어진다.

이러한 점을 보완할 수 있는 방법:

  • 변수는 꼭 필요한 경우에만 제한적으로 사용할 것
  • 변수의 유효 스코프(작동범위)를 최대한 좁게 만들어 변수로 인한 부작용을 억제
  • 전역 변수를 최대한 사용하지 않는다.
  • 전역변수의 문제점: 자바스크립트 deep dive 200쪽
  • 변수보다는 상수를 이용한다. (가능하다면 let보다는 const)
  • 변수 이름을 변수의 목적이나 의미에 맞게 네이밍할 것-> 마치 첫 아이 이름 짓듯

*전역 변수(global variable): 블록문 밖에서 선언해서 블록문 안에서도 사용할 수 있는 변수

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

***자바스크립트 deep dive 108쪽 참고

undefined와 null 모두 하나의 데이터 타입이라고 정의할 수 있다. 둘다 각각의 타입명의 유일한 값이 된다.

undefined:
아무값도 할당받지 않은 상태를 의미하며, undefined 타입은 변수 자체의 값 또한 undefined이다. var로 선언시 암묵적으로 변수의 디폴트는 undefined이다. (선언 후 빈 상태일텐데 자바스크립트 엔진이 undefined로 초기화하는 것)

var a;
console.log(a); // undefined
console.log(typeof a); // undefined

null:
자바스크립트는 대소문자를 구분하므로 null은 Null, NULL 등과는 다르다. 변수에 값이 없다는 것을 의도적으로 명시(의도적 부재)할 때 사용하며 이 뜻은 이전에 참조하던 값을 더 이상 참조하지 않겠다는 의미이다. 그렇게 하면 자바스크립트 엔진은 그 공간에 대해 가비지 콜렉션을 수행할 것이다.

*함수가 반환하는 null: 함수가 유효한 값을 반환할 수 없을때 명시적으로 null을 반환한다. 하지만 이런 상황에 undefined가 반환되어야 한다는 의견도 있음

var element = document.querySelector('.myClass');
// HTML 문서에 myClass 클래스를 가진 요소가 없다면 null을 반환한다.
console.log(element); // null

*엄밀히 말해서는 null은 객체이고 참조 자료형이다. (null이 빈 참조를 나타내는 데 자주 사용되기 때문)

var nullType = null;
console.log(typeof null); // object

*가비지 컬렉션이란?
프로그램에서 더 이상 사용하지 않는 메모리를 자동으로 정리하는 것으로 비워줘야 할 메모리 공간을 정리함으로 메모리 누수(memory leak)를 방지하는 것

참고링크

JavaScript 객체와 불변성이란 ?

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

***자바스크립트 deep dive 137쪽 참고

자바스크립트가 제공하는 7가지 데이터 타입 (숫자, 문자열, 불리언, null, defined, 심벌, 객체타입)은 크게 원시타입 (기본형 타입) 과 객체타입(참조형 타입)으로 나눈다.

기본형 데이터:
원시 값은 변경이 불가능한 값(immutable value)로, 변수에 할당하면 확보된 메모리 공간에는 실제 값이 저장된다.

참조형 데이터:
변경가능한 값 (mutable value)로, 객체를 변수에 할당하면 확보된 메모리 공간에는 참조 값이 저장된다. (원본의 원시 값이 복사되어 전달된다는 뜻) 이를 참조에 의한 전달 (pass by reference)라고 한다.

*여기서 변경이 불가능하다는 말은 변수가 아니라 값에 대한 진술이이다. 변수는 언제든지 재할당을 통해 변수 값을 변경(엄밀히 말하면 교체)할 수 있다.

- 불변 객체를 만드는 방법

자바스크립트에는 자바와 다르게 변수에 접근제한자를 두어 외부에서 변경할 수 없도록 만드는게 불가능하다. 따라서 객체를 불변하게 만들기 위해선 약속을 통해 지켜야 한다. 다시 말해, 기존 객체를 그대로 가져와서 재할당 해주는게 아니라, 기존 객체는 그대로 두고 함수를 이용해 새로운 객체를 만들어야 한다.

*객체의 불변을 유지할 수 있도록 도와주는 라이브러리를 이용할 수 있다 (immutable.js , baobao.js)

- 얕은 복사와 깊은 복사

***자바스크립트 deep dive 150쪽 예제 참고

얕은 복사:
아래 예시처럼 객체를 직접 대입하는 경우 참조에 의한 할당이 이루어지므로 둘은 같은 데이터(주소)를 가지고 있다. 때문에 obj2의 property를 수정하고, obj1를 출력해도 obj2 값과 동일하다.

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

깊은 복사:
주소를 복사해서 공유하는 것이 아니라, 아예 새로운 객체안 속성(property)만 복사 해서 사용

…(spread) 연산자를 통한 복사와 Object.assign() 메소드를 통한 복사: obj1과 obj2는 다른 주소를 갖게되지만 딱, 1 depth 까지만 깊이 복사되고 그 이상은 얕게 복사됨

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

깊은 복사 하는 법: 2중 이상의 객체가 중첩된 경우 lodash의 cloneDeep 기능을 이용하면 된다!

참고링크

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

- 스코프, 호이스팅, TDZ

***자바스크립트 deep dive 189쪽 참고

스코프
모든 식별자(변수이름, 함수이름, 클래스이름 등)는 자신이 선언된 위치에 의해 다른 코드가 식별자 자신을 참조할 수 있는 유효범위가 결정되는데 이것이 스코프이다.

스코프는 식별자가 유효한 범위이다.

다르게 말하면, 자바스크립트 엔진이 식별자를 검색할 때 사용하는 규칙이라고도 할 수있다.

*코드의 문맥과 환경
코드가 어디서 실행되며 주변에 어떤 코드가 있는지를 랙시컬 환경lexical environment이라고 한다. 즉, 코드의 문맥은 랙시컬 환경으로 이루어진다. 이를 구현한 것이 실행 컨텍스트execution context이며 모든 코드는 실행 컨텍스트에서 평가되고 실행된다.

예) 여기서 전역 스코프를 가진 x와 함수 스코프를 가진 x는 별개의 변수이다.

var x = 'global'; //전역 스코프를 가진다.

function foo() {
  var x ='local'; //함수 스코프를 가진다.
  console.log(x);
}

foo();

console.log(x);

스코프가 없다면? : 같은 이름을 갖는 변수는 충돌을 일으키므로 프로그램 전체에서 하나밖에 사용할 수 없게 된다. (변수 이름은 중복되지 않게 하는게 기본이긴 하다.)

지역local이란? : 함수 몸체 내부를 뜻한다. 지역은 지역 스코프를 만든다. 지역 변수는 지역 스코프와 하위 지역 스코프에서 유효하다. (함수1 내에 또다른 함수2가 있을 때 함수1의 지역 변수가 함수2에서도 유효 함)

*함수의 중첩: 함수내에 또다른 함수가 들어가는 것으로 nested function이라고 하며 이럴때 스코프는 계층적 구조를 가진다.(스코프 체인 형성)
외부함수outer function = 상위함수

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

***자바스크립트 deep dive 158쪽 참고

함수 정의방식은 4가지가 있는데, 미묘하게 다르다.

  1. 함수 선언문
function add(x,y) {
  return x+y;
  1. 함수 표현식
var add = function (x,y) {
  return x+y;
  
  1. function 생성자 함수
var add = newFunction('x','y','return s+ y');
  1. 화살표 함수(ES6)
var add = (x,y) => x + y;
  • 함수선언식은 함수 이름을 생략할 수 있으나 함수 이름을 생략할 수 없다.
    (function add () {}에서 'add' 생략 불가)
  • 자바스크립트의 함수는 일급객체이다. 일급객체란 함수를 값처럼 자유롭게 사용할 수 있다는 의미이다. 일급객체인 함수는 함수 리터럴로 생성한 함수 객체를 변수에 할당할 수 있다. 이러한 함수 정의 방식이 바로 함수 표현식이다.

***자바스크립트 deep dive 164쪽 참고

  • 함수선언식과 함수 표현식의 호이스팅 방식의 차이:
    함수 선언문으로 정의한 함수는 함수 선언문 이전에 호출이 가능!
    하지만 함수 표현식으로 정의된 함수는 이전에 호출이 불가능!

why?

두 함수의 생성 시점이 다르기 때문. 모든 선언문(let같은)이 그러하듯, 함수 선언문도 런타임 이전에 자바스크립트 엔진에 의해 먼저 실행된다. 함수 표현식은 변수에 할당되는 값이 리터럴인 문이다. 그러므로 할당문이 실행되는 시점, 즉 런타임에 평가되는 것 > 다시말해 함수 호이스팅이 아니라 변수 호이스팅이 발생한다.

- 실행 컨텍스트와 콜 스택

***자바스크립트 deep dive 364쪽 참고

실행 컨텍스트execution context: 소스코드를 실행하는 데 필요한 환경을 제공하고 코드의 실행결과를 실제로 관리하는 영역이다. > 식별자(변수, 함수, 클래스등의 이름) 을 등록하고 관리하는 스코프와 코드 실행 순서 관리를 구현한 내부 매커니즘으로, 모든 코드는 실행 컨텍스트를 통해 실행되고 관리된다.

콜스택call stack: 실행 컨텍스트 스택이라고 부른다. 실행 컨텍스트는 스택 자료구조로 관리된다. 코드가 실행되는 시간의 흐름에 따라 스택에는 실행 컨텍스트가 추가push되고 제거pop된다.

*실행 컨텍스트 흐름:
전역 코드의 평가와 실행-실행 컨텍스트가 스택에 생성push =>
외부함수 코드의 평가와 실행-실행 컨텍스트 스택에 생성push =>
내부(하위) 함수 코드의 평가와 실행-실행 컨텍스트 스택에 생성push =>
내부(하위) 함수 종료시 외부 함수로 이동 =>
내부함수 실행 컨텍스트를 스택에서 제거pop =>
외부함수 종료시 전역 코드로 이동 =>
외부함수 실행 컨텍스트 제거 pop =>
전역코드 더 이상 실행할 것 없으므로 전역 실행 컨텐스트 제거pop

- 스코프 체인, 변수 은닉화, 클로저

***자바스크립트 deep dive 393쪽 참고

스코프 체인은 위에서 스코프를 다룰 때 언급했다:
*함수의 중첩: 함수내에 또다른 함수가 들어가는 것으로 nested function이라고 하며 이럴때 스코프는 계층적 구조를 가진다. >>> 스코프 체인 형성

클로저closure
자바스크립트의 고유 개념은 아니지만 함수를 일급 객체로 취급하는 다른 언어에서 가져온 것이다. 외부함수보다 중첩함수보다 더 오래 유지되는 경우가 있는데 이럴 때 이미 생명주기가 종료한 외부함수의 변수를 참고할 수 있다. 이러한 중첩함수를 클로저closure라고 한다.

How is this possible?
생명주기가 종료될 때 외부함수의 실행 컨텍스트는 콜스택에서 제거되지만 외부함수의 렉시컬 환경까지 소멸하는 것은 아니기 때문이다.

클로저가 성립하려면,
중첩함수가 상위 스코프의 식별자를 참조하고 있고 중첩함수가 외부함수보다 더 오래 유지되어야 한다. (중첩함수가 참조하지 않는 외부함수의 식별자는 기억하지 않으므로 메모리에서 제거된다.)

*렉시컬 스코프: 자바스크립트 엔진은 함수를 어디서 호출했는지가 아니라 어디서 정의했는지에 따라 상위 스코프를 결정한다. 이를 렉시컬 스코프라고 함. 이 렉시컬 스코프를 이용한 것이 클로저이다.

변수 은닉화
직접적으로 변경되면 안 되는 변수에 대한 접근을 막는 것을 은닉화라고 하고 이러한 상황에서 클로저가 쓰인다. 변수의 상태statef를 안전하게 은닉information hiding하고 특정 함수에게만 상태 변경을 허용할 수 있다.

예)

const increase = function() {
  //카운트 상태 변수
  let num = 0;
  
  //카운트 상태를 1만큼 증가
  return ++num;
}

console.log(increase()); //1
console.log(increase()); //1
console.log(increase()); //1
//이전 상태를 유지하지 못함

위 함수의 경우, increase함수가 호출될 때마다 지역변수 num은 다시 선언되고 0으로 초기화되기 때문에 출력 결과는 언제나 1이다. (상태가 변경되기 이전의 상태를 유지하지 못함)

클로저를 사용한 함수

const increase = function() {
  //카운트 상태 변수
  let num = 0;
  
  return function(){
    //카운트 상태를 1만큼 증가
    return ++num;
  };
}());

console.log(increase()); //1
console.log(increase()); //2
console.log(increase()); //3
//이전 상태를 유지하지 못함

위 코드가 실행되면 즉시 실행 함수가 호출되고 즉시 실행함수가 반환한 함수가 increase변수에 할당된다. 즉시 실행 함수는 호출된 이후 소멸되지만 즉시 실행 함수가 반환한 클로저는 increase 변수에 할당되어 호출된다. 이때 클로저는 자신이 정해진 위치에 의해 결정된 상위 스코프인 즉시 실행함수의 렉시컬 환경을 기억하고 있다. >>> 자유변수 num을 언제 어디서 호출할지를 참조하고 변경할 수 있는 것.

실습 과제

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

let b = 1;
function hi () {
const a = 1;
let b = 100;
b++;
console.log(a,b);
}

//console.log(a); 
// 주석 처리된 부분이 오류가 나는 이유는 a가 지역 변수이기 때문에 
//함수를 불러올 때만 선언되고 function밖에서 쓸 수 없기 때문이다. 
//이 부분을 수정하려면 const a = 1;을 function밖으로 빼거나 
//console.log(a);를 함수안으로 넣어야 한다.

console.log(b); // 1
hi(); // 1 101
console.log(b); // 1

참고서적: 모던 자바스크립트 Deep Dive (이웅모 지음)

profile
꾸준하게 하는 법을 배우는 중입니다!

0개의 댓글