일상/컴퓨터

[JavaScript 프로그래밍] 데이터 타입과 변수

미적미적달팽이 2024. 10. 9. 05:46

https://www.codeit.kr/topics/modern-javascript

 

모던 자바스크립트 - 자바스크립트 강의 | 코드잇

프로그래밍 언어도 기존의 문제점을 보완하거나 유용한 기능을 추가해서 새로운 버전을 만들기도 하는데요. 최근에 가장 많이 쓰이는 형태의 자바스크립트를 '모던 자바스크립트'라고 합니다.

www.codeit.kr

 

모던 자바스크립트 이해하기 - ECMA Script

자바스크립트라는 프로그래밍 언어의 표준이 되는 것으로, 기능이 보완되면서 발전되어 나아가는 자바 스크립트를 ECMA international이라는 국제 표준 기구에서 관리하고 있다. 자바 스크립트를 사용해야할 때 준수해야하는 사항을 ECMA-262라는 문서로 관리하게 되는 데 이를 지칭하는 것이 ECMA Script이다.

https://ecma-international.org/publications-and-standards/standards/ecma-262/

 

ECMA-262 - Ecma International

ECMAScript® 2024 language specification, 15th edition - ECMAScript is a programming language based on several technologies like JavaScript.

ecma-international.org

https://ko.wikipedia.org/wiki/ECMA%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8

 

ECMA스크립트 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. ECMA스크립트(ECMAScript, 또는 ES[1])란, Ecma International이 ECMA-262 기술 규격에 따라 정의하고 있는 표준화된 스크립트 프로그래밍 언어를 말한다. 자바스크립트를 표

ko.wikipedia.org

 

첫번째 버전은 1997년에 등장하였고, 이후 ES6부터 1년마다 새로 나오면서 ES2015, ES2016 이렇게 불리고 있으며, 2015년에 나온 6번째 버전인 ES6가 자바스크립트의 발전에 크게 기여 하였다. 이후 스크립트를 구별하기 위해 ES6+라고 부르기도 하였다. 하지만 웹 브라우저에 적용을 할 때는 ES6의 새로운 문법을 바로 반영을 못하기에 최신 버전의 ECMA script가 나오더라도 바로 적용하지 않고 보편적으로 사용하는 범위내에서 버전을 준수하게 되었다.

문법 별로 브라우저의 지원 여부는 아래에서 확인할 수 있다.

https://compat-table.github.io/compat-table/es6/

 

ECMAScript 6 compatibility table

Sort by Engine types Features Flagged features Show obsolete platforms Show unstable platforms <!-- --> V8 SpiderMonkey JavaScriptCore Chakra Carakan KJS Other ⬤ Minor difference (1 point) ⬤ Small feature (2 points) ⬤ Medium feature (4 points) ⬤ La

compat-table.github.io

https://caniuse.com/

 

Can I use... Support tables for HTML5, CSS3, etc

 

caniuse.com

 

변화하는 시대와 상황에 맞추기 위해 현시점에서 사용하기 적합한 범위 내에서 최신 버전의 표준을 준수하는 자바 스크립트를 모던 자바 스크립트라고 하게 되었다. 따라서 자바 스크립트의 기본 개념을 더 견고히 하고, 그로 인해 새로운 문법 중에서 더욱 유용한 문법을 찾아낼 수 있게 되면서 활용할 수 있게 된다. 

따라서 자바 스크립트와 ECMA Script는 다른 존재이다. 첫 번째 차이점은, JavaScript는 프로그래밍 언어이고, ECMAScript는 프로그래밍 언어의 표준이다. ECMAScript는 JavaScript가 갖추어야 할 내용을 정리해둔 '설명서'이고, JavaScript는 ECMAScript를 준수해서 만들어낸 '결과물' 이다. 참고로 ECMAScript가 JavaScript화 하기 위해 등장하긴 했지만, ECMAScript는 JavaScript 뿐만아니라 모든 스크립트 언어(scripting languages)가 지켜야 하는 표준이다. 만약 자바스크립트와 같은 언어를 직접 만들고자 한다면, 이 ECMAScript를 준수해야 한다는 것이다.

 그리고 두 번째 차이점은 JavaScript는 ECMAScript를 기반으로 하지만 ECMAScript에 정의된 내용뿐만 아니라, 다른 부가적인 기능도 있다. 특히, 자바스크립트로 HTML 코드를 제어하기 위해 사용하는 DOM(Document Object Model)을 다루는 문법들은 ECMAScript에 표준화된 문법이 아니라 WebIDL에 의해 표준화 되어 있다.

