자바스크립트 어려워
근데 너무 멋지다
플젝 끝나고 더 공부해봐야지
파이썬도 더 해야되는데..
내용
1. 클로저
- 어휘적환경 Lexical Environment
- 클로저 Closure 와 은닉화
2. 스케줄링 함수
- setTimeout() / clearTimeout()
- setInterval() / clearInterval()
- delay = 0
3. this
- call / apply / bind
4. 상속
- hasOwnProperty() / prototype
- __proto__ 를 이용한 객체 상속
- prototype chain
- 프로퍼티 반환 범위 for .. in / Object.keys / Object.values
- 생성자 함수 new 와 함께쓰는 방법
- instanceof / constructor
- 은닉화
5. 클래스
- Class 사용법, 편리한점
- extends 를 이용한 Class 상속
- method overriding
6. 프로미스
- Promise / .then() / .catch() / .finally()
- Promise chaning
- Pomise.all([]) / Promise.race([])
7. async await
- async / await
1.
클로저(Closure)
1-1.
어휘적 환경(Lexical Environment)
자바스크립트는 어휘적 환경을 갖는다.
코드가 실행되면, 스크립트 내에서 선언한 변수들은 Lexical 환경에 올라간다.
- one: let도 호이스팅이 일어나기 때문에 Lexical 환경에는 올라가지만 아직 초기화가 안되어있어 사용 불가
- addOne: 함수선언문은 Lexical 환경에 올라가면서 초기화까지 되기 때문에 바로 사용 가능
- one: 초기값 undefined를 가짐. 이제 사용해도 에러는 발생하지 않는다
- one: 숫자 1이 할당됨
함수가 실행되는 순간 새로운 Lexical 환경이 만들어진다.
함수의 내부 Lexical 환경에는 함수가 넘겨받은 매개변수와 지역변수들이 저장된다.
내부 Lexical 환경은 전역 Lexical 환경에 대한 참조를 갖는다.
코드에서 변수를 찾을 때, 내부 → 전역 으로 범위를 넓혀가면서 찾음.
- 변수 one : 내부에 없으니 전역에서 발견
- 변수 num : 내부에서 발견
1-2.
클로저(Closure)
코드 초기 실행 시
- makeAdder 함수와 add3 변수가 전역 Lexical 환경에 들어감
- add3 변수는 아직 초기화가 안되어있는 상태이므로 사용할 수 없음
makeAdder가 실행되면 함수의 내부 Lexical 환경이 만들어지고,
여기에는 전달받은 x의 값이 들어간다. (내부 Lexical 환경에는 함수가 넘겨받은 매개변수와 지역변수들이 저장된다.)
그리고 함수가 실행됐으니깐 add3 변수에는 return하는 function이 할당된다.
add3을 실행하면, add3에 할당되어 있던 funcion이 실행되는데,
이때 또 Lexical 환경이 만들어진다. 여기에는 전달받은 y의 값이 들어간다.
이제 x + y 를 하기위해 변수를 찾는다
1) 먼저 자신의 내부 Lexical 환경에서 y를 찾았고 x는 못찾았음
2) 한단계 범위를 넓혀서 상위함수인 makeAdder Lexical 환경에서 x 발견
add3 함수가 생성된 이후에도
상위함수인 makeAdder의 변수 x 에 접근이 가능하다.
즉, 외부함수의 실행이 끝나서 외부함수가 소멸된 이후에도
내부함수가 외부함수의 변수에 접근할 수 있다.
이걸 Closure 라고 한다!
Closure
- 함수와 그 함수의 렉시컬 환경의 조합
- 함수가 생성될 당시의 외부 변수를 기억
- 생성된 이후에도 그 변수에 계속 접근 가능
예시
function makeCounter() {
let num = 0;
return function() {
return num++; // 외부함수의 변수 num에 접근
};
}
let counter = makeCounter();
console.log(counter()); // 0
console.log(counter()); // 1
console.log(counter()); // 2
생성한 후에 계속 num 변수를 기억하면서 증가시킨다.
이 숫자들은 절대 임의로 변경시킬 수 없고
오직 counter 를 통해서만 가능하다
이게 은닉화 개념인데 아래 4-7 에서 한번 더 공부
2.
스케줄링 함수
2-1.
setTimeout
일정 시간이 지난 후 함수를 실행한다.
setTimeout(fn, 3000); // 3000 == 3초
setTimeout은 기본적으로 두 개의 매개변수를 받는다
첫 번째: 일정시간이 지난 후 실행할 함수
두 번째: 시간
function showName(name){
console.log(name);
}
setTimeout(showName, 3000, "Spino"); // Spino가 인수로 전달됨
만약 실행할 함수에 인수가 필요하다면
세 번째 매개변수로 넣어준다.
clearTimeout
예정된 타임아웃 작업을 없앤다.
function showName(name){
console.log(name);
}
const tId = setTimeout(showName, 3000, "Spino");
clearTimeout(tId); // 취소
clearTimeout을 이용해서 스케줄링을 취소할 수 있다. 3초안에 실행되니까 아무일도 안일어난다
2-2.
setInterval
일정 시간 간격으로 함수를 반복한다.
사용법은 setTimeout 이랑 똑같다.
function showName(name){
console.log(name);
}
setInterval(showName, 3000, "Spino"); // "Spino" 가 3초마다 출력됨
차이점은 한번 실행하고 끝나는 게 아니라 계속 반복실행된다.
clearInterval
예정된 인터벌 작업을 없앤다.
function showName(name){
console.log(name);
}
const tId = setInterval(showName, 3000, "Spino");
clearInterval(tId); // 취소
중단하려면 clearInterval을 해주면 된다.
2-3.
delay = 0
시간을 0으로 설정해도 코드가 바로 실행되지는 않는다.
현재 실행중인 스크립트가 종료된 이후 스케줄링함수를 실행하기 때문이다.
그리고 기본적으로 브라우저는 4ms 정도의 대기시간이 있어서 적어도 그 이후에 실행됨
3.
call, apply, bind
: 함수 호출 방식과 관계없이 this를 지정할 수 있음
3-1. call
- 모든 함수에서 사용할 수 있으며, this를 특정값으로 지정할 수 있음
3-2. apply
- 함수 매개변수를 처리하는 방법을 제외하면 call과 완전히 같음
- 차이점: call은 일반적인 함수와 마찬가지로 매개변수를 직접 받지만, apply는 매개변수를 배열로 받음
3-3. bind
- 함수의 this값을 영구히 바꿀 수 있음
이거 세개는 아직 제대로 이해를 잘 못해서 this 부터 다시 공부한 다음에 정리해야 될 것 같다ㅠㅠ
4. 상속, prototype
4-1. prototype
hasOwnPoperty
객체에는 자기가 프로퍼티를 갖고있는지 확인하는 메서드가 있음
const user = {name : "Spino"}
> user.name // 프로퍼티 접근
< "Spino"
> user.hasOwnProperty("name") // 프로퍼티 확인
< true
> user.hasOwnProperty("age") // 프로퍼티 확인
< false
hasOwnProperty 는 만든적이 없는데 왜 쓸 수 있는걸까?
__proto__ 라는 객체가 있기 때문이다
이것을 prototype(프로토타입) 이라고 한다.
객체에서 프로퍼티를 읽으려고 하는데 없으면 prototype에서 찾는다.
만약 hasOwnProperty 라는 메서드가 객체 안에 있다면 (내가 새로 정의했다면)
내가 만든 메서드가 작동하고 멈춘다 (prototype에서는 탐색하지 않는다)
파이썬에서 배운 __매직메서드__ 느낌이다.
이미 정의되어있는 매직메서드를 내가 오버라이딩 하면 덮어씌워지는 것도 비슷한것같다
4-2. 상속
프로토타입을 적용해서 공통 프로퍼티를 상속받을 수 있다.
car 라는 객체에 공통 프로퍼티를 정의하고,
bmw 객체와 audi 객체에는 각각 자기만의 프로퍼티를 정의해준다
그리고 __proto__ 를 적용해서 car 객체를 상속받을 수 있다.
bmw.__proto__ = car; // 상속
audi.__proto__ = car; // 상속
실제로 해보자
bmw와 audi를 찍어봤는데 자기 내부의 프로퍼티만 출력되고 있다?
상속... 받는다며...
상속시키고 싶었던 프로퍼티(wheels)에 접근해보자
의도했던대로 상속받은 wheels 값 4가 출력된다.
color 랑 navigation 밖에 없는데 어디서 wheels 를 찾는거지?
bmw 객체에 더 자세히 들어가보면 __proto__ 이 있고, 이걸 열어보면
__proto__에 drive 와 wheels 가 상속되어 있는 것을 확인할 수 있다!
마찬가지로 프로퍼티를 찾는 순서는 다음과 같다.
일단 bmw 객체 내에서 wheels 프로퍼티를 찾는다.
찾는다면 거기서 탐색을 멈추고, 만약 없다면 prototype에서 찾는다
파이썬의 클래스 상속이랑 같은 원리로 상속은 계속 이어질 수 있다.
cat.__proto__ = animal; // 고양이 <- 동물 상속
persian.__proto__ = cat; // 페르시안 <- 고양이 상속
4-3. Prototype Chain
이렇게 상속은 계속 이어질 수 있는데
프로퍼티를 찾는 순서는 항상 객체 자신 내부 부터 시작해서
한단계씩 __proto__ 를 타고 올라가면서 찾는다.
persian 에 찾는 프로퍼티가 없다면
persian의 prototype (==cat) 에서 프로퍼티를 찾음
cat 에도 찾는 프로퍼티가 없다면
cat의 prototype (==animal) 에서 프로퍼티를 찾음
이걸 Prototype Chain 이라고 한다.
4-4.
for .. in
: prototype 에서 상속받은 프로퍼티까지 전부 순회해서 반환한다
Object.keys, Object.values
: 객체 자신만의 프로퍼티만 반환
만약 for ... in 문에서 구분하고 싶다면
아까 4-1 에서 봤던 hasOwnProperty 를 사용해준다
(hasOwnProperty 는 객체가 직접 갖고있는 프로퍼티만 true 를 반환해준다)
4-5.
생성자 함수와 같이 쓸때는
Obj.prototype.property
이런식으로 상속을 정의해주면 편하다
4-6.
instanceof
해당 객체가 상속받은 인스턴스인지 확인할 수 있음
> persian instanceof Cat
< true
constructor
생성자로 만들어진 인스턴스 객체에는 constructor 라는 프로퍼티가 존재함
> persian.constructor === Cat
< true
4-7.
은닉화
현재 상태에는 문제가 하나 있다.
객체의 프로퍼티를 재정의해주면 맘대로 바꿔버릴 수 있다는 것이다
> persian.color
< "white" // 원래 흰색인데
> persian.color = "black"
> persian.color
< "black" // 맘대로 바꿀 수 있음
이걸 closure를 활용해서 은닉화 해보면
초기에 세팅했던 프로퍼티(color) 값을 얻을 수만 있고, 바꿀 수는 없다.
getColor 함수가 생성될 당시의 컨텍스트를 기억하고 있기 때문이다.
5.
Class
: ES6에 추가된 스펙
# ES6
ECMAScript 2015
2015년에 발표된 자바스크립트의 6번째 버전을 의미함
Class, 화살표함수, let/const 같은 새로운 문법들이 포함되었음
5-1.
Class 차이점
생성자 함수
const User = function(name, age){
this.name = name;
this.age = age;
};
User.prototype.showName = function() {
console.log(this.name);
};
const spino = new User("Spino", 23);
클래스를 사용하면
class User2 {
constructor(name, age){
this.name = name;
this.age = age;
}
showName() {
console.log(this.name);
}
}
const Brachio = new User2("Brachio", 15);
차이점
1) 문법이 더 편리해진다.
2) 실수로 new를 빼고 작성하면 알아서 TypeError를 반환해주기 때문에 알수없는 오류를 줄일 수 있다.
3) for ... in 문에서 객체가 갖고있는 프로퍼티만 보여주기 때문에 hasOwnProperty 안써두 됨
5-2.
Class 상속
extends
class 를 상속받아서 새로운 class 를 만들 수 있다.
상속받은 클래스의 프로퍼티들도 모두 __proto__ 로 상속된다.
(파이썬에서 class 상속받으면 method들이 모두 상속되는 것과 동일)
5-3.
method overriding (메소드 오버라이딩)
원래 있던 프로퍼티를
상속받은 클래스에서 재정의하면
덮어씌워진다.
부모에 있는 메소드를 그대로 사용하면서
확장만 하고싶다면
super.methodname 를 사용하면 된다
6. Promise
6-1.
프로미스
const pr = new Promise((resolve, reject) => {
// code
});
new Pomise 로 생성하고 두 개의 함수를 전달 받을 수 있다.
- resolve : 성공했을 때 실행되는 함수
- reject : 실패했을 때 실행되는 함수
# callback
어떤일이 완료되었을 때 실행되는 함수를 callback 함수라고 함.
Promise 는 두 개의 프로퍼티를 갖고있음
state 와 result
// 판매자
const pr = new Promise((resolve, reject) => {
setTimeout(() => {resolve("OK")}, 3000)
});
// 소비자
pr.then(
function(result){console.log(result + "가지러가자")},
).catch(
function(err){console.log("다시주문해주세요...")}, // 명시하는것을 권장
).finally(
function(){console.log("---주문 끝---")}, // 처리완료시 항상 실행
);
- then : resolve 와 reject 를 처리할 수 있는데, reject 부터는 catch 한테 넘길 수 있다.
- catch : 에러가 발생했을 경우(reject인 경우)에만 실행됨
- finally : 이행이든 거부든 어쨋든 처리가 완료되면 항상 실행된다 (로딩화면같은걸 없앨 때 유용하다구 함)
6-2.
프로미스 체이닝
const f1 = (message) => {
console.log(message);
return new Promise((res, rej)) => {
setTimeout(() => {
res("1번 주문 완료");
}, 3000); // 3초
});
};
const f2 = (message) => {
console.log(message);
return new Promise((res, rej)) => {
setTimeout(() => {
res("2번 주문 완료");
}, 2000); // 2초
});
};
const f3 = (message) => {
console.log(message);
return new Promise((res, rej)) => {
setTimeout(() => {
res("3번 주문 완료");
}, 5000); // 5초
});
};
console.log("시작!"); // 시작
f1()
.then((res) => f2(res)) // 1번 주문 완료
.then((res) => f3(res)) // 2번 주문 완료
.then((res) => console.log(res)) // 3번 주문 완료
.catch(console.log)
.finally(() => {
console.log("끝"); // 끝
});
f1 실행하면 Promise를 반환하고 resolve 반환값을 f2 에게 전달
마찬가지로 f2 도 Pomise를 반환하고 resolve 반환값을 f3 에게 전달
이렇게 Promise 가 연결되는 것을
Promise chaning(프로미스 체이닝) 이라고 한다
코드를 보면 세 개의 주문에 각자 걸리는 시간이 다르다.
순차적으로 주문한다고 생각하면
총 3+2+5초 = 10초가 걸린다.
하지만 동시에 주문할 수 있지 않나?
그렇다면 가장 오래걸리는 5초면 주문이 전부 끝날 수 있겟지
그럴 때 쓰는것이 바로
6-3.
Promise.all
console.time('x'); // 시간 재보자
Promise.all([f1(), f2(), f3()]).then((res) => {
console.log(res);
console.timeEnd('x'); // 약 5000ms
}); // ["1번 주문 완료", "2번 주문 완료", "3번 주문 완료"]
배열을 인자로 받고, 각 Pomise 들이 넘겨준 값도 배열로 들어온다.
콘솔로 시간을 찍어보면
시간이 절약된것을 확인할 수 있다!
주의할점은
reject 일때 실패 에러가 뜨면서 어떤 데이터도 볼 수 없다는 것이다.
하나의 정보라도 누락될 시 페이지를 보여주면 안될 때 사용하면 좋다
다 보여주거나 or 다 안보여주거나
6-4.
Promise.race
console.time('x'); // 시간 재보자
Promise.race([f1(), f2(), f3()]).then((res) => {
console.log(res);
console.timeEnd('x'); // 약 2000ms
}); // "2번 주문 완료"
사용방법은 Promise.all 이랑 동일함.
- all : 모든 작업이 완료될 때까지 기다림
- race : 하나라도 1등으로 완료되면 끝내버림
언제쓰냐면
ex) 용량이 큰 이미지들을 로딩하는데 하나라도 완료되면 그 이미지를 보여줄 때 사용한다
7.
async await
를 사용하면 Promise 의 then 메소드를 chain 형식으로 호출하는 것보다 가독성이 좋다
7-1.
async
함수 앞에 async 라는 키워드를 붙여주면 항상 Promise 를 반환한다.
async function getName() {
return "Spino";
}
console.log(getName()); // Promise {<fulfilled>: "Spino"}
그래서 함수를 호출한 다음에 .then() .catch() 를 사용할 수 있다.
getName().then((name) => {
console.log(name);
}); // Spino
만약 함수 내부에서 예외가 발생하면 rejected 상태의 Promise 가 반환된다.
async function getName() {
throw new Error("err...");
}
getName().catch((err) => {
console.log(err);
}); // Error: err...
7-2.
await
await 는 async 함수 내부에서만 사용할 수 있다.
(일반 함수에서 사용하면 에러 발생함)
await 오른쪽에는 Promise 가 오고, 그 Promise 가 처리될 때까지 기다린다
function getName(name) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(name);
}, 1000);
});
}
async function showName() {
const result = await getName("Spino");
console.log(result);
} // 1초 후에 Spino 가 찍힘
getName()에서 resolve 되는 값을 기다렸다가 넣어준 것이다
Promise / then으로 작성한 코드를 async / await 로 변경해보자
// Promise / then
f1()
.then((res) => f2(res))
.then((res) => f3(res))
.then((res) => console.log(res))
.catch(console.log)
.finally(() => {
console.log("끝");
});
// async / await
async function order() {
const result1 = await f1();
const result2 = await f2(result1);
const result3 = await f3(result3);
console.log(result3);
console.log("끝");
}
동일하게 실행되는데
async / await 는
데이터가 기다렸다가 변수에 들어가는 게 직관적으로 보여서 가독성이 좋다.
그래서 Promise / then 을 쓰는 것보다 async / await 쓰는게 더 권장됨
Promise / then 에서 에러 처리를 위해 .catch()를 사용한 것처럼
async / await 에서는 try ... catch 문을 사용한다
// async / await
async function order() {
try { // .then()에 해당
const result1 = await f1();
const result2 = await f2(result1);
const result3 = await f3(result3);
console.log(result3);
} catch (e) { // .catch()에 해당
console.log(e);
} // .finally()에 해당
console.log("끝");
}
'JavaScript' 카테고리의 다른 글
자바스크립트 공부 #2 (0) | 2024.10.04 |
---|---|
자바스크립트 공부 #1 (0) | 2024.10.04 |