JS 입문자가 헷갈리는 개념들을 비교를 통해 정리해보았다.
(입문자의 정리라 잘못된 설명이 있을 수 있습니다. 주저 없이 지적해주세요.)
null
: 의도적으로 값이 없다는 것을 표현하는 자료형
undefined
: 선언만 하고 값 자체를 지정하지 않은 자료형
null
은 메모리에 null값이 저장되어 있지만, undefined
는 아무런 값도 저장되어 있지 않다.
let a;
console.log(a); // undefined
a = null;
console.log(a); // null
참고) 함수 매개변수의 기본값 사용하기
함수 인자로 undefined
를 주면, 매개변수(parameter)의 기본값을 사용한다.
함수 인자로 null
을 주면, 기본값이 아니라 null값을 그대로 사용한다.
function meet(param = '우리 지금', artist) {
console.log(`{param} 만나 - {artist}`);
}
meet('undefined', '장기하와 얼굴들') // 우리 지금 만나 - 장기하와 얼굴들
meet('null', '폴킴') // null 만나 - 폴킴
== 연산자
: 유형변환 비교 (유형을 변환하여 값이 같으면 true 반환)
=== 연산자
: 엄격한 비교 (값과 자료형이 모두 같아야 true 반환)
두 값을 비교할 때는 === 연산자를 사용하는 것이 좋다.
3 == 3 // true
3 === 3 // true
3 == '3' // true
3 === '3' // false
null == undefined // true
null === undefined // false
true == 1; // true
true === 1; // false
true == '1'; // true
true === '1'; // false
NaN == NaN // false
// 부동소수점에 대한 표준인 The IEEE 754에 따르면,
// NaN은 NaN을 포함한 어떤 수와도 같지 않다.
// 참고 출처 : https://en.wikipedia.org/wiki/IEEE_754-1985
boolean 타입으로의 형변환이 필요한 경우가 있다. 이를테면, if ('velog') 라고 할 때, 'velog'는 문자열이지만, boolean 타입으로 형변환이 이루어져야 한다. 이때 true값으로 형변환되는 값을 truthy
하다고 하며, false값으로 형변환되는 값을 falsy
하다고 한다.
falsy한 값 : 0
/ ""
/ false
/ null
/ undefined
/ NaN
truthy한 값 : 그 외 나머지
주의 - 빈 배열([]
)과 빈 객체({}
)는 truthy 값이다!
자바스크립트에서 AND 연산자와 OR 연산자는 아래와 같이 동작한다.
A && B
: A가 truthy 값이면 B를 반환하고, A가 falsy 값이면 A를 반환한다.
A || B
: A가 truthy 값이면 A를 반환하고, A가 falsy 값이면 B를 반환한다.
'python' && 'javascript' // 'javascript'
null && 'javascript' // null
'python' || 'javascript' // 'python'
0 || 'javascript' // 'javascript'
cf) 파이썬도 같은 방식으로 동작한다.
ES2020에는 null 병합 연산자 '??'가 추가되었다.
A ?? B
: A가 null이나 undefined면 B를 반환하고, 그 외의 경우에는 A를 반환한다.
OR 연산자와의 비교
OR 연산자
는 A가 falsy 값인지에 따라 true/false를 판단하고,
null 병합 연산자
는 A가 null
혹은 undefined
인지에 따라 true/false를 판단한다.
null || 'javascript' // 'javascript'
0 || 'javascript' // 'javascript'
null ?? 'javascript' // 'javascript'
0 ?? 'javascript' // 0
옵셔널 체이닝 연산자 ?.
: 연산자 왼쪽의 속성 값이 null
또는 undefined
라면 undefined
를 반환하고, 그 외의 경우에는 그 다음 속성 값을 반환한다.
let user = {}
console.log(user.country.city) // TypeError
위 코드의 경우, user 객체가 country 속성을 가지고 있지 않다. 따라서 user.country.city에 접근하면 TypeError가 발생하게 된다.
이 문제를 해결하기 위해서는 user.country.city에 접근하기 전에, user.country가 null이나 undefined가 아님을 확인해야 한다.
let user = {}
console.log(user.country && user.country.city)
옵셔널 체이닝 연산자 ?.
를 이용하면 같은 코드를 더 간단하게 나타낼 수 있다.
let user = {}
console.log(user.country?.city)
두괄식 결론 : var
대신 let
과 const
를 사용하자. 특히 변수를 재사용할 일이 없다면 const
를 사용하자.
[var 키워드의 문제]
문제 1 : 호이스팅(Hoisting)
var 변수의 선언이 해당 함수 유효 범위의 최상단에서 처리될 수 있다. 아래 코드에서와 같이, var 키워드로 변수를 선언하는 경우, 변수 선언이 최상단에서 처리되어, 변수 선언 위에서도 변수가 참조될 수 있다.
// 변수를 선언하고 값을 할당했는데, 변수 선언은 최상단에서 처리된다.
console.log(nct); // 127
var nct = 127
문제 2 : 유효 범위의 문제
var
: 함수 스코프
(함수 기준으로 스코프가 구분된다.)
let
, const
: 블록 스코프
(블록 { } 기준으로 스코프가 구분된다.)
함수 스코프의 var
는 유효 범위를 함수 단위로만 구분할 수 있다. 따라서 조건문이나 반복문 내에서만 사용할 수 있는 지역 변수를 만들 수 없다. 블록 스코프의 let
이나 const
를 사용하면 이 문제를 해결할 수 있다.
for (var i = 0, i < 10, i++) {
sum = sum + i
}
console.log(i); // 10 (i를 반복문 내부의 지역 변수로 만들 수 없음.)
for (let j = 0, j < 10, j++) {
sum = sum + j
}
console.log(j); // j는 반복문 내부의 지역 변수이므로, Error 발생
< let vs const >
let
: 재할당 가능
const
: 재할당 불가
변수의 재할당이 필요한 경우에만 let
을 쓰고, 그 외에는 const
를 쓰는 것이 좋다.
주의) const
의 경우에도 내부 속성값은 수정할 수 있다.
const seulgi = {age: 27, hobby: 'javascript'}
seulgi.age = 28; // 속성값 수정
seulgi.position = 'main_dancer'; // 속성값 추가
delete seulgi.hobby; // 속성값 삭제
console.log(seulgi); // {age: 28, position: 'main_dancer'}
// 함수 선언의 예시
function printRosen() {
console.log('Rosen');
};
// 함수 표현식의 예시
const printRosen = function() {
console.log('Rosen');
};
[함수 선언과 var로 선언한 변수의 평행이론]
호이스팅
이 발생한다. 아래 코드처럼 함수 선언 위에서도 함수를 사용할 수 있다.printRosen(); // 'Rosen'
function printRosen() {
console.log('Rosen');
};
함수 스코프
를 가진다. B함수가 A함수 내에서 선언되는 경우, B함수는 A함수 내에서만 사용할 수 있다. 반면 그 외의 경우, B함수는 전역 함수가 된다. (함수 표현식은 블록 스코프
를 가진다.)ES6에서부터 =>
을 통해 함수를 간단하게 표현하는 문법이 도입되었다.
=>
추가
// 일반 함수 1
let square = function(x) {
return x * x;
}
// 화살표 함수 1
let square = x => x * x;
// 일반 함수 2 (...은 spread 구문으로, 바로 아래에 나온다.)
const printWords = function(...words) {
for (let word of words) {
console.log(word);
}
}
// 화살표 함수 2
const printWords = (...words) => {
for (let word of words) {
console.log(word);
}
}
일반 함수와 화살표 함수의 차이
1) 화살표 함수에는 일반 함수와 달리 arguments
객체가 없다.
2) 일반 함수의 this
는 해당 함수를 호출한 객체에 따라 동적으로 변한다. 반면 화살표 함수의 this
는 해당 함수가 선언되기 직전의 this
값과 같다. 함수 내부에서 this
를 생성하지 않아서, 직전에 사용된 this
를 불러오는 것이다.
Rest 파라미터
: 여러 인자(argument)를 하나의 매개변수(parameter)로 묶는
방식
function no_medal (a, b, c, ...others) {
return others
}
no_medal('1위', '2위', '3위', '4위', '5위') // ['4위', '5위']
Spread 구문
: 배열처럼 하나로 묶여 있는 값을 개별 값들로 펼치는
방식
function printCoins (c1, c2, c3) {
console.log(`내가 산 코인은 ${ c1 }, ${ c2 }, ${ c3 }이다.`);
}
const coins = ['bitcoin', 'dogecoin', 'ethereum']
printName(coins);
// 내가 산 코인은 bitcoin,dogecoin,ethereum, undefined, undefined이다.
printName(...coins);
// 내가 산 코인은 bitcoin, dogecoin, ethereum이다.
javascript에는 기본 for문 외에도 다양한 형태의 반복문 형태가 존재한다.
for ~ in 문
: 객체(object)의 키값을 하나씩 순회한다.
for ~ of 문
: 반복 가능한 객체(iterable)의 요소를 하나씩 순회한다.
참고 : iterable 객체 더 알아보기
const mad_monster = {
'tan': 'main_vocal',
'jho': 'visual',
}
for (let member in mad_monster) {
console.log(`매드 몬스터의 멤버, ${member}`);
}
// 출력 결과
// 매드 몬스터의 멤버, tan
// 매드 몬스터의 멤버, jho
const bye = ['b', 'y', 'e']
for (let x of bye) {
console.log(x);
}
// 출력 결과
// b
// y
// e
arr.
forEach
( callback ( currentvalue [ , index [ , array ] ] ) [ , thisArg ] )arr.
map
( callback ( currentValue [ , index [ , array ] ] ) [ , thisArg ] )
map
메서드와 forEach
메서드도 for ~ of문
처럼 배열의 요소를 하나씩 순회하며 콜백 함수를 호출한다.
단, forEach
메서드는 리턴값이 없지만, map
메서드는 함수를 호출한 결과를 모아 새로운 배열을 리턴한다.
const odd_numbers = [1, 3, 5, 7];
odd_numbers.forEach((number, i) => {
console.log(`${i} X 2 + 1 = ${number}`);
})
// 출력 결과
// 0 X 2 + 1 = 1
// 1 X 2 + 1 = 3
// 2 X 2 + 1 = 5
// 3 X 2 + 1 = 7
// forEach 메서드는 리턴값이 없으므로, 이와 같이 다른 변수에 담을 수 없다.
const even_numbers = odd_numbers.map(number => number * 2);
console.log(even_numbers); // 출력 결과 : [2, 4, 6, 8]
filter
메서드 : 주어진 조건식을 만족하는 값들을 배열
에 담아 반환한다.
(만족하는 값이 없으면 빈 배열을 반환한다.)
find
메서드 : 주어진 조건식을 만족하는 첫번째 값
을 반환한다.
(만족하는 값이 없으면 undefined를 반환한다.)
const cities = [
{ name: 'Seoul', country: 'Korea' },
{ name: 'Paris', country: 'France' },
{ name: 'Busan', country: 'Korea' },
{ name: 'Tokyo', country: 'Japan' },
{ name: 'Toronto', country: 'Canada' },
{ name: 'Sao Paulo', country: 'Brazil' },
{ name: 'Osaka', country: 'Japan' },
];
let cities_in_korea = cities.filter(city => city.country === 'Korea');
console.log(cities_in_korea);
// 출력 결과
// [{name: 'Seoul', country: 'Korea'}, {name: 'Busan', country: 'Korea'}]
const cities_in_japan = cities.find(city => city.country === 'Japan');
console.log(cities_in_japan);
// 출력 결과
// {name: 'Tokyo', country: 'Japan'}
const cities_in_sweden = cities.find(city => city.country === 'Sweden');
console.log(cities_in_sweden); // 출력 결과 : undefined
참고) reject
메서드 : filter
메서드와 정반대다. 주어진 조건식을 만족하지 않는 값들을 배열
에 담아 반환한다.
some
메서드 : 배열의 값 중 하나라도 조건식을 만족하면 True를 반환한다.
(빈 배열의 경우, 무조건 False를 반환한다.)
every
메서드 : 배열의 모든 값이 주어진 조건식을 만족할 때만 True를 반환한다.
(빈 배열의 경우, 무조건 True를 반환한다.)
const numbers = [1, 3, 5, 7];
console.log(numbers.some(num => num > 6)); // True
console.log(numbers.every(num => num > 6)); // False
some
메서드와 every
메서드는 find
메서드와 마찬가지로 배열을 탐색하는 도중 탐색을 중단할 수 있다. some
메서드는 조건식을 만족하는 값을 하나라도 발견했을 때, every
메서드는 조건식을 만족하지 않는 값을 하나라도 발견했을 때 탐색을 중단한다. 왜 그런지는 어렵지 않게 알 수 있다.
Set
은 ES6에서 새로 등장한 자료형이다.
(파이썬을 먼저 배운 사람들에게는 익숙할 자료형이다.)
[배열과 Set의 차이]
1) Set
은 배열
과 달리 중복을 허용하지 않는다!
2) 배열
은 인덱스를 통해 개별 요소에 접근할 수 있다. 반면, Set
은 개별 요소에 접근할 수 없다.
// 배열을 Set으로 변환하는 방법 : Set 생성자 활용
const newArr = ['가위', '바위', '가위', '보', '바위'];
const newSet = new Set(newArr); // {'가위', '바위', '보'}
// Set을 배열로 변환하는 방법 1 : Spread 연산자 활용
const numberSet = new Set([1, 2, 3]);
const numberArr = [...numberSet]; // [1, 2, 3]
// Set을 배열로 변환하는 방법 2 : Array.from() 메서드 활용
const numberSet2 = new Set([4, 5, 6]);
const numberArr2 = Array.from(numberSet2); // [4, 5, 6]