자바스크립트의 동작 원리 - 자바 스크립트의 데이터 타입

자바 스크립트는 데이터 타입이 상대적으로 유연하게 변할 수 있다는 특징이 있다. 예를 들어, 파이썬은 다른 데이터 타입끼리 연산이 불가능하며, C나 자바는 변수에도 따로 타입을 지정하고 타입이 지정해진 변수에는 다른 타입을 할당할 수 없다. 하지만, 자바스크립트에서는 숫자와 문자열 간의 연산이 가능하며, 변수 타입을 따로 지정하지 않기에 다른 타입의 값으로 재할당이 가능하다.

이런 특징으로 자바 스크립트에는 데이터 타입이 없다고 생각할 수 있는데, 데이터 타입이 엄연히 존재하기에, 정확하게 이해하는 것이 필요하다. 언제 어떻게 데이터 타입이 변화하는지 특징을 이해하는 것이 중요하다. 곱셈의 경우에는 연산 대상을 모두 숫자 형으로 형 변환을 한 다음 연산을 수행한다.

 

Symbol

심볼은 기본형 데이터 타입 중 하나로 코드 내에서 유일한 값을 가진 변수 이름을 만들 때 사용한다.

const user = Symbol();
const user = Symbol('this is a user');

Symbol이라는 함수를 통해 심볼 값을 만들어 낼 수 있으며 괄호 안에 심볼에 대한 설명을 붙일 수도 있다. 이렇게 Symbol 값을 담게 된 user라는 이름의 변수는 다른 어떤 값과 비교해도 true가 될 수 없는 고유한 변수가 된다.

const user = Symbol('this is a user');

user === 'this is user'; // false
user === 'user'; // false
user === 'Symbol'; // false
user === true; // false
user === false; // false
user === 123; // false
user === 0; // false
user === null; // false
user === undefined; // false
...

똑같은 심볼을 만들어도 False가 반환된다.

const symbolA = Symbol('this is Symbol');
const symbolB = Symbol('this is Symbol');

console.log(symbolA === symbolB); // false

 

BigInt

자바 스크립트에서 아주 큰 정수 Integer를 표현하기 위해 새로 생긴 데이터 타입이다. 자바스크립트에서는 연산에서 오류가 발새하지 않는 안전한 최대 정수는 2**53 - 1, 안전한 최소 정수는 -(2**53 - 1) 이다. 2**53 - 1은 약 9,000조 정도의 숫자이다. 9000조보다 더 큰 정수 표현에는 한계가 존재하기에 이보다 더 큰 정수를 표현하기 위한 데이터 타입이다. BigInt 타입의 값은 일반 정수 마지막에 알파벳 n을 붙이거나 BigInt라는 함수를 사용한다.

console.log(9007199254740993n); // 9007199254740993n
console.log(BigInt('9007199254740993')); // 9007199254740993n

BigInt의 생성자에 문자열로 값을 넘겨 준 이유는 큰 정수를 그대로 사용하면 안전한 최대 정수로 처리하기 때문이다. 다만, BigInt 타입은 말 그대로 큰 정수를 표현하기 위한 데이터 타입이기 때문에 소수 표현에는 사용할 수가 없다.

1.5n; // SyntaxError
10n / 6n; // 1n
5n / 2n; // 2n

그리고 BigInt 타입끼리만 연산할 수 있고, 서로 다른 타입끼리의 연산은 명시적으로 타입 변환을 해야 한다.

3n * 2; // TypeError
3n * 2n; // 6n
Number(3n) * 2; // 6

 

typeof 연산자

사용하는 값이 어떤 데이터 타입을 갖고 있는 지 확인하려면 typeof 연산자를 사용해야 한다. typeof 연산자는 키워드 다음에 공백(띄어쓰기)을 두고 값을 작성해도 되고, 함수를 사용하듯 괄호로 감싸서 사용할 수도 있다.

