[javascript] 헷갈리는 기본 개념 비교를 통해 정리하기

gramm·2021년 5월 15일
2

JS

목록 보기
1/2
post-thumbnail
post-custom-banner

JS 입문자가 헷갈리는 개념들을 비교를 통해 정리해보았다.

(입문자의 정리라 잘못된 설명이 있을 수 있습니다. 주저 없이 지적해주세요.)


1. null vs undefined

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 만나 - 폴킴

2. '==' 연산자 vs '===' 연산자

== 연산자 : 유형변환 비교 (유형을 변환하여 값이 같으면 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

3. truthy 값 vs falsy 값

boolean 타입으로의 형변환이 필요한 경우가 있다. 이를테면, if ('velog') 라고 할 때, 'velog'는 문자열이지만, boolean 타입으로 형변환이 이루어져야 한다. 이때 true값으로 형변환되는 값을 truthy하다고 하며, false값으로 형변환되는 값을 falsy하다고 한다.

  • falsy한 값 : 0 / "" / false / null / undefined / NaN

  • truthy한 값 : 그 외 나머지

주의 - 빈 배열([])과 빈 객체({})는 truthy 값이다!


4. AND 연산자 vs OR 연산자

자바스크립트에서 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) 파이썬도 같은 방식으로 동작한다.


5. null 병합 연산자 / 옵셔널 체이닝 연산자

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)

6. var / let / const

두괄식 결론 : var 대신 letconst를 사용하자. 특히 변수를 재사용할 일이 없다면 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'}

7. 함수 선언 vs 함수 표현식


// 함수 선언의 예시
function printRosen() {
  console.log('Rosen');
};

// 함수 표현식의 예시
const printRosen = function() {
  console.log('Rosen');
};

[함수 선언과 var로 선언한 변수의 평행이론]

  1. 호이스팅이 발생한다. 아래 코드처럼 함수 선언 위에서도 함수를 사용할 수 있다.
printRosen();  // 'Rosen'

function printRosen() {
  console.log('Rosen');
};
  1. 함수 스코프를 가진다. B함수가 A함수 내에서 선언되는 경우, B함수는 A함수 내에서만 사용할 수 있다. 반면 그 외의 경우, B함수는 전역 함수가 된다. (함수 표현식은 블록 스코프를 가진다.)

8. 일반 함수 vs 화살표 함수

ES6에서부터 => 을 통해 함수를 간단하게 표현하는 문법이 도입되었다.

  • function 키워드 삭제, ( )와 { } 사이에 => 추가
  • 파라미터가 하나인 경우 ( ) 삭제 가능
  • { } 속 내용이 return문 한 줄인 경우 { } 및 return 삭제 가능

// 일반 함수 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를 불러오는 것이다.


JS의 this 알아보기

화살표 함수 더 알아보기


9. Rest 파라미터 vs Spread 구문

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이다.

10. for ~ in / for ~ of / forEach / map

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]

11. filter vs find vs some vs every

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 메서드는 조건식을 만족하지 않는 값을 하나라도 발견했을 때 탐색을 중단한다. 왜 그런지는 어렵지 않게 알 수 있다.


12. 배열(Array) vs 셋(Set)

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]

App. 헷갈리는 개념을 정리한 좋은 글들

값에 의한 호출 (call by value) vs 참조에 의한 호출 (call by reference)

backtick( ` ) vs single quoto( ' )

profile
I thought I introduced
post-custom-banner

1개의 댓글