var
로 변수를 선언할 경우 스코프 내에 이미 동일한 식별자를 가진 변수가 존재한다면 해당 변수에 값을 재할당 한다.let
이나 const
로 선언한 변수는 재선언을 시도하면 에러가 발생한다.자바스크립트의 값은 원시 타입 ( Primitive Type ) 과 객체로 나뉜다.
원시 타입은 다음과 같이 7가지가 있다.
number
string
boolean
null
undefined
Symbol
BigInt ( ES2020에서 추가 )
원시타입은 하나의 값만 가지며, immutable 데이터이다.
자바스크립트의 숫자 타입은 정수, 실수 구분없이 숫자 타입 한 가지만 존재한다.
숫자 타입은 모든 데이터를 64비트 부동 소수점 형태로 저장하기 때문에 진정한 정수 데이터는 존재하지 않는다.
const num = 8 // 10진수
const binaryNum = 0b1111 // 2진수
const octNum = 0o17 // 8진수
읽기 전용 속성이다.
숫자로 변환할 수 없는 값을 숫자로 변환하려고 하거나 산술 연산의 결과가 숫자가 아니면 NaN값이 반환된다.
NaN은 자기 자신과도 동등하지 않은 결과를 반환하기 때문에 ===
연산을 사용하지 않고 isNaN()
메서드를 사용해야 한다.
NaN === NaN // false
숫자의 최대값은 Number.MAX_VALUE
로 정의한다.
최소값은 Number.MIN_VALUE
로 정의한다.
하지만 안전하게 표시할 수 있는 정수값의 범위는 더 작은 범위이다.
Number.MAX_SAFE_INTEGER
와 Number.MIN_SAFE_INTEGER
사이로 정의하는 것이 좋다.
이 범위를 벗어난 숫자의 연산은 부정확한 결과를 반환할 수 있다.
만약 해야만 한다면 BigInt
타입을 사용하자.
console.log(0.1 + 0.2 === 0.3) // false
부동 소수점 표현때문에 위와 같이 나온다.
이러한 문제는 Number.EPSILON
을 사용해 해결할 수 있다.
let equal = (Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON);
console.log(equal);
홑따옴표, 쌍따옴표, 백틱을 이용해 표현한다.
템플릿 리터럴은 백틱을 사용한다.
템플릿 리터럴 안에서는 달러 기호와 중괄호로 표현식을 감싸 문자열 안에 그 값을 삽입할 수 있다.
의도적으로 값이 없음을 나타내고 싶을 때 null을 사용하고, 값이 할당되지 않음을 나타내고 싶을 때 undefined를 사용한다.
심볼은 다른 원시 타입과는 달리 Symbol()
함수를 호출해서 생성한다.
눈여겨볼 점은 new
키워드를 붙이지 않고 생성한다는 점이다.
심볼은 객체가 아닌 원시 타입이므로 new
키워드를 사용해 생성할 수 없다.
const sym1 = Symbol('key');
const sym2 = Symbol('key');
console.log(sym1 === sym2); // false
전역 심볼을 생성해 매번 새로운 심볼을 생성하지 않고 기존 심볼을 검색해 사용하기도 한다.
Symbol.for()
메서드를 사용하면 전역 심볼을 생성하며, 다른 라이브러리들과 충돌을 피하기 위해 prefix를 사용해 구분하는 것이 좋다.
const sym1 = Symbol.for('myApp.key');
console.log(sym1 === Symbol.for('myApp.key')); // true
심볼은 객체나 클래스에서 유일한 프로퍼티를 만들 때 사용한다.
심볼을 사용해 프로퍼티를 만들면 유일함이 보장되어 프로퍼티 추가 시 충돌이 날 염려가 없다.
또한 외부에서 직접 해당 프로퍼티에 접근할 수 없어 의도치 않은 프로퍼티 변경을 막을 수 있다.
정수뒤에 n
을 붙여 10진수 리터럴로 사용하거나 심볼과 유사하게 BigInt()
함수를 호출해 생성한다.
const safeMaxNum1 = 9007199254740991n;
const safeMaxNum2 = BigInt(9007199254740991);
기존의 숫자 연산은 안전한 숫자 범위를 벗어나 부정확한 결과를 반환하지만, BigInt 타입은 정확한 결과를 반환한다.
숫자 타입이나 Math 객체의 메서드를 함께 사용해 연산할 수 없다.
기존 숫자 타입과 연산하고 싶다면 반드시 명시적으로 타입을 변환한 후 사용해야 한다.
원시 타입이 아닌 모든 값은 객체이다.
객체는 이름(키)와 값 형태로 여러값을 포함하는 컨테이너이다.
이름(키)에 해당하는 프로퍼티 명은 숫자와 문자열, 심볼만 가능하며 값에 해당하는 프로퍼티 값은 어떤 표현식이든 올 수 있다.
객체의 프로퍼티는 점 표기법 또는 대괄호 표기법으로 접근한다.
대부분은 사용하기 간결한 점 표기법을 많이 사용하지만 변수를 사용해 프로퍼티에 접근하거나 접근하려는 프로퍼티 명에 연산자가 포함된 경우에는 반드시 대괄호 표기법을 사용해야 한다.
객체에 존재하지 않는 프로퍼티에 접근할 경우 undefined
값을 반환한다.
점 표기법이나 대괄호 표기법을 사용해 객체의 프로퍼티를 동적으로 생성 및 변경할 수 있다.
만약 어떤 프로퍼티에 접근할 때마다 동적인 계산이 필요하거나 값이 변경될 때마다 별도의 처리코드가 필요하다면 getter와 setter 접근자 프로퍼티를 사용해보자.
일반 프로퍼티처럼 사용가능 하다.
const obj = {
myName : "js",
set name(name){
if(name !== null){
console.log('set');
this.myName = name;
}
},
get name(){
console.log("get");
return this.myName;
}
}
obj.name; // get
obj.name = 1; // set
접근자 프로퍼티는 프로퍼티 값을 갱신할 때 유효성을 검증하거나 조건에 따라 다른 값을 반환하는 작업들을 할 때 많이 사용된다.
위의 코드에서 주의할 점이 있다.
getter와 setter로 obj객체에 name
이라는 가상의 프로퍼티가 생겼다고 볼 수 있다.
그러니 obj객체의 myName 속성 이름과 getter와 setter 프로퍼티의 이름이 같으면 안된다.
접근자 프로퍼티를 생성하는 또 다른 방법은 Object.defineProperty()
를 사용하는 것이다.
const obj2 = { myName: 'react'};
Object.defineProperty(obj2, 'name', {
set(name){
if(name !== null){
console.log('set');
this.myName = name;
}
},
get() {
console.log('get');
return this.myName;
}
})
obj2.name; // get
obj2.name = 2; // set
Object.defineProperty()
메서드는 첫 번째 인자로 대상이 되는 객체, 두 번재 인자로 추가 또는 갱신하려는 프로퍼티 명이나 심볼을 넘긴다.
그리고 마지막 인자로 프로퍼티 서술자 ( descriptor ) 를 정의한 객체를 넘긴다.
프로퍼티 서술자는 데이터 서술자(data descriptors)와 접근자 서술자(accessor descriptors) 두 가지 형식이 있다.
getter와 setter는 접근자 서술자 이며 나머지는 데이터 서술자이다.
프로퍼티 서술자는 다음과 같다.
descriptor | 설명 |
---|---|
configurable | 객체에서 이 속성을 제거할 수 있는지, 그리고 속성의 (value 와 writable 을 제외한) 특성을 바꿀 수 있는지 결정한다. 특성을 변경할 수 있고, 객체에서 삭제할 수도 있으면 true 이다. 기본 값은 false 이다. |
enumerable | Object.assign() 과 ...연산자 가 속성을 볼 수 있는지, 없는지를 결정한다. 이에 더해, Symbol 속성을 제외한 나머지 속성에 대해서는 for...in 반복문과 Object.keys()에서의 노출 여부도 추가로 결정한다. 속성이 객체의 속성 열거 시 노출되면 true 이다. 기본 값은 false 이다. |
value | 속성값이다. 기본값은 undefined 이다. |
writable | 할당연산자 ( = ) 로 속성의 값을 바꿀 수 있으면 true 이다. 기본 값은 false 이다. |
get | getter 접근자 프로퍼티 메서드로 기본 값은 undefined 이다. |
set | setter 접근자 프로퍼티 메서드로 기본 값은 undefined 이다. |
주의할 점은 value
서술자와 get
서술자를 같이 사용하면 에러가 난다.
같은 의미로 writable
서술자와 set
서술자도 같이 사용하면 에러가 난다.
참고 링크 - MDN
배열은 객체이지만 정수 타입인 인덱스를 프로퍼티로 갖는 특별한 객체이다.
Array()
생성자 사용const arr = new Array(1, '2', true);
Array()
생성자 함수는 새로운 배열을 생성하고 인자로 받은 값들을 배열 원소로 채워 넣어 초기화 한다.
만약 생성자 함수의 인자가 1개이고 숫자 값이라면 해당 값을 배열의 length 프로퍼티에 할당해 새로운 배열을 생성한다.
[주의!] 하지만 이 방법으로 배열을 생성하면 length 프로퍼티만 갖고 인덱스 프로퍼티는 없는 배열이 생성되므로 forEach()
나 map()
과 같은 순회 작업시 실질적인 값이 없으므로 버그가 발생할 수 있다.
아래의 예제를 참고하자.
위의 이미지를 보면 arr
의 속성이 length
뿐이다.
인덱스 속성인 0
, 1
, 2
가 없으므로 forEach()
가 수행되지 않는다.
만약 length
속성을 인위적으로 변경해보자.
길이가 5인 배열로 변한다.
하지만 이 경우도 실질적인 값들은 존재하지 않는다.
단순히 length속성만 변경되었을 뿐이다.
이렇게 length와 다르게 실제 값들이 비어있는 배열을 희소배열이라고 부른다.
이를 회피하기 위해 아래와 같이 스프레드 문법을 사용한다.
대괄호를 사용하여 정의한다.
앞서 객체에서 동적으로 프로퍼티를 추가할 수 있다고 했다.
배열도 객체이기 때문에 동적으로 배열 원소를 추가할 수 있다.
특히 자바스크립트의 배열은 순차적으로 값을 넣을 필요 없이 아무 인덱스 위치에나 값을 동적으로 추가할 수 있다.
이때 값을 추가한 인덱스의 위치에 따라 length 프로퍼티도 자동으로 갱신된다.
중간에 값이 비어있는 배열을 뜻한다.
이 비어있는 상태는 명시적으로 배열의 원소에 undefined
를 할당한 것과는 다르다.
희소배열의 빈 원소는 forEach()
, map()
, filter()
와 같은 배열의 내장 메서드에서 무시된다.
반면에 find()
나 findIndex()
메서드는 빈 원소도 무시하지 않고 모두 탐색한다.
이처럼 희소배열은 일관적이지 않은 동작을 수행하며, 코드 가독성이나 데이터 구조 파악에도 좋지 않으니 사용을 지양하도록 한다.
배열에 원소를 추가 / 삭제 할때마다 자동 갱신된다.
직접 조작도 가능하다.
const arr = [1, 2, 3, 4, 5];
arr.length = 10; // 기존보다 더 크게하면 빈 요소로 나머지를 채운다.
console.log(arr); // [1, 2, 3, 4, 5, empty x 5]
arr.length = 3; // 기존보다 더 작게하면 나머지를 자른다.
console.log(arr); // [1, 2, 3]
shift()
unshift()
push()
pop()
splice()
sort()
concat()
slice()
map()
forEach()
filter()
얕은 복사는 대상 객체를 새로 생성하지만 내부에 중첩된 객체는 새로 생성하지 않고 동일한 객체를 참조한다.
반대로 중첩된 객체까지 모두 새로 생성하는 복사를 깊은 복사라 한다.
위의slice()
메소드와spread operator
를 이용한 복사는 얕은 복사이다.
자바스크립트에서는 일반 객체를 배열처럼 사용할 수 있다.
이러한 객체를 유사 배열 객체라고 하며 length 프로퍼티로 양의 정수 값을 가진 객체여야만 한다.
대표적인 유사 배열 객체는 arguments
객체이다.
arguments
객체는 함수에 전달한 인자를 유사 배열 객체로 만든 데이터이다.
function foo(parameter1, parameter2, parameter3){
console.log(arguments[0]);
console.log(arguments[1]);
console.log(arguments[2]);
console.log(arguments.length);
}
foo("가", "나", "다"); // 가 나 다 3
하지만 찐 배열이 아니기 때문에 ( 정확히 말하자면 Array.prototype을 상속받지 않기 때문에 ) 배열의 내장 메서드를 사용할 수 없다.
하지만 유사 배열 객체도 찐 배열의 내장 메서드를 사용할 수 있는 방법이 있다.
배열의 내장 메서드를 call()
또는 apply()
함수와 결합해 사용하면 된다.
function foo(parameter1, parameter2, parameter3){
Array.prototype.forEach.call(arguments, (arg)=> console.log(arg));
}
foo("가", "나", "다"); // 가 나 다
왜 이런지 간략하게 설명한다.
this
는 호출되는 상황에 따라 다르게 매핑이 된다.
그 중에서 this
를 포함하는 함수가 메서드로써 호출되면 this
는 메서드를 포함하고 있는 객체에 매핑이 된다.
즉, obj.f()
를 호출하면 f()
내에 정의 된 this
가 obj
로 매핑이 된다.
하지만 일반 함수인 f()
를 호출하면 this
는 전역 객체인 window
에 매핑이 된다. ( 정확히 말하자면 f()
는 일반 함수처럼 보이지만 window.f()
라는 전역 객체의 메서드라고 볼 수 있다. )
일반 함수인 f()
를 호출하면서 this
는 obj
에 매핑하려면 call()
이나 apply()
를 사용하면 된다.
다시 위의 코드를 보자.
유사 배열 객체는 Array.prototype
을 상속받지 못해서 forEach()
메서드를 사용하지 못한다.
따라서 Array.prototype.forEach()
를 call()
로 호출한다.
Array.prototype.forEach.call(arguments, 생략)
에서 this
는 arguments
와 매핑되고 이 말은 arguments.forEach(생략)
와 같다는 의미이다.
자바스크립트는 문자열의 프로퍼티에 접근할 때 내부적으로 문자열 값을 가지고 임시 객체로 변환한다.
그리고 프로퍼티 접근이 종료되면 생성된 객체는 메모리에서 제거된다.
이러한 과정을 박싱 ( Boxing ) 이라고 부른다.
그리고 박싱 과정에서 생성되는 임시 객체를 랩퍼 객체 라고 부른다.
const str = 'js';
console.log(str.length); // 2
// const str = new String('js');
언박싱은 박싱과는 반대로 랩퍼 객체를 원시 타입으로 변환한다.
언박싱은 명시적으로 valueOf()
메서드를 호출해 수행한다.
자바스크립트 구문의 구성 요소 3가지
표현식
문
연산자
표현식은 값으로 평가되는 구문이다.
값을 반환한다는 표현을 자바스크립트에서는 값을 평가한다고 표현한다.
자바스크립트의 문은 일종의 지시를 내리는 것이다.
표현문
문으로도 사용할 수 있는 표현식을 표현문이라 한다.
부수 효과가 있는 표현식이며 할당 표현식 또는 함수 호출과 같은 표현식이다.
선언문
변수 또는 함수를 정의한다.
조건문
조건부에 해당하는 표현식의 값에 따라 문을 실행하거나 건너뛴다.
반복문
조건 표현식의 평가 결과에 따라 문을 반복 실행한다.
점프문
특정 위치로 건너뛰어 문을 실행한다.
break 문
continue 문
return 문
연산자들을 사용한 대부분의 표현식들은 부수 효과가 없다
하지만 전치, 후치 증감 연산자는 부수 효과를 가진 연산자이다.
+
, -
연산자는 덧셈, 뺄셈 연산자로 사용되기도 하지만, 단항 연산자로서 다른 역할을 하기도 한다.
단항 +
연산자는 주로 숫자 타입으로 타입 변환할 때 사용되며 피연산자가 숫자로 변환할 수 없는 문자열이면 결과값으로 NaN을 반환한다.
falsy 값
false
null
undefined
NaN
"" ( 빈 문자열 )
0
0n
truthy 값
각각 AND, OR, NOT 연산자이다.
item1 && item2 // item1이 거짓이면 false를, item1이 참이면 item2를 반환
item1 || item2 // item1이 참이면 true를, item1이 거짓이면 item2를 반환
item1 ?? item2 // item1이 null 또는 undefined라면 item2를, 그외엔 item1를 반환
비교 연산자 : 숫자나 문자열의 순서만 비교할 수 있다.
동등 연산자 : 일반적인 동등 연산자 ( == ) 와 엄격한 동등 연산자 ( === ) 두 종류가 있다.
in
연산자는 객체에 특정 프로퍼티가 있는지 확인하는 연산자이며, 우측에는 반드시 객체 타입이 와야 한다.
instanceof
연산자는 좌측 피연산자가 우측 피연산자의 인스턴스인지 판단하는 연산자이다.
우측에는 반드시 생성자 함수 또는 클래스가 와야 한다.
내부적으로 프로포타입 체인이라는 메커니즘으로 판단한다.
비트 연산자
조건 연산자 ( 3항 연산자 )
할당 연산자
ES2021에 추가된 문법으로
||=
( 논리 할당 연산자 ) 가 있다.x = x||y
를 축약한 문법니다.
쉼표 연산자
typeof 연산자
원시 타입이 피연산자라면 원시 타입의 이름이 결과 값으로 반환된다.
(예 'string', 'number', 'boolean', 'undefined', 'symbol', 'bigint' )
함수를 제외한 객체가 피연산자일때 'object'로 평가되며 함수는 'function'으로 평가된다.
typeof null의 결과는 'null'이 아닌 'object'이다. 안타깝게도 초창기 오류를 방치한 결과이다. null 타입인지 확인하고 싶으면
x === null
를 이용한다.
[ 참고 ] : 기초부터 완성까지 프론트엔트 ( 비제이퍼블릭 )