typeof 'Codeit'; // string
typeof Symbol(); // symbol
typeof {}; // object
typeof []; // object
typeof true; // boolean
typeof(false); // boolean
typeof(123); // number
typeof(NaN); // number
typeof(456n); // bigint
typeof(undefined); // undefined

주의해야 할 점은 typeof 연산자의 결과가 모든 타입과 1:1로 매칭되지 않는다는 점이 있다. 예를 들면, typeof null을 하면 문자열 null이 리턴되는 게 아니라 문자열 object가 리턴된다.

typeof null; // object

이는 자바스크립트 처음 구현 당시에 특별한 문법 체계로 인한 것으로 ECMAscript에서 수정이 제안되었지만 버그 우려로 되지는 않았다. typeof에 대한 문법 체계는 아래 사이트에서 더 자세히 볼 수 있다.
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/typeof#null

 

typeof - JavaScript | MDN

typeof 연산자는 피연산자의 평가 전 자료형을 나타내는 문자열을 반환합니다.

developer.mozilla.org

함수의 데이터 타입은 객체로 취급되기에 object가 리턴될 것이라 생각하지만 function으로 나온다.

function sayHi() {
  console.log('Hi!?');
}

typeof sayHi; // function

 

Boolean

if('codeit'){
	console.log('I love JavaScript! :)');
}else{
	console.log('I hate JavaScript! :(');
]

이 코드의 실행 결과는 I love JavaScript! :)이다. 자바스크립트에서는 if, for, while 문의 조건 부분에서 불린 타입이 필요한 부분에서는 다른 값이 들어가도 불리언 값으로 취급이 된다.

여기서 False로 평가되는 값은 Falsy, True로 평가되는 값은 Truthy라고 불리며, 빈 배열과 빈 객체인 [], {}는 Truthy 취급을 받는다. 만약 falsy와 truthy값을 명확하게 확인하고 싶다면 Boolean 함수를 사용해서 직접 boolean 타입으로 형 변환 해볼 수도 있다.

// falsy
Boolean(false);
Boolean(null);
Boolean(undefined);
Boolean(0);
Boolean(NaN);
Boolean('');

// truthy
Boolean(true);
Boolean('codeit');
Boolean(123);
Boolean(-123);
Boolean({});
Boolean([]);

truthy와 falsy의 개념은 자바스크립트에서 불린 타입으로의 형 변환을 이해할 때 핵심이되는 개념이니 중요하다.

const flowers = ['장미', '수국', '백합', '튤립', '진달래'];

for (let i = 4; i; i = i - 2) {
  console.log(flowers[i]);
}

if (flowers.length) {
  console.log(flowers[3]);
}

if (Number('codeit')) {
  console.log(flowers[1]);
}

이 코드의 실행 결과는

진달래
백합
튤립

이다.

if, for, while 등 불린 타입의 값이 요구되는 부분에서 truthy와 falsy 개념은 중요하다. for 문의 안에서 조건을 평가하는 부분이 반드시 false가 되는 것이 아니라 0이나 ''(빈 문자)가 되는 순간에도 반복은 멈출 수 있다.

주어진 코드의 3번 줄부터 시작하는 for문을 보면, 가장 먼저 변수 i가 초깃값으로 숫자 4를 가지고 선언이 된다. 숫자 4는 truthy 값이니 중괄호 안쪽에 작성한 코드가 작동한다. flowers의 i번 index, 즉 4번 index에 있는 값을 콘솔에 출력하니깐 콘솔에는 가장 먼저 진달래가 출력된다. 자바스크립트의 배열 index는 0부터 시작한다.

그런 다음 중괄호 안의 코드가 모두 실행되고, 소괄호의 마지막 부분이 실행되는데, 코드를 보면 i - 2를 i에 할당하는 규칙을 갖고 있다. i는 숫자 2가 되고, 숫자 2는 truthy 값이 되어서 한 번 더 중괄호 안에 코드가 실행된다. 콘솔에는 flowers의 2번 index의 값인 백합이 진달래 다음으로 출력이 되는 것이다. 이후 또 한 번 i - 2가 i에 할당되는데, 이제 i는 숫자 0을 가지게 된다. 숫자 0은 falsy 값이기 때문에 for 문의 반복은 여기서 종료된다.

