1) Javascript 복습
1-1) 스코프
1-2) 호이스팅
1-3) function 키워드
1-4) this binding(this가 어려운 이유)
1) Javascript 복습
Javascript 기본문법 - 공부 순서
1. 함수 선언과 호출
2. 콜스택
3. 스코프(전역변수, 지역변수)
4. 호이스팅("실행 컨텍스트"를 배우기 위한 사전 작업)
5. function 키워드
6. this
7. 클로저
8. 함수형 프로그래밍 : map, forEach, filter 등
1-1) 스코프
var, let의 차이는 호이스팅의 발생유무(var는 호이스팅이 일어나는 반면 let은 호이스팅이 일어나지 않는다!!)이다!!
- 블록 레벨 스코프
- 함수 레벨 스코프
- 렉시컬 스코프
{
var a = 10;
}
console.log(a); // 10
{
let b = 10;
}
console.log(b); // ReferenceError: b is not defined
중괄호 유형
- for () {}
- if () {}
- function () {}
블록 레벨 스코프는 모든 "{}(중괄호)"를 가리킴!!
함수 레벨 스코프는 "함수 선언부의 {}(중괄호)"만 가리킴!!
"var"는 함수 레벨 스코프를 따르는 반면, "let과 const"는 블록 레벨 스코프를 따름!!
for (let i = 0; i < 10; i++) {}
console.log(i); // undefined
for (var i = 0; i < 10; i++) {}
console.log(i); // 10
function main() {
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, 1000 * i);
}
}
main();
// 2가지 사실(1.var는 함수 레벨 스코프를 따른다. / 2.setTimeout은 background로 넘어가 event loop를 통해 실행된다.)을
// 명심한 상태에서 위의 코드를 살펴보면
// setTimeout()이 실행되기 전에 이미 for 문은 다 돈 상태가 되기 때문에
// 최종적으로 "5"가 콘솔에 setTimeout에서 지정한 시간 간격으로 5번 찍힌다!
렉시컬 스코프
const x = "x";
function c() {
const y = "y";
console.log("c", y);
}
function a() {
const x = "xx";
console.log("a", x);
function b() {
const z = "z";
console.log("b", z);
c();
}
b();
}
a();
b();
1-2) 호이스팅
호이스팅 : 실행 컨텍스트 생성 시 렉시컬 스코프 내의 선언이 끌어 올려지는 것
var x = "sangbeom";
function inner() {
var x = "hello";
console.log(x); // hello
}
inner();
var x = "sangbeom";
function inner() {
console.log(y); // undefined(var 호이스팅이 발생하여 var 선언부가 해당 실행 컨텍스트 내의 최상단으로 끌어올려지기 때문!!)
var y = "hello";
}
inner();
해설
이전 블로그 글에서 정리했던 "call stack"에 대해서 다시 한 번 생각을 해 보면
"heap("heap"이 "실행 컨텍스트"와 동일하다고 보면 됨!!)"이라는 공간과 "call stack"이라는 공간이 있다.
이 Javascript가 실행될 때,
"Anonymous 함수"가 실행됨(익명함수 실행)
익명함수가 실행되면서 선언 부분만 실행시킴!
이때 "heap"이라는 부분이 "실행 컨텍스트"이다!!
익명함수 실행
(1) "x" 변수 선언
var x = "sangbeom"; // "x"라는 변수가 "sangbeom"이라는 값을 가지고 있음!
(2) inner 함수 선언
function inner() {
var x = "hello";
console.log(x);
}
(3) inner 함수 호출
inner();
콜스택
콜스택에 쌓이는 순서(익명함수가 먼저 실행되어 쌓이고, 그 위로 inner 함수를 호출함으로써 실행시킨 inner 함수 호출문이 새로운 실행 컨텍스트-이 실행 컨텍스트 안에 inner 함수 내부의 x 변수, console.log() 구문이 들어감!!-로 위에 쌓임!!)
inner()
Anonymous() <-- 익명함수를 실행시키는 것을 의미함!!
{
var x = 'hello';
console.log(x);
}
실행 컨텍스트
- 여기서 0001, 0002, 0003은 실행 컨텍스트를 이해하기 위해 가정한 저장공간(메모리)이라 보면 됨!
0001 x sangbeom
0002 inner {}
0003 x hello
- 아래와 같이 참조 데이터 형식으로 실행 컨택스트를 이해하면 좋음!
0002 inner {
0001 x hello
}
0001 x sangbeom
let x = "sangbeom";
function inner() {
// let은 var와 달리 호이스팅이 발생하지 않아 변수 y를 찾을 수 없다는 참조에러가 발생함!!
console.log(y); // Uncaught ReferenceError: Cannot access 'y' before initialization
let y = "hello";
}
inner();
(중요!!) var, function 키워드만 호이스팅이 발생한다!!
inner();
function inner() {
console.log('정상적으로 작동');
}
// 이 경우, function 키워드는 호이스팅이 발생하기 때문에
// 이를 통해 선언된 inner 함수 선언부가 최상단으로 끌어 올려지고
// 먼저 처음 실행된 anonymous 함수에 해당 함수 선언부가 담기고,
// 이어서 inner 함수 호출문이 기존 실행 컨텍스트 위에 새로운 실행 컨텍스트를 생성하고
// 여기에 inner 함수의 console.log() 구문이 담기고 콜스택에서 실행됨과 동시에 빠져나감!!
(문제1)
function inner() {
console.log(x); // sangbeom
}
var x = 'sangbeom';
inner();
풀이(문제1)
var가 호이스팅이 발생하여 해당 실행 컨텍스트 내의 최상단으로 끌어 올려지고, inner 함수 호출 전에 x 변수에 "sangbeom"이라는 값이 할당되어 콘솔을 찍었을 시 "sangbeom"이 잘 찍힘!!
(문제2)
inner();
function inner() {
console.log(x);
let x = 'hello';
}
var x = 'sangbeom';
풀이(문제2) --> Temporal Dead Zone(TDZ)
위의 경우에는 콘솔을 찍었을 시 "Uncaught ReferenceError: Cannot access 'x' before initialization" 에러가 찍힘!!
이는 let의 경우, 변수의 선언과 초기화가 분리되어 그 사이에 TDZ가 생성되기 때문에
만일 변수 선언 이전에 TDZ가 생성되어 접근한다면 위와 같이 Reference Error가 발생함!!
(문제3)
inner();
function inner() {
x = 'hello';
console.log(x); // hello
}
console.log(x); // hello
var x = 'sangbeom';
풀이(문제3)
var와 function 키워드는 각각 호이스팅이 발생하므로 콘솔에 "hello"가 2번 찍힘!!
추가
inner();
function inner() {
x = 'hello';
console.log(x); // hello
}
console.log(x); // hello
(중요!!) 변수 앞에 변수 선언 키워드(var, let, const)를 생략할 시 Javascript는 자동적으로 실행 컨텍스트 최상단에 var 키워드를 만들어 주기 때문에 위와 같은 결과가 나옴!!
(문제4)
inner();
function inner() {
x = 'hello';
console.log(x); // hello
}
var x = 'sangbeom';
console.log(x); // sangbeom
풀이(문제4)
var와 function 키워드는 각각 호이스팅이 발생하므로 첫 번째 콘솔에는 "hello"가, 두 번째 콘솔에는 "sangbeom"이 찍힘!!
1-3) function 키워드
function 키워드는 하는 역할이 너무 많기 때문에 어렵다!!
(1) 일반함수로 사용하는 경우
// 일반함수 --> 일반함수를 쓰고 싶은 경우에는 arrow function(화살표 함수)을 사용하는 것이 좋음!!
// 일반함수의 경우, "this"를 찍었을 시 "window(전역객체)"가 찍힘!!
function Foo(a, b) {
console.log(this); // window(전역객체)
return [a, b];
}
const a = Foo(1, 2);
console.log(a); // [1, 2]
(2) 생성자 함수로 사용하는 경우
// 생성자 함수 --> 생성자 함수를 쓰고 싶은 경우에는 "class"를 사용하는 것이 좋음!!
function Foo(a, b) {
// this = {}
console.log(this);
this.arr = [a, b];
// return this
}
const a = new Foo(1, 2);
console.log(a);
/*
const Foo2 = () => {
console.log(this);
}
console.dir(Foo2);
console.dir(Foo);
*/
(3) 객체 메서드로 사용하는 경우
// 객체 메서드
function Foo(a, b) {
console.log(this);
this.arr = [a, b];
}
const bar = {
method:Foo;
}
bar.method(5, 6); // [5, 6]
function 키워드를 쓰면 "this"가 자주 바뀐다!!
function으로 선언한 함수가 상황에 따라 "this"가 바뀐다!!
--> function 키워드는 "this binding"이라는 것이 적용되기 때문이다!!
1-4) this binding(this가 어려운 이유)
this binding : function이라는 키워드로 함수를 선언하면 this를 바꿀 수 있다!!
- bind
- call
- apply
(1) function.prototype.bind
function Foo(a, b) {
console.log(this);
return [a, b];
}
const fooBind = Foo.bind({name:'sangbeom'}); // function
fooBind(); // [undefined, undefined]
fooBind(1, 2); // [1, 2]
console.log(fooBind);
Foo라는 함수를 선언하고,
fooBind라는 변수에 Foo.bind 메서드를 호출해서 this를 바꾼 Foo 함수값을 리턴해줌!!
(2) function.prototype.call
(3) function.prototype.apply
--> call, apply는 인수를 넣어 함수를 즉시 실행시킴!!(단, 인수 형식만 아래와 같이 좀 차이가 있음!)
function Foo(a, b) {
console.log(this);
return [a, b];
}
// 첫 번째 인자값 = this 값
Foo.call({name:'sangbeom'}, 1, 2)
Foo.apply({name:'sangbeom'}, [3, 4])
ES6
- 일반함수 --> arrow function
- 생성자 함수 --> class
- 객체 메서드 --> 메서드 축약형
객체 메서드
ES5
// 객체 메서드
const bar = {
method:function (a, b) { // binding도 되면서 생성자(constructor)도 있음!!
console.log(this);
return [a, b];
}
}
bar.method(5, 6); // [5, 6]
ES6
// 메서드 축약형
const bar = {
name:'web7722',
method(a, b) { // binding은 되지만 생성자(constructor)가 없음!!
console.log(this);
return [a, b];
}
}
bar.method(1, 2); // [1, 2]
생성자 함수
ES5
// 생성자 함수
function Foo(a, b) {
console.log(this);
this.arr = [a, b];
/*
this.getArr = function() {
return this.arr;
}
*/
}
Foo.prototype.getArr = function() {
return this.arr;
}
const foo = new Foo(1, 2); // {arr:[1, 2], getArr:() => {return this.arr}}
// 상속(Javascript는 실제 가지고 있는 객체, 즉 실체 혹은 인스턴스를 넣어줌으로써 상속을 함!!)
function eagle() {
this.size = 'big';
}
function pigeon() {
this.wing = 2;
}
eagle.prototype = new pigeon();
console.dir(eagle);
const bird = new eagle();
console.log(bird);
console.log(bird.wing);
console.log(bird.valueOf());
String.prototype.split(); // String 객체 안에 split 메서드가 있다는 뜻이다!!
ES6
// class
class Foo {
constructor(a, b) {
this.arr = [a, b];
}
getArr() {
return this.arr;
}
}
const a = new Foo();
console.log(a);
for (const key in a) {console.log(key)}
(정리)
this는 실행될 때 결정됨!!
그리고 function 키워드는 안 됨!
ES6 문법을 사용해야 더 직관적임!!
'Javascript > Javascript(2022 version)' 카테고리의 다른 글
Javascript 기초(8) - callback hell, Promise, async/await (0) | 2022.12.21 |
---|---|
Javascript 복습(2) - 클로저, 로또 Refactoring (0) | 2022.12.16 |
Javascript 기초(7) - 함수, 콜스택, 스코프 체인, this (0) | 2022.11.26 |
Javascript 기초(6) - 상속 및 메서드 (0) | 2022.11.04 |
Javascript 기초(5) - 객체 및 메서드 (0) | 2022.11.03 |