2022.09.08 개인 공부 - JavaScript
기존에 배운 C 계열 언어 (C, C++, C#) 및 Node.js 수업에서 배운 내용과 다르거나 다뤄지지 않은 부분만, 본인의 공부를 위하여 기재한다.
Node.js나 서버에 활용하리라 생각되는 부분을 우선하여 공부한다.
var는 한번 선언된 변수를 다시 선언할 수 있다.
var는 선언하기 전에 사용할 수 있다 - 호이스팅 (Hoisting)
호이스팅 :
스코프 내부 어디서든 변수 선언이 최상위에 선언된 것처럼 행동
선언은 호이스팅 되지만, 할당은 호이스팅 되지 않는다.
let과 const에서 호이스팅 문제가 발생하지 않는 이유
Temporal Dead Zone (TDZ) :
let과 const 선언의 스코프 내부 상위 영역
TDZ 영역에 있는 변수는 사용할 수 없다
코드를 예측 가능하게 하고 잠재적인 버그를 줄일 수 있다
→ 호이스팅으로 인한 버그를 줄인다
호이스팅은 스코프 단위로 일어난다.
변수의 생성 과정
선언 단계
초기화 단계
할당 단계
var는 선언과 초기화가 동시에 일어난다
→ 초기화에서 undefined를 할당
let은 선언과 초기화가 분리되어 진행된다
const는 선언, 초기화, 할당이 모두 동시에 일어난다
var :
함수 스코프 (function-scoped)
let, const :
블록 스코프 (block-scoped)
변수 비교 표
var | let | const | |
---|---|---|---|
생성 | 선언, 초기화 | 선언 | 선언, 초기화, 할당 |
값 변경 가능 | O | O | X |
스코프 범위 | 함수 스코프 | 블록 스코프 | 블록 스코프 |
TDZ 영향 | O | X | X |
호이스팅 발생 | O | X | X |
객체 리터럴과 같이 객체를 만드는 또다른 방법
생성할 때 함수명 앞에 new를 붙여 사용
첫 글자를 대문자로 하는 것이 관례
매개변수를 받아 객체를 생성
function User(name, age){
this.name = name;
this.age = age;
}
let user = new User("Mike", 33);
function User(name, age){
this.name = name;
this.age = age;
this.sayName = function(){
console.log(this.name);
}
}
let user2 = new User("Han", 40);
user2.sayname(); // Han
아래 예시에서와 같이 프로퍼티 이름 부분에 변수를 대괄호로 묶어 넣어주면 변수에 할당된 값을 이름으로 사용할 수 있다.
예시
// 일반적인 객체의 선언
let a = 'age';
const user = {
name : 'Mike',
age : 30
}
// 계산된 프로퍼티 사용
let a = 'age';
const user = {
name : 'Mike',
[a] : 30
}
const user = {
[1 + 4] : 5,
["안녕" + "하세요"] : "Hello"
}
// 콘솔
user
> {5: 5, 안녕하세요: "Hello"}
객체 복제
클론_객체 = 객체
는 객체의 참조 값(주소 값)만 복제된다.
⇒ 하나의 객체를 두 변수가 접근
동일하게 복제하려면 assign 사용
매개변수로 들어가는 첫 번째 인수의 객체에 두 번째 인수가 병합된다.
const user = {
name : 'Mike',
age : 30
}
const newUser = Object.assign({}, user);
// 왼쪽의 빈 객체에 오른쪽 객체의 내부 요소가 병합되어 복제
// {} + { name : 'Mike', age : 30 }
Object.assign({ gender:'male' }, user);
// gender 프로퍼티에 user 내부 요소가 복제되어 병합(추가)
// { gender : 'male', name : 'Mike', age : 30 }
Object.assign({ name : 'Tom' }, user);
// 같은 프로퍼티에 값이 있으면 덮어쓴다.
// { name : 'Mike', age : 30 }
첫 번째 빈 객체에 두 번째 객체의 내부 요소를 병합한다. (복제한다)
다른 프로퍼티가 있다면 두 번째 객체의 내부 요소를
추가하여 병합한다.
같은 프로퍼티에 다른 값이 있다면 두 번째 객체의 것으로
덮어쓰고 병합한다.
const user = {
name : 'Mike'
}
const info1 = {
age : 30,
}
const info2 = {
gender : 'male',
}
Object.assign(user, info1, info2);
/*
user = {
name : 'Mike',
age : 30,
gender : 'male',
}
*/
const user = {
name : 'Mike',
age : 30,
gender : 'male',
}
Object.keys(user);
// ["name", "age", "gender"]
const user = {
name : 'Mike',
age : 30,
gender : 'male',
}
Object.values(user);
// ["Mike", 30, "male"]
프로퍼티 키와 값을 쌍으로 묶어 배열로 반환
키/값 쌍 배열을 요소로 갖는 배열 반환
const user = {
name : 'Mike',
age : 30,
gender : 'male',
}
Object.entries(user);
/*
[
["name", "Mike"],
["age", 30],
["gender", "male"]
]
*/
키와 값으로 묶인 배열을 요소로 갖는 배열을 객체로 변환
배열 쌍에서 앞을 키로 본다.
const arr =
[
["name", "Mike"],
["age", 30],
["gender", "male"]
];
Object.fromEntries(arr);
/*
{
name : 'Mike',
age : 30,
gender : 'male',
}
*/
일반적인 프로퍼티 키 : 문자형
심볼의 생성
const a = Symbol();
new를 붙이지 않는다.
매개변수로 문자형을 넘겨 설명도 넣을 수 있다.
const id = Symbol('id');
설명은 심볼의 description
프로퍼티로 접근 가능
const id = Symbol('id 입니다.');
id.description; // "id 입니다."
심볼의 특성 - 유일성 보장
유일한 식별자를 만들 때 사용한다.
각 심볼은 전체 프로그램에서 딱 하나
심볼형 변수끼리는 일치 연산자 (===), 동등 연산자 (==)로 비교하면 false가 나온다.
프로퍼티 키 : 심볼형
const id = Symbol('id');
const user = {
name : 'Mike',
age : 30,
[id] : 'myid'
}
// 콘솔
> user
>> {name: "Mike", age: 30, Symbol(id): "myid"}
> user[id]
>> "myid"
이 때, 심볼형 프로퍼티는 숨겨져 있다.
객체 메소드를 사용해도 반환 되지 않는다.
( Object.keys(), values(), entries() 등 )
Object.keys(user); // ["name", "age"]
Object.values(user); // ["Mike", 30]
Object.entries(user); // [Array(2), Array(2)]
for … in문으로 순회해도 나오지 않는다.
for(let a in user){ }
이로써 특정 데이터의 원본은 수정하지 않으면서 프로퍼티(속성)을 추가할 수 있다.
전역 심볼 : Symbol.for()
하나의 심볼만 보장받을 수 있다.
Symbol 함수와 달리 Symbol.for 메소드는 하나를 생성하고
키를 통해 같은 Symbol을 공유한다.
const id1 = Symbol.for('id');
const id2 = Symbol.for('id');
id1 === id2; // true
Symbol.keyFor(id1) // "id"
전역 심볼의 키를 얻는 메소드 → Symbol.keyFor()
숨겨진 Symbol key 보는 법
Object.getOwnPropertySymbols(객체명)
심볼 키 배열 반환
Reflect.ownKeys(객체명)
심볼 키를 포함한 객체의 모든 키 배열 반환
사용 양상 예시 코드
// 다른 개발자가 만들어 놓은 객체
const user = {
name: "Mike",
age: 30,
};
// 내 작업
// user.showName = function(){};
const showName = Symbol("show name");
user[showName] = function(){
console.log(this.name);
};
user[showName]();
// 사용자가 접속하면 보는 메세지
for (let key in user) {
console.log(`His ${key} is ${user[key]}.`);
> Mike
> His name is Mike.
> His age is 30.
> // His showName is function(){}.
toString()
숫자를 문자로 바꾼다.
매개변수로 받은 수의 진법으로 변환한다. 기본값 10진법
let num = 10;
num.toString(); // "10"
num.toString(2); // "1010"
let num2 = 255;
num2.toString(16); // "ff"
toFixed()
소수점 자릿수 표현을 지정
매개변수로 받은 자릿수까지 표현 (그 아래에서 반올림)
문자열로 반환한다.
let userRate = 30.1234;
userRate.toFixed(2); // "30.12"
userRate.toFixed(0); // "30"
userRate.toFixed(6); // "30.123400"
isNaN()
NaN인지 여부를 검사
유일하게 NaN인지 검사할 수 있는 방법이다.
- NaN은 자기 자신과도 똑같지 않다고 판단된다.
let x = Number('x'); // NaN
x == NaN // false
x === NaN // false
NaN == NaN // false
isNaN(x) // true
isNaN(3) // false
parseInt()
문자열을 숫자로 바꾼다
Number()
와 달리 문자가 혼용되어 있어도 동작한다.
let margin = '10px';
parseInt(margin); // 10
Number(margin); // NaN
let redColor = 'f3';
parseInt(redColor); // NaN
두 번째 인수를 받아 진법을 바꿀 수 있다.
let redColor = 'f3';
parseInt(redColor); // NaN
parseInt(redColor, 16); // 243
parseInt('11', 2); // 3
parseFloat()
문자열을 부동소수점 수로 바꾼다.
parseInt()
와 같은 방식으로 동작한다.
let padding = '18.5%';
parseInt(padding); // 18
parseFloat(padding); // 18.5
Math.PI
Math.ceil()
Math.floor()
Math.round()
Math.random()
Math.max()
Math.min()
Math.abs()
Math.pow(n, m)
거듭 제곱
n은 밑, m은 지수
Math.sqrt()
arr.splice
요소를 삭제하고 반환
arr.splice(n, m)
특정 요소 삭제
n : 시작 인덱스
m : 지우는 개수
let arr = [1, 2, 3, 4, 5];
let result = arr.splice(1, 2);
console.log(arr); // [1, 4, 5];
console.log(result); // [2, 3];
arr.splice(n, m, x, ...)
특정 요소 지우고 추가
x 이후는 추가하는 요소
let arr = [1, 2, 3, 4, 5];
arr.splice(1, 3, 100, 200);
console.log(arr); // [1, 100, 200, 5];
console.log(result); // [2, 3, 4];
arr.slice(n, m)
arr.concat(arr2, arr3, …)
arr.forEach(fn)
배열의 각 요소에 대하여 함수 반복
함수를 인수로 받는데, 그 함수의 매개변수는 각 요소, 인덱스, 배열이다.
arr.indexOf(n)
처음부터 검사하여 인수의 요소를 발견하면 인덱스 반환
arr.indexOf(n, m)
arr.lastIndexOf(n)
arr.includes(n)
arr.find(fn)
함수를 인수로 받아, 함수의 조건을 만족하는 요소 포함 여부 확인
첫 번째 요소를 찾으면 true, 못 찾으면 undefined 반환
arr.findIndex(fn)
함수를 인수로 받아, 함수의 조건을 만족하는 첫 번째 요소의 인덱스 반환
첫 번째 요소의 해당 인덱스, 못 찾으면 -1 반환
arr.filter(fn)
arr.reverse()
arr.map(fn)
arr.sort()
배열 재정렬
배열 자체가 변경되므로 주의해야 한다.
함수를 인수로 받아 정렬 로직을 정할 수 있다.
기본값 오름차순
let arr = [27, 8, 5, 13];
arr.sort(); // arr = [5, 8, 13, 27];
arr.sort((a, b) => {
return b - a; // 양수면 a, b를 뒤바꾼다
}); // arr = [27, 13, 8, 5];
Lodash라는 라이브러리는 sort에 유용한 로직을 정리해놓았다.
_.sortBy(arr)
로 사용 가능
https://lodash.com 공식 사이트 참고
(Destructuring Assignment)
(Rest Parameters, Spread Syntax)
…로 사용
함수에 매개변수를 전달하는 방법
arguments
나머지 매개변수
함수로 넘어 온 모든 인수에 접근
함수 내에서 이용 가능한 지역 변수
length / index
Array(배열) 형태의 객체
배열 내장 메소드 없음
… 뒤에 배열 이름
전달 받은 모든 인수를 요소로 배열로 나타남
function add(...numbers) {
let result = 0;
numbers.forEach((num) => (result += num));
console.log(result);
}
add(1, 2, 3); // 6
add(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // 55
function User(name, age, ...skills) {
this.name = name;
this.age = age;
this.skills = skills;
}
const user1 = new User("Mike", 30, "html", "css");
const user2 = new User("Tom", 20, "JS", "React");
const user3 = new User("Jane", 10, "English");
console.log(user1.skills); // ["html", "css"]
console.log(user2.skills); // ["JS", "React"]
console.log(user3.skills); // ["English"]
배열을 요소로 넣어 풀어 쓸 수 있다.
…배열명
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let result = [0, ...arr1, ...arr2, 7, 8, 9];
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
배열의 병합을 간편히 구현
객체도 가능
let user = {name: 'Mike'};
let mike = {...user, age: 30};
console.log(mike); // {name: "Mike", age: 30}
자바스크립트는 어휘적 환경 (Lexical Environment)을 갖는다.
클로저 (Closure)
함수와 어휘적 환경(렉시컬 환경)의 조합
함수가 생성될 당시의 외부 변수를 기억
외부 변수를 참조
생성 이후에도 계속 접근 가능
어휘적 환경 (렉시컬 환경, Lexical Environment) (220913 추가)
앞으로 필요하면 더 공부
함수 호출 방식과 관계없이 this를 지정할 수 있는 함수
call
모든 함수에서 사용 가능
this를 특정 값으로 지정 가능
const mike = {
name: "Mike",
};
const tom = {
name: "Tom",
};
function showThisName() {
console.log(this.name);
}
showThisName(); // 아무것도 안 나온다.
showThisName.call(mike); // Mike
// this 객체를 mike로 넘긴다.
showThisName.call(tom); // Tom
// this 객체를 tom으로 넘긴다.
apply
매개변수를 제외하고 call과 같다.
매개변수를 배열로 받는다.
const mike = {
name: "Mike",
};
function update(birthYear, occupation) {
this.birthYear = birthYear;
this.occupation = occupation;
}
update.call(mike, 1999, "singer");
console.log(mike); // {name: "Mike", birthYear: 1999, occupation: "singer"}
update.apply(mike, [1999, "singer"]);
console.log(mike); // {name: "Mike", birthYear: 1999, occupation: "singer"}
apply는 배열 요소를 함수 매개변수로 사용할 때 유용
bind
const user = {
name: "Mike",
showName: function () {
console.log(`hello, ${this.name}`);
},
};
user.showName(); // hello, Mike
let fn = user.showName;
fn(); // hello, 이후로 아무것도 표시 안 됨
fn.call(user); // hello, Mike
fn.apply(user); // hello, Mike
let boundFn = fn.bind(user);
boundFn(); // hello, Mike
**__proto__**
객체가 어떤 객체를 상속 받고 있는지를 나타내는 프로퍼티
자식_객체.__proto__ = 부모_객체
형식으로 사용한다.
이를 이용한 상속 관계의 연속을
프로토타입 체인(Prototype Chain)이라고 한다.
다만 이는 레거시 기능으로, JavaScript 엔진에서 매우 느린 작업이다. 사용이 추천되진 않는다.
대신, Object.create()
를 사용하여 프로토타입 새 객체를 만들거나 Object.getPrototypeOf()
를 사용하여 프로토타입의 속성값을 받는 것을 추천한다.
참고 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/proto
Promise
객체는 비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과 값을 나타낸다.
프로미스 사용
const pr = new Promise((resolve, reject) ⇒ { /*code*/ });
**resolve**
는 성공했을 때, **reject**
는 실패했을 때 호출되는 콜백 함수
Promise
객체의 동작 방식
new Promise
로 생성된 프로미스 객체는
state
와 result
를 프로퍼티(속성)로 갖는다.
처음에 state
는 작업이 이루어지는 동안
pending
(대기) 값을 가진다.
result
는 undefined
작업이 완료되고,
3-1. 성공하면
**resolve(value)**
가 호출되고,
state
값은 **fulfilled
(이행됨),**
result
값은 value를 할당 받는다.
3-2. 실패하면
**reject(error)**
가 호출되고,
state
값은 **rejected
(거부됨),**
result
값은 error를 할당 받는다.
Promise
객체가 result
를 반환하면 메소드 then
을 사용해 결과에 따른 동작을 정할 수 있다.
4-1. Promise
객체의 catch
메소드는 error가 반환되었을 때만 실행된다.
4-2. Promise
객체의 finally
메소드는 결과에 상관없이 처리가 완료되면 항상 실행된다.
async 키워드를 사용하면 함수가 항상 프로미스를 반환한다,
await 키워드는 async 함수 내부에서만 사용 가능하다.
await 키워드는 프로미스를 수식한다.
함수의 실행을 도중에 멈췄다가 재개할 수 있는 기능
생성
function
옆에 ⇒ `function`
내부에 yield
키워드 사용
예시
function* fn() {
yield 1;
yield 2;
yield 3;
return "finish";
}
const a = fn();
사용
next()
메소드
실행하면 가장 가까운 yield 문을 만날 때까지 실행하고 데이터 객체를 반환한다.
객체는 value
와 done
을 프로퍼티로 갖는다.
value
: yield 수식을 받은 값. default는 undefined.
done
: Generator 함수 코드가 끝났는지 여부
인수를 전달하면 yield
키워드로 할당할 수 있다.
return()
메소드
호출하는 즉시 인수를 value
로 할당하고 **done
속성값을 true로 바꿔 Generator 함수를 종료**한다.
매개변수로 주어진 값을 반환하고 Generator 종료
return
문이 삽입된 것처럼 작동한다.throw()
메소드
호출하는 즉시 catch를 실행하고 **done
속성값을 true로 바꿔 Generator 함수를 종료**한다.
Generator에 오류를 발생시키고 Generator 종료
throw
문이 삽입된 것처럼 작동한다.예시 코드
function* fn() {
try {
console.log(1);
yield 1;
console.log(2);
yield 2;
console.log(3);
yield 3;
console.log(4);
console.log(5);
yield 4;
const num = yield "인수 전달";
console.log(num);
yield 5;
return "finish";
} catch (e) {
console.log(e);
}
}
const a = fn();
a.next(); > 1 // {value: 1, done: false}
a.next(); > 2 // {value: 2, done: false}
a.next(); > 3 // {value: 3, done: false}
a.next(); > 4 > 5 // {value: 4, done: false}
a.next(); > // {value: "인수 전달", done: false}
a.next(5); > 5 // {value: 5, done: false}
a.next(); > // {value: "finish", done: true}
a.next(); > // {value: undefined, done: true}
const b = fn();
b.next(); > 1 // {value: 1, done: false}
b.next(); > 2 // {value: 2, done: false}
b.next(); > 3 // {value: 3, done: false}
b.return('END'); // // {value: "END", done: true}
const c = fn();
c.next(); > 1 // {value: 1, done: false}
c.next(); > 2 // {value: 2, done: false}
c.next(); > 3 // {value: 3, done: false}
c.throw(new Error('err')); // Error: err// {value: undefined, done: true}
Generator는 iterable
반복이 가능
Symbol.iterator
메소드가 구현되어 있어야 한다.
Symbol.iterator
는 iterator
를 반환해야 한다.
iterator
value
와 done
속성을 가진 객체를 반환하는**next
메소드**를 가진다.재밌게도, 배열 Array 또한 iterable하고 iterator이다.
yield*
다른 Generator 함수를 또 호출할 수 있다.
function* gen1() {
yield "W";
yield "o";
yield "r";
yield "l";
yield "d";
}
function* gen2() {
yield "Hello,";
yield* gen1();
yield "!";
}
console.log(...gen2());
> Hello, W o r l d !
반복 가능한 모든 객체를 수식하여 사용할 수 있다.