그럼 이제 7번 줄에 있는 코드가 실행된다. if문의 조건에는 flowers.length가 작성되어있다. length 프로퍼티는 배열의 길이를 나타내주는 프로퍼티로 flowers 배열에는 총 5개의 요소가 있으니 숫자 5가 true로 평가돼서 flowers의 3번 index인 튤립이 출력된다.

마지막 11번 줄부터 시작하는 if문 하나가 남았다. 조건은 문자열 codeit을 숫자형태로 형 변환한 결과이다. 문자열 codeit은 숫자로 형 변환했을 때 NaN 값이 되고, NaN 값은 falsy 값이기 때문에 중괄호 안에 코드는 실행되지 않는다.

결국, 주어진 코드를 모두 실행하면 콘솔에는 진달래, 백합, 튤립이 출력된다.

 

AND와 OR 연산자

Truthy와 Falsy는 논리 연산자에서 어떻게 작동할까? 

console.log('Codeit' && 'JavaScript');

이런 경우 문자열이기에 True로 취급될 수 있을 것 같지만, 실행 결과는 "JavaScript"가 나오게 된다. 

논리 연산자는 상황에 따라 한쪽을 선택하게 된다. 기본적으로 AND는 둘 다 True인 경우 True를 출력하고, 하나라도 false인 경우 False를 리턴하게 된다.

console.log(true && true);
console.log(true && false);
console.log(false && true);
console.log(false && false);
true
false
false
false

  자바스크립트에서 어느 한쪽을 선택한다는 관점에서 볼 때, AND 연산자는 왼쪽 피연산자의 값이 Truthy하면 오른쪽 값을 반환하고, Falsy하면 왼쪽값을 반환한다.

OR 연산자는 양쪽이 모두 False일 때만 false를 리턴한다. 

console.log(true || true);
console.log(true || false);
console.log(false || true);
console.log(false || false);
true
true
true
false

 

하나를 선택한다는 관점에서 보게 된다면 OR 연산자는 왼쪽이 Truthy하면 왼쪽값을, Falsy하면 오른쪽값을 리턴하게 된다.

console.log(null && undefined);
console.log(0 || true);
console.log('0' && NaN);
console.log({} || 123);
null
true
NaN
{}

첫번째는 falsy이기에 왼쪽값이, 두번째도 Falsy이기에 오른쪽 값이, 세번째는 왼쪽값이 Truthy하기에 오른쪽 값이, 마지막은 Truthy하기에 왼쪽값이 출력되었다. 이러한 특성은 아래와 같이 응용할 수 있다.

function print(value) {
    const message = value || 'Codeit';

    console.log(message);
}

print();
print('JavaScript');
Codeit
JavaScript

두 연산자의 우선 순위는 어떻게 될까?

function checkAnswer(value) {
  if (value < 10 && value > 0 && value !== 3) {
    return '정답입니다!';
  } 

  return '틀렸습니다!';
}

console.log(checkAnswer(4)); // 정답입니다!

여기, 파라미터 value로 전달되는 값이 10보다 작으면서 0보다는 크고, 그러면서도 3은 아닐 때 '정답입니다!' 라는 문자열을 콘솔에 출력하는 함수가 있다. 위에 있는 코드처럼 AND 연산자나 OR 연산자 중 하나만 계속해서 사용할 때는 문제 없지만, 만약 AND 연산자와 OR 연산자를 섞어서 사용할 때는 연산의 우선순위가 존재한다. AND 와 OR 연산자 사이에서는 AND 연산자의 우선순위가 더 높다.

console.log(true || false && false); // true
console.log((true || false) && false); // false

console.log('Codeit' || NaN && false); // Codeit
console.log(('Codeit' || NaN) && false); // false

