동적인 웹사이트를 만들기 위해 scripting 언어를 추가하게 된 것이 시작.
ECMAScript : JS 공식 문서라고 이해하면 편함. 개발자들이 브라우저의 환경에 구애받지 않도록 하기 위해 jQuery가 각광받는 등 Chrome이 등장하면서 여러 브라우저끼리 ECMA6로 다같이 협의를 봤음! 라이브러리의 도움 없이도 모든 웹사이트에서 잘 작동하는 JS를 쓸 수 있게 됨.
SPA : 하나의 페이지 안에서 부분적으로 데이터를 받아서 업데이트할 수 있게 해주는 것이 대세일 뿐만 아니라 nodejs도 등장하고~ 점점 js는 강력해짐
Web api : 브라우저가 이해할 수 있는 함수들. console은 node,js와 브라우저 모두에서 사용 가능한 api
Html에서 js를 어떻게 포함하는 것이 더 효율적인가? : Async vs defer
head 태그 안에 포함하게 되면
Body 태그 끝에 포함하게 되면
Head 안에 async
키워드와 함께 포함
head 안에 defer
키워드와 함께 포함
js 상단에 ‘use strict’;
키워드를 적어두면 조금 더 상식적인 범위 안에서 js를 작성할 수도 있고, js 성능에도 긍정적 영향을 미침!
var vs let & const : 스코프의 차이를 보임.
(var은 문제가 많아 더이상 사용을 권장하지 않음. 호이스팅 문제도 있음)
(호이스팅이란? 선언이 아래에서 위로 끌어올려지는 것)
var name = 'yj';
var name2 = 'yj2'; // 문제 X
let name = 'yeji';
let name = 'yeji2'; // 에러 발생
Primitive type
reference type (object type)
function : 함수
let text = "hello"; // string
text = 1; // number
text = '7' + 5; // string
js는 실행 과정에서 계속하여 동적으로 자료형을 바꾸는 모습을 보임. 따라서 동적으로 자료형을 바꾸지 못하도록 강제해버리는 TS가 등장했음! (물론 js를 먼저 가고 ts로 넘어가는 것이 정석이니까 그렇게 갑시다)
+
연산자 : 문자열이 등장하는 경우 문자열을 이어붙여줌+, -, *, /, %, **
연산자 : 숫자에 대하여 연산 실행. (**의 경우 거듭제곱)++, --
연산자 : 증감 연산자=
연산자 : 대입 >, <
연산자 : 비교&&(and), ||(or), !(not)
true || true || complexFn()
코드와 같이 무거운 연산일수록 뒤에 배치하여 실행할 경우를 최소화하는 것이 좋다)==, ===
: 느슨한 비교, 엄격한 비교 연산자if (조건) { } else if (조건) { } else { }
(조건) ? (조건이 참일때의 값) : (조건이 거짓일때의 값) // 삼항연산자
switch(변수) { case 값: ... break; case 값: ... break; default: ...}
while(조건) { } // 조건이 참인 경우 계속하여 실행
do { } while(조건) // 한번이라도 먼저 실행하고 싶을 때
for(초기식; 조건식; 증감식) { } // 반복횟수를 명시적으로 적어주면서 반복하는 반복문
반복문 안에 반복문을 넣는 중첩 반복문은 시간복잡도에 제곱 영향을 미치므로 지양하자😥
function : sub-program! 여러번 재사용이 가능하다는 장점 😀
(js에서 함수는 object니 주의)
function 이름 (매개변수1, 매개변수2, ...) { ... return 값; }
const 이름 = function() { }
() => { }
const 이름 = () => { }
es6에서 매개변수의 defualt 값을 생성할 수 있는 문법 & 가변적 매개변수(rest parameter) 등장
function 이름 (매개변수1, 매개변수2=기본값) { }
function 이름 (매개변수1, ...옵션매개변수) { }
scope 개념을 한번 더 짚고 넘어가자면, 함수 안과 밖은 완전히 별개의 공간이라고 생각할 것
그렇다면 함수에서 연산한 값을 밖으로 내보내려면? return
을 사용하여 함수의 실행결과로 값을 넘겨주면 된다!
early return
을 잘 사용하여 함수를 먼저 종료해도 되는 경우 조건에 적절한 return
문을 선언할 것
callback function
: 함수의 이름을 전달하여 함수를 실행할 권한을 넘겨주자. 함수 안에서 자기 자신을 호출할 수도 있음(재귀함수)
IIFE
(Immediately Invoked Function Expression) : 함수를 즉시 실행하는 방법 (잘 사용하지는 않으나 알아는 두자)
(function hi() {console.log("hi");})();
class
: template, no data in, declare once (설계도)
object
: instance of a class, created many times, data in (실제 사물)
js는 프로토타입을 기본으로 하고 있으나 es6에서 클래스가 추가되어 보다 다른 객체지향 언어와 비슷한 양상을 띄게 됨.
클래스는 다음의 포맷으로 설계한다.
class Person{
//constructor
constructor(name, age) {
// fields
this.name = name;
this.age = age;
}
// methods
speak() {
console.log(`${this.name} : hello!`);
}
}
해당 클래스로 객체를 만들어보자.
const yj = new Person('yj', 27);
다만 직접적으로 fields에 접근하는 것은 객체지향의 캡슐화 개념에 부합하지 않기 때문에 getter, setter
를 사용하는 것을 권장한다.
get age() {
return this._age;
}
set age() {
this._age = age > 0 ? age : 1;
}
getter, setter
은 내부에서 값을 할당해줄 때 호출되므로 변수명을 동일하게 사용하게 되면 무한정 호출이 일어나 call stack이 꽉 차버리는 상황이 발생할 수 있다. 따라서 변수명을 다르게 설정해줘야 한다. (위의 코드에서는 언더바_를 붙임)최근에 접근제어 지시자 public, private
, 객체를 만들지 않고 클래스만으로 사용 가능하도록 해주는 옵션 static
이 추가되었다.
클래스에서 다른 클래스를 사용할 수 있도록 해주는 extends
공통된 부분을 하나의 클래스에 정의해둔 뒤 상속받는다. 그리고 추가적으로 기능을 추가하고 싶은 경우 동일한 이름의 함수를 새롭게 정의하면 override
를 해주며 다형성을 보장한다.
(동일한 이름의 함수를 재정의하면 부모의 요소를 부르는 것이 아니라 자식의 함수를 호출)
어떤 object가 특정 object에 해당되는지 체크할 수 있는 방법
const obj1 = {};
const obj2 = new Object();
const obj = { key1 : value1 };
obj.key2 = value2; // 동적으로 키 생성
delete obj.key2; // 동적으로 키 삭제
console.log(obj.key1);
console.log(obj['key1']; // computed property. 동적으로 key의 값을 넣어줘야할 때 사용하자
function newPerson(name, age){
return {
name, // name : name 에서 값을 넣어주는 부분을 생략할 수 있다
age
}
}
function Person(name, age){
// this = {}; // 생략. js 엔진이 알아서 처리
this.name = name;
this.age = age;
// return this; // 생략. js 엔진이 알아서 처리
}
const yj = Person("yj", 24); // constructor 처럼 사용 가능!
key가 object 안에 존재하는지 체크
for (key in obj){
console.log(key);
}
// 순차적으로 데이터에 접근할 때 for of 사용
const arr = [1,2,3];
for(value of arr){
console.log(value);
}
=
방식으로 단순히 객체를 할당해주면 동일한 주소지를 갖고 있으므로 객체 값이 복사가 되지 않는다. 따라서 객체를 제대로 복사해주는(deep copy)해주는 방법은 다음과 같다.
// old way
const user = { name: "yj", age: '20' };
const user2 = {};
for(key in user){
user2[key] = user[key];
}
// Object.assign 사용
const user3 = Object.assign({}, user); // 함수의 원형은 MDN 공식문서 참고하기
만약 Object.assign에 여러개의 객체를 전달하게 되면 뒤에 등장한 객체일수록 우선순위가 높다(값이 덮어씌워질 때 뒤의 값으로 덮어씌워짐)
연속된 데이터를 저장할 수 있는 자료구조 array
는 js에서 object 안에 정의되어 있다.
const fruits = ['apple', 'banana'];
console.log(fruits.length);
console.log(fruits.[0]); // 인덱스 값으로 접근
배열 내 요소를 반복문을 통해 접근할 수 있는 다양한 방법들에 대해 알아보자. forEach가 개인적으로 제일 편하다 😄
// for문
for(let i=0; i<fruits.length; i++){
console.log(fruits[i]);
}
// for of 문
for(let item of fruits){
console.log(item);
}
// forEach문. callback Fn을 전달받아 배열 내 각 요소마다 실행
fruits.forEach((item) => console.log(item););
배열에 데이터를 넣고 빼고 조작하는 방법 : push, pop, unshift, shift, splice, concat, indexOf, includes
fruits.push(item) // 맨 뒤에 데이터 추가
fruits.pop() // 맨 뒤의 데이터 뿅
fruits.unshift(item) // 맨 앞에 데이터 추가
fruits.shift() // 맨 앞의 데이터 뿅 -> shift, unshift는 배열의 길이에 속도 영향받음!
fruits.splice(1, 1); // 인덱스를 지정하여 데이터 제거. 시작 인덱스, 끝 인덱스
fruits.splice(1, 1, 'melon'); // 지워진 자리에 새로운 데이터 삽입!
const newFruits = fruits.concat(['watermelon']); // 전달받은 데이터와 합쳐 새로운 데이터를 반환
fruits.indexOf('melon'); // 인덱스 반환
fruits.includes('kiwi'); // 값의 유무 반환
join
: 구분자를 갖고 배열 내의 요소를 하나의 문자열로 반환. 구분자 생략 시 콤마로 자동 삽입fruits.join("");
string.split()
: 문자열 -> 배열로 변환. string 객체 내의 내장 메서드reverse
: 원본 배열 뒤집은 뒤 그 결과 반환 (원본을 건드림!)slice(start, end)
: start부터 end 이전까지의 요소를 갖는 새로운 배열 반환find(()=>{})
: 각각의 요소에 콜백함수를 실행하여 리턴값이 참인 첫번째 요소를 반환filter(()=>{})
: 콜백함수의 리턴값이 참인 요소만 갖는 배열 반환map(()=>{})
: 콜백함수를 호출하며 각각의 요소에 콜백함수를 실행한 결과 기반의 새로운 배열 만들어 반환some(()=>{})
: 콜백함수를 실행하여 참인 요소가 있는지 없는지 반환 (boolean) 배열 안에 조건을 만족하는 값이 있는지 체크할 때 유용!every(()=>{})
: 콜백함수의 실행결과가 모두 참인지 그 여부를 반환 (boolean)reduce((accum, curr)=>{}, init)
: reduce 함수에는 두개의 파라미터를 전달. 첫번째 파라미터는 accumulator 와 current 를 파라미터로 가져와서 결과를 반환하는 콜백함수이며, 두번째 파라미터(init
)는 reduce 함수에서 사용 할 초깃값. 여기서 accumulator 는 누적된 값을 의미.브라우저와 서버가 통신을 하는 과정에서 데이터를 주고받을 때, 그 데이터의 형식이 바로 json
이다. key-value 형태로 전달되며 프로그래밍 언어, 브라우저에 제한을 받지 않는다는 강력한 특징이 있다!
js 내에서 object <-> json 변환을 어떻게 하는지 알아보자.
// object -> json : JSON.stringify()
let json = JSON.stringify(true);
json = JSON.stringify({name: 'yj'});
json = JSON.stringify(['yj', '20']);
오버로딩이 많이 되어 있으니 사용하기 전에 한번 더 stringify 공식문서를 참고하자!
object 전달 시 함수는 변환되지 않는다.
// json -> object : JSON.parse()
const obj = JSON.parse(json);
변환되는 데이터는 모두 문자열이므로 변환 과정에서 자료형을 변환해야 하는 경우 콜백함수를 사용하자,
유용한 사이트들
https://jsondiff.com/
https://jsonbeautifier.org/
https://jsonparser.org/
https://tools.learningcontainer.com/json-validator/
JS는 기본적으로 동기적인 언어다. 즉 정해준 순서에 맞게 코드가 나타나는 순서대로 실행이 된다. 비동기를 이해하려면 setTimeout
예제가 제일 좋다고 생각한다.
setTimeout(()=> console.log("hi"), 1000);
1초 뒤에 hi라는 문자열이 출력된다. 브라우저에게 1초 뒤에 콜백함수를 출력하라고 전달하는 전형적인 비동기 로직이 들어간 코드다. 비동기 처리 과정에서 콜백함수가 중첩되고 호출되는 지옥의 상황이 은근 흔하다.... 물론 이런 지옥의 과정에서 구원해줄 async & await
구원자가 es8에서 등장하긴 했다!
비동기 상황을 값으로 표현한다. (pending / fulfilled / rejected 상황 중 하나)
const promise = new Promise(resolve, reject) => {
// .. 실행할 코드
// 성공한 경우
resolve(value);
// 실패한 경우
reject(new Error("에러 발생!"));
}
Promise의 결과를 가지고 처리하는 방법은 3가지다. then, catch, finally
를 사용할 수 있다.
then : 가장 기본으로 2가지의 함수를 넘길 수 있다. 먼저 넘겨주는 함수는 성공 시 실행하며 두번째 함수는 실패 시 실행한다. 콜백함수 1개만 넘겨주는 경우 성공 시 실행하는 함수만 넘겨받는 것! 또한 then의 반환 결과는 Promise 객체이다. 따라서 chaining
이 가능하다.
promise.then( (resolve)=> {}, (reject)=>{});
catch : 에러가 발생한 경우만 다루고 싶을 때. then(null, callbackFn)
과 동일
promise.catch( (reject)=> {});
finally : 결과가 어떻든 마무리가 필요할 때. 성공 실패 여부에 관심이 없으므로 전달받는 요소가 없다. 자동으로 다음 핸들러에 결과, 에러를 전달.
promise.finally(()=>{}).then(......);
then 함수가 Promise 객체를 반환하기 때문에 계속하여 then 메서드를 호출할 수 있다.
promise
.then((resolve)=> {})
.catch((reject)=> {})
.then((resolve)=> {})
.then((resolve)=> {})
.catch((reject)=> {});
중간에 에러 핸들링이 필요한 경우에는 적절한 위치에 catch 메서드를 넣어주자!
비동기 처리의 꽃✨
async
키워드를 함수 앞에 붙여주면 자동으로 Promise 객체를 반환하도록 설정이 된다. async function fetchUser() {
// ... 뭔가 서버로부터 받아오는 로직 코드 ...
return 'yj';
}
await
키워드는 async
함수 안에서만 사용이 가능하다. 해당 Promise 객체가 종료될 때까지 기다리도록 강제하는 키워드.function delay(ms){
return new Promise(resolve => setTimeout(resolve, ms));
}
async function getApple(){
await delay(2000);
return 'apple';
}
에러 핸들링해줄 때도 try catch
문을 사용하여 해줄 수 있다.
Promise 객체가 실행하는 과정에서 서로 영향을 미치지 않아 병렬로 수행해도 되는 경우에는 Promise.all()
API를 사용하자. all 키워드는 배열로 Promise 객체를 전달받아 한꺼번에 실행할 수 있다.
Promise.all([promise1, promise2]).then([r1, r2]) => {...});
ES6 에서 도입된 spread 와 rest 문법은 외형이 동일하나 동작이 다르다. 이번 기회에 정리해둬야지.
const slime = {
name: '슬라임'
};
const cuteSlime = {
name: '슬라임',
attribute: 'cute'
};
const purpleCuteSlime = {
name: '슬라임',
attribute: 'cute',
color: 'purple'
};
// spread 문법 사용
const slime = {
name: '슬라임'
};
const cuteSlime = {
...slime,
attribute: 'cute'
};
const purpleCuteSlime = {
...cuteSlime,
color: 'purple'
};
// 배열에서의 spread 문법 사용
const animals = ['개', '고양이', '참새'];
const anotherAnimals = [...animals, '비둘기'];
...(spread 문법)
은 기존의 것을 건드리 않고, 새로운 객체 및 배열을 만들 수 있다.
rest는 객체, 배열, 그리고 함수의 파라미터에서 사용이 가능하다.
// 객체에서 rest 사용
const purpleCuteSlime = {
name: '슬라임',
attribute: 'cute',
color: 'purple'
};
const { color, ...rest } = purpleCuteSlime;
// 배열에서 rest 사용
const numbers = [0, 1, 2, 3, 4, 5, 6];
const [one, ...rest] = numbers;
// 함수 파라미터에서 rest 사용
function sum(...rest) {
return rest;
}
함수의 경우 arguments 객체(유사 배열 객체)를 사용했던 것과 비슷하다. 다만 필수적으로 전달해야되는 매개변수가 존재하고 그 외의 요소를 옵션으로 전달받아야 되는 상황이라면 다음과 같이 rest parameter를 쓰는 것이 더 좋겠다.
function sum(operator, ...rest) {
// ...
return result;
}
operator 매개변수는 반드시 전달해줘야 하는 문자열이고 그 외의 숫자나 데이터는 옵션으로 전달받는 상황이다. 위와 같이 코드를 작성해주면 명료하게 함수의 매개변수를 통해 작동 방향을 암시할 수 있다.
매우 유용한 기능이어 추가로 정리해보려고 한다. 논리 연산자를 보다 똑똑하게 사용하는 방법이다. 논리 연산자를 사용 할 때에는 무조건 true 혹은 false 값을 사용해야 되는 것은 아니다. 문자열이나 숫자, 객체를 사용 할 수도 있고, 해당 값이 Truthy 하냐 Falsy 하냐에 따라 결과가 달라지는 것을 이용하여 코드를 짧게 단축시킬 수 있다.
function render() {
return condition1 && condition2 && `<div>Hello</div>`;
}
위의 코드에서 true 자리에 렌더링하는 조건이 부여되고 condition1, condition2
의 결과가 참이라고 가정하자. 그러면 두 조건이 모두 참이기 때문에 가장 마지막에 위치한 문자열이 리턴된다. 위와 같이 논리 연산자를 사용하지 않았다면 swtich
나 if
를 사용하여 코드가 길어졌을 것이다.
&&
나 ||
연산자를 사용한 단축 평가를 통해 코드를 보다 간결하게 작성하자 😎