동적인 웹사이트를 만들기 위해 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를 사용하여 코드가 길어졌을 것이다.
&&나 ||연산자를 사용한 단축 평가를 통해 코드를 보다 간결하게 작성하자 😎