위 코드처럼 OR 연산자 뒤에 AND 연산자를 사용한다면, 소괄호로 OR 연산을 감쌀 때와 감싸지 않았을 때 서로 다른 결과를 보여주는 것을 알 수 있다. 프로그래밍을 하다 보면 AND와 OR 연산자뿐만 아니라 다양한 연산자들을 복합적으로 사용하게 될 텐데, 연산의 우선순위를 명확하게 하지 않으면 예상치 못한 결과를 얻을 수 있으니 잘 구분해두는 것이 중요하다. 

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Operator_precedence

 

연산자 우선순위 - JavaScript | MDN

연산자 우선순위는 연산자를 실행하는 순서를 결정합니다. 우선순위가 높은 연산자가 먼저 실행됩니다.

developer.mozilla.org

아래는 AND와 OR 연산자의 boolean 판정 예시 코드이다. 어떻게 출력이 될까?

console.log('String' && 123);
console.log({} || []);
console.log(0 && false);
console.log(null || undefined);
console.log(NaN && 'Codeit');
console.log('' || true);
console.log('JavaScript' && null);
console.log(3000 || undefined);

123
{}
0
undefined
NaN
true
null
3000

1. 'String' && 123은 AND 연산자의 왼쪽 피연산자가 truthy 값이기 때문에 오른쪽에 있는 123을 출력한다.
2. {} || []은 OR 연산자의 왼쪽 피연산자가 truthy 값이기 때문에 그대로 {}을 출력한다.
3. 0 && false는 AND 연산자의 왼쪽 피연산자가 falsy 값이기 때문에 그대로 0을 출력한다.
4. null || undefined는 OR 연산자의 왼쪽 피연산자가 falsy 값이기 때문에 오른쪽에 있는 undefined를 출력한다.
5. NaN && 'Codeit'은 AND 연산자의 왼쪽 피연산자가 falsy 값이기 때문에 그대로 NaN을 출력한다.
6. '' || true은 OR 연산자의 왼쪽 피연산자가 falsy 값이기 때문에 오른쪽에 있는 true를 출력한다.
7. 'JavaScript' && null는 AND 연산자의 왼쪽 피연산자가 truthy 값이기 때문에 오른쪽에 있는 null을 출력한다.
8. 3000 || undefined는 OR 연산자의 왼쪽 피연산자가 truthy 값이기 때문에 그대로 3000을 출력한다.

연산자가 3개 있을 때 판정에 대한 예시 코드이다. 

console.log('codeit' && undefined && null);
console.log('codeit' || false || null);
console.log('codeit' && 123 || null);
console.log('codeit' || 123 && true);

결과는

undefined
codeit
123
codeit

이렇게 나오게 된다. 이 코드에 소괄호를 적용하면 이렇게 된다.

console.log(('codeit' && undefined) && null);
console.log(('codeit' || false) || null);
console.log(('codeit' && 123) || null);
console.log('codeit' || (123 && true));

 

null 병합 연산자

ES2020에서 추가가 되었으며  영어로는 'Nullish coalescing operator' 라고 한다. 물음표 두 개(??)를 사용해서 null 혹은 undefined 값을 가려내는 연산자이다. 

const example1 = null ?? 'I'; // I
const example2 = undefined ?? 'love'; // love
const example3 = 'Codeit' ?? 'JavaScript'; // Codeit

console.log(example1, example2, example3); // I love Codeit

연산자 왼편의 값이 null 이나 undefined라면 연산자 오른편의 값이 리턴되고, example3처럼 연산자 왼편의 값이 null 이나 undefined가 아니라면 연산자 왼편의 값이 리턴되는 원리로 작동한다. 결과적으로 마지막 줄에서 콘솔에 출력되는 값은 I love Codeit이 되는 것이다.

OR 연산자와는 어떤 접이 다른 것일까? 

const title1 = null || 'codeit';
const title2 = null ?? 'codeit';

console.log(title1); // codeit
console.log(title2); // codeit

const title1 = false || 'codeit';
const title2 = false ?? 'codeit';

console.log(title1); // codeit
console.log(title2); // false

const width1 = 0 || 150;
const width2 = 0 ?? 150;

console.log(width1); // 150
console.log(width2); // 0

null 병합 연산자(??)는 왼편의 값이 null이나 undefined인지 확인하고 OR 연산자(||)는 왼편의 값이 falsy인지를 확인하기 때문에 코드와 같이 null이나 undefined가 아닌 falsy 값을 활용할 때 결과가 서로 다르게 나온다.

 

변수와 스코프

변수는 이름을 통해서 어떤 값에 특별한 의미를 부여할 수 있는 상자로 볼 수 있다. 프로그래밍에서 추상화의 가장 기본적인 수단으로 볼 수 있으며, 자바스크립트에서는 ES2015부터 var라는 키워드로 변수를 선언하게 되었다. 이후 var의 문제를 해결하기 위해 값의 재할당이 필요하면 let 키워드를, 아니라면 const를 통해 변수를 선언하게 된다.

그렇다면 var에는 어떤 문제가 있었을까? 

console.log(title);
var title = 'codeit';
console.log(title);

undefined
codeit

이 코드 실행 결과처럼 변수 선언 이전에 console를 실행했을 때 error가 아닌 undefined가 출력된다. 이렇게 변수 선언이 위로 올라간 것처럼 실행 결과가 나오는 호이스팅(Hoisting)이 발생한다. 할당된 값은 할당 이후에 접근이 가능하다.

console.log(title);
let title;

var 대신 let으로 선언을 하면 ReferenceError 에러가 발생한다.

두번째는 var는 중복선언이 가능하다는 것이다. 

var title = 'Codeit';
console.log(title); // Codeit
var title = 'JavaScript';
console.log(title); // JavaScript
let title = 'Codeit';
console.log(title);

let title = 'JavaScript';
console.log(title);

var는 이전에 선언된 변수를 사라지게 하지만, let이나 const는 중복 선언이 불가능해서 SyntaxError 에러가 발생하게 된다. 

마지막으로 변수의 유효 범위인 스코프에서 차이가 있다. 스코프가 적용되는 개념은 전역 변수와 지역 변수의 문제가 있다. 하지만 var는 함수 단위로 평가가 되기에 조건문이나 반복문에서 var로 변수를 선언해도 전역 변수로 취급이 된다. 그래서 let과 const는 코드블록으로 취급되는 중괄호 {}를 기준으로 변수의 유효범위를 구별하게 된다.

var x = 3;  // Global Variable

function myFunc() {
    var y = 4;  // Local Variable
    console.log(`x in myFunc: ${x}`);
    console.log(`y in myFunc: ${y}`);
}

myFunc();
console.log(x);
console.log(y);

여기서는 x가 전역 변수이고 y가 지역 변수이기에 console.log(y);에서 오류가 발생한다. 

var x = 3;

if (x < 4) {
    var y = 3;
}

for (var i = 0; i < 5; i++) {
    console.log(i);
}

console.log('x:', x);
console.log('y:', y);
console.log('i:', i);

하지만 여기에서는 반복문과 조건문 안에서 선언된 y와 i가 전역 변수로 취급이 되기에 실행이 잘 나오게 된다.

0
1
2
3
4
x: 3
y: 3
i: 5

let x = 3;

if (x < 4) {
    let y = 3;
}

for (let i = 0; i < 5; i++) {
  console.log(i);
}

console.log('x:', x);
console.log('y:', y);
console.log('i:', i);

let으로 선언하게 되면 y와 i 부분에서 오류가 발생하게 된다.

{
  let title = 'Codeit';
  console.log(title);
}

console.log(title);

여기에서도 코드 블록 밖의 console.log(title);에서 오류가 똑같이 발생하게 된다.

const sum = 0;

function getSum(x, y) {
  const sum = x + y;

  return sum;
}

if (getSum(1, 5) > 3) {
  const sum = 4 + 6; 
}
 
console.log(sum);

이 코드에서는 function과 if문 안에서 선언된 sum에 대해서는 지역 변수이게 마지막 console.log에서는 전역 변수로 선언된 0이 출력된다. 

function sayHi() {
  const userName = 'codeit';
  console.log(`Hi ${userName}!`);
}

for (let i = 0; i < 5; i++) {
  console.log(i);
}

{
  let language = 'JavaScript';
}

console.log(userName); // ReferenceError
console.log(i); // ReferenceError
console.log(language); // ReferenceError

 

반응형