코딩애플 Javascript ES6 Part 1

신승준·2022년 6월 30일
0

this

<script>
    // use strict를 사용하면, 의도적으로 window를 띄우려던 경우가 아니라면 undefined나 에러가 출력된다.
    // 사실상 window를 this로 사용하는 경우는 없다. 그냥 window 객체를 사용하면 되기 때문이다.
    // 따라서 이러한 경우에는 개발자에게 오류임을 알려줄 수 있다.
    // 'use strict'

    // 1. window
    // 일반 함수 실행 방식으로 함수가 실행된다면, this는 단순히 window 객체를 참조한다.
    console.log(this);

    function func() {
        console.log(this);
    }

    func(); // undefined
    window.func(); // window

    // 1-1. window
    var age = 20;

    const jun = {
        age: 27,
        logAge: function () {
            console.log(this.age);
        },
    };

    var junAge = jun.logAge;

    // 이 때 this는, 일반 함수 실행 방식에 의해 불려지기 때문에 this는 window이다.
    // 따라서 this.age는 20이므로 20이 출력된다.
    junAge(); // window

    // 2. dot notation
    var obj = {
        data: "Kim",
        func: function () {
            console.log(this);
        },
    };

    obj.func(); // obj

    // 2-1.
    var obj2 = {
        data: {
            func: function () {
                console.log(this);
            },
        },
    };

    obj2.data.func(); // data

    // 2-2.
    var obj3 = {
        data: {
            func: () => {
                console.log(this);
            },
        },
    };

    obj3.data.func();   // arrow function은 this를 재설정하지 않는다. 따라서 window가 뜬다.

    var obj4 = {
        data: {
            func() {
                console.log(this);
            },
        },
    };
    
    obj4.data.func();   // data
</script>
  • 결국 this는 자기가 포함된 object를 나타내게 된다.



this 연습 문제

<script>
    // object를 많이 생성하고 싶을 때, object를 찍어내는 함수를 생성자(constructor)라고 한다.
    // 3. 이 때 this는, 생성자에 의해 새로 생겨나는 instance object를 뜻한다.
    function Machine() {
        this.name = "Kim";
    }
    
    var obj = new Machine();
    
    console.log(obj);
    
    // 4. 이벤트 리스너 안에서, 이 때 this는 event.currentTarget과 동일하다.
    // 이 this는 addEventListener에 의해 불려지는데, 결국 #button1에서 dot notation으로 불려진다.
    // 즉 여기서 this는 id=button1인 태그이다.
    document.querySelector("#button1").addEventListener('click', function(event) {
        console.log(this);                  // event.currentTarget
        console.log(event.currentTarget);   // 어떤 요소를 클릭하여 이 event가 발생하게 되는지
    });
    
    // 4-1.
    // forEach는 밑의 for문과 똑같이 동작한다.
    // 따라서 결국엔 this는 일반 함수 실행 방식에 의해 실행된다.
    // 즉, this는 window가 출력된다.
    document.querySelector("#button2").addEventListener('click', function(event) {
        let arr = [1, 2, 3];
        arr.forEach(function (element) {
            console.log(element);
            console.log(this);
        })
        
        // for (let i = 0; i < arr.length; i++) {
        //     function func (element) {
        //         console.log(arr[i]);
        //         console.log(this);
        //     }
            
        //     func(i);
        // }
    })
    
    // 4-2.
    var obj2 = {
        names: ['Kim', 'Lee', 'Park'],
        func: function() {
            console.log(this);              // obj2
            obj2.names.forEach(function() {
                console.log(this);          // window
            })
            
            obj2.names.forEach(() => {
                console.log(this);          // 일반 함수가 아니라 arrow function이다. console.log(this)는 arrow function에 의해 실행된다.
              								// 이 this는 func에 의해 실행되고 이 func는 obj2 소속이므로 상위 단계는 obj2이다.
            })
        }
    }
    
    obj2.func();
</script>
  • arrow function은 상위 스코프에서 this를 찾는다.
  • 하지만 forEach에서 arrow function이 아니라 function으로 정의되었다면 결국 일반 함수 실행 방식으로 실행되기 때문에 window가 출력된다.



            // object를 많이 생성하고 싶을 때, 생성자(constructor)를 이용한다.
            function Machine() {
                // 3. 이 때 this는, 생성자에 의해 새로 생성되는 instance object를 뜻하게 된다.
                this.name = "Kim";
            }

            var obj = new Machine();

            console.log(obj);

            document
                .querySelector("#button1")
                .addEventListener("click", function (event) {
                    // 4. (이벤트 리스너 안에서) 이 때 this는 event.currentTarget과 동일하다.
                    console.log(this);
                    // event.currentTarget : 지금 event(클릭 이벤트)가 동작하고 있는 곳을 뜻한다. 지금 클릭 이벤트가 일어나고 있는 곳을 가르키고 있게 된다.
                    console.log(event.currentTarget);
                    // 즉 여기서 document.querySelector('#button1')과 this, event.currentTarget은 같은 곳을 가르키고 있다.
                });

            // 특수한 경우
            document
                .querySelector("#button2")
                .addEventListener("click", function (event) {
                    let arr = [1, 2, 3];
                    // 함수 안에 들어가는 함수를 callback함수라고 보면 된다.
                    arr.forEach(function (element) {
                        console.log(element);
                        // 일반 함수 안에서 사용했기 때문에 window가 출력된다. 이 함수를 담고 있는 것은 window라고 보면 된다.
                        console.log(this);
                    });
                });

            var obj2 = {
                names: ["Kim", "Lee", "Park"],
                func: function () {
                    // 이 때 this는 obj2를 출력시킨다. func는 obj2에 담겨져 있기 떄문이다.
                    console.log(this);
                    obj2.names.forEach(function () {
                        // 이 함수도 일반 함수이다. 그냥 일반 함수라서 이 함수를 포함하는 window 객체가 출력되게 된다.
                        console.log(this);
                    })
                    obj2.names.forEach(() => {
                        // 지금은 arrow function을 사용하였다. 이 arrow function은 this를 재설정시키지 않는다. 상위 단계(부모)로부터 오는 this를 그대로 사용한다.
                        // 따라서 상위 단계인 function(), 즉 func는 obj2 소속이므로 이 this는 obj2를 출력시킨다.
                        console.log(this);
                    })
                },
            };
            
            obj2.func();



var, let, const

<script>
    // 'use strict';
    
    // 기존엔 var만 존재했다.
    // ES6에서 let과 const가 추가되었다.
    // 변수의 특징 : 선언 / 할당 / 범위

    // var : 재선언 O, 재할당 O, 범위 : function
    // var는 밑에와 같이 재선언이 가능하다.
    var nameVar = "Kim";
    var nameVar = "Park";
    nameVar = "Shin"; // 재할당이 가능하다.

    // let : 재선언 X, 재할당 O, 범위 : '중괄호' {}
    // Identifier 'ageLet' has already been declared 해당 경고 문구가 뜬다.
    // let ageLet;
    // let ageLet;
    let age = 20;
    age = 30; // 재할당이 가능하다.

    // const : 재선언 X, 재할당 X, 범위 : '중괄호' {}
    // const는 선언 시 초깃값이 필요하다.
    // const whatConst;     // 초깃값을 선언해달라는 경고 문구, Missing initializer in const declaration이 뜬다.
    const nameConst = "Kim";
    // nameConst = 'Shin';     // 재할당이 불가능하다. Assignment to constant variable 라는 경고 문구가 뜨게 된다.

    const person = {
        name: "Kim",
    };
    console.log(person);

    person.name = "Shin"; // object 안의 값은 변경될 수 있다. person 자체가 재할당 된 것이 아니라, 그저 그 안의 내용이 바뀌었을 뿐인 것이다.
    console.log(person);

    Object.freeze(person); // person이 변경되는 것을 막는다. 만약 use strict 모드에서,
    person.name = "Park";   // 이렇게 person을 변경하려는 코드가 실행된다면 에러를 출력한다.
    console.log(person);
    
    function func() {
        var nameFunc = 'kim';       // var는 function 안에서만 존재한다.
    }
    
    console.log(nameFunc);          // function 밖에서는 유효하지 않다.
    
    if (true) {
        let nameIf = 'Park';        // let은 중괄호안에서만 유효하다. const 또한 마찬가지이다.
    }
    
    console.log(nameIf);            // 에러
</script>



Hoisting, 전역변수

<script>
    // 변수의 Hoisting 현상 : 변수의 선언을 변수 범위의 맨 위로 끌고 오는 현상이다.
    var age = 30;

    // 위는 실제로 아래와 같다.
    var age; // 맨 위로 변수 선언을 올린다.

    age = 30;

    // 전역 변수 : 모든 곳에서 쓸 수 있는 변수이다. 지금은 age가 전역 변수이다.

    function func() {       // 함수 또한 hoisting이 일어난다.
        console.log(age);
    }
    func();
    
    window.name = 'Kim';    // 이 또한 전역 변수이다.
                            // 자바스크립트 기본 함수를 담은 object이다. alert, DOM, getElementById(), console.log() 등이 담겨져 있는 아주 큰 객체이다.
                            // 전역 변수를 만들 때는 이와 같이 window를 더 많이 사용한다. 전역 변수라는 느낌을 더 주기 때문이다.
                            
    console.log(window.name);
    
    // 예제
    if (true) {
        let a = 1;
        // var b = 2;
        
        if (true) {
            let b = 3;       // 위의 var b = 2;가 없어지면, 이 if 문의 b는 이 if 문에서만 유효하기 때문에 console.log('b', b);는 b가 선언되지 않은 것으로 생각하고 에러를 발생시킨다.
            console.log('a', a);
        }
        
        console.log('b', b);
    }
</script>



variable 연습 문제

<script>
    // Q1 - 에러 출력
    // func();                      // 이것 자체는 상관없다. func는 hoisting되기 때문에 이 코드 자체는 문제가 없다.

    // function func() {
    //     console.log(hi);        // let, const는 hoisting 시 undefined가 자동으로 할당되지 않는다. 반면에 var는 undefined가 자동으로 할당된다.
    //     let hi = "Hello";       // 이는 실수를 없애기 위함이다. 변수 선언 및 초기화 전에 console.log를 찍는 것은 실수이기 때문이다.
    // }

    // Q2 - undefined
    // func();                     // 변수 선언만 위로 올라왔을뿐, 실제로 func에 function을 대입하는 것은 밑의 코드이다. 따라서 func()로 ()를 붙여 실행시키려고 하면, 아직 function이 아니기 때문에 에러가 출력된다.

    // var func = function() {      // 변수 선언 부분만 위로 올라간다.
    //     console.log(hi);
    //     var hi = 'Hello';
    // }

    // Q3
    // let a = 1;
    // var func = function() {
    //     a = 2;
    // }

    // console.log(a);              // 함수를 선언만 했을 뿐, 실행은 한 적이 없다.

    // Q4
    // let a = 1;
    // var b = 2;
    // window.a = 3;
    // window.b = 4;

    // console.log(a);              // 가장 가까운 값을 출력시키려고 한다. 따라서 window.a보다는 let a를 출력시킨다.
    // console.log(a + b);

    // Q5
    // // var i = 5;                   // 밑의 for문을 돌고 나면 var i는 전역변수로서 밖에 이렇게 5로 남아있다. for문을 다 돌면 var i는 5에서 멈추기 때문이다.
                                    // 이 상태에서 console.log(i)가 실행되면 var i = 5를 찾게 되어서 5가 계속해서 출력된다.
    
    // for (var i = 1; i < 6; i++) {
    //     setTimeout(function () {
    //         console.log(i);         // 이 때 console.log(i)는 바로 실행되지 않는다. 1 ~ 5초 뒤에 실행되는 코드이다.
    //     }, i * 1000);
    // }

    // for (let i = 1; i < 6; i++) {
    //     // let의 범위는 블록이다. 따라서 이 블록 내에서 let i = 1, let i = 2처럼 남게 된다.
    //     // let i = 1 ~ let i = 5;
    //     // 이후 console.log(i)가 실행되면 해당 블록 내에서 i를 찾았을 때 전역 변수 var i = 5가 아니라 let i = 1 ~ let i = 5를 찾게 될 것이다.
        
    //     setTimeout(function () {
    //         console.log(i);
    //     }, i * 1000);
    // }

    // Q6
    var buttons = document.querySelectorAll("button");
    var modals = document.querySelectorAll("div");
    
    // 반복문이 도는 시점과, for문 안의 코드가 실행되는 시점이 다르기 때문에 에러가 출력된다.
    // var는 전역변수로서 결국엔 3이 된다.
    // 버튼을 클릭할 때 실행되는, 즉 반복문보다 늦게 실행이 된다.
    for (var i = 0; i < buttons.length; i++) {
        buttons[i].addEventListener('click', function() {
            modals[i].style.display = 'block';
        })
    }
    
    // 만약 let을 사용했다면 해당 블록 내에 let i = 0, let i = 1, let i = 2이 남게 된다.
    // 반복문 안에 참조할 수 있도록 let i가 생기는 것이다.
    // for (let i = 0; i < buttons.length; i++) {
    //     buttons[i].addEventListener('click', function() {
    //         modals[i].style.display = 'block';
    //     })
    // }
</script>



Template literals / tagged literals

<script>
    var letter = `손흥민`; // 백틱을 사용하고 있다.
    // "", ''를 쓰는 것이 아니라 백틱을 쓰는 이유
    // 1. 엔터키를 눌러도 글자가 깨지지 않는다.
    // 실제로 출력해도 엔터만 될 뿐 깨지지는 않는다.

    // var letter2 = "손흥
    // 민";
    // 이렇게 하면 빨간 에러 줄이 뜨면서 깨지게 된다.

    // 2. 변수를 중간에 넣기 쉽다.
    // var letter3 = "안녕하세요 저는 " + letter + "입니다."; // 원래 방식
    // console.log(letter3);

    var letter4 = `안녕하세요 저는 ${letter}입니다.`; // 백틱을 사용하면 중간에 ${}를 사용해서 변수를 넣을 수 있다. +를 쓸 필요가 없어진다.
    // console.log(letter4);

    // 이는 밑에와 같이 HTML을 작성할 때 유용해진다.
    // var temp = `<div>${letter4}</div>`;
    // console.log(temp);

    // tagged literal
    // 소괄호 () 대신에 백틱을 붙이는 문법이 있다. 그냥 함수를 실행시켜준다.
    // 따라서 밑의 console.log에서 10이 출력된다. 백틱은 그저 함수를 실행시키는 용도일 뿐이다.
    // function func() {
    //     return 10;
    // }

    // console.log(func`안녕하세요`);

    // 사용하는 이유
    function split(letters, variable1) {
        console.log(letters);       // 백틱 안의 문자들을 Array에 담고 있다.
        console.log(variable1);     // 변수를 담고 있다.
        
        console.log(letters[1] + variable1 + letters[0]);   // 이렇게 원하는대로 순서를 바꿔서 출력해줄 수 있다.
        // console.log(variable2);
    }
    
    var letter5 = '신승준';
    
    split`안녕하세요 저는 ${letter}입니다.`;
    // // 위 백틱 안의 내용이 나누어졌다는 것을 알 수 있다.
    // // 즉 문자를 해체해서 단어 순서를 변경하거나, 단어를 제거하거나, ${변수} 위치를 옮기는 등의 작업이 손쉽게 가능해진다. 
</script>



Template literals / tagged literals 연습 문제

<script>
    // Q1
    // var pants = 20;
    // var socks = 100;
    
    // function split(letters, variable1, variable2) {
    //     console.log(letters[0] + variable2 + letters[1] + variable1);
    // }
    
    // split`바지${pants} 양말${socks}`;
    
    // Q2
    var pants = 0;
    var socks = 100;
    
    function split(letters, variable1, variable2) {
        if (variable1 === 0) {
            variable1 = ' 다 팔렸어요.';
        }
        
        console.log(letters[0] + variable1 + letters[1] + variable2);
    }
    
    split`바지${pants} 양말${socks}`;
</script>



Spread Operator

<script>
    // // ... spread operator
    // // 기본 활용 방도
    // // 대괄호 []를 제거해준다.
    // var arr = ["hello", "world"];
    // console.log(arr);
    // console.log(...arr);
    // console.log("hello", "world");

    // // 문자
    // // 마찬가지로 대괄호가 다 제거되었다고 보면 된다.
    // var letter = "hello";
    // console.log(letter);
    // console.log(...letter);
    // console.log("h", "e", "l", "l", "o");
    // console.log(letter[0]); // 문자는 배열처럼 인덱싱이 가능하다.
    // console.log(letter[0], letter[1], letter[2], letter[3], letter[4]);

    // ... 활용 방안
    // var a = [1, 2, 3];
    // var b = [4, 5];

    // var c = [...a];         // 1, 2, 3으로 만들고 그것을 []로 감쌌다.
    // var d = [...a, ...b];
    // // var e = ...a;        // 1, 2, 3이 되므로 오류가 난다.

    // console.log(a);
    // console.log(c);
    // console.log(d);

    // deep copy할 때 ...이 유용하다.
    // var a = [1, 2, 3];
    // // var b = a;      // reference data type, a와 b가 [1, 2, 3]을 공유하게 된다.
    // var b = [...a];     // b가 단독으로 [1, 2, 3]을 가진다. 풀어헤쳤다가 1, 2, 3을 가지게 된다. 이제 a와 b는 독립적으로 [1, 2, 3]을 가지게 된다.
    //                     // 이러한 복사, 독립적으로 값을 가지게 되는 상황을 deepcopy라고 한다.

    // a[3] = 4;       // deepcopy하지 않으면 이렇게 할 때 a와 b가 모두 바뀐다.

    // console.log(a);
    // console.log(b);

    // object를 복사할 때도 마찬가지이다. deepcopy
    var obj1 = { a: 1, b: 2 };
    var obj2 = { ...obj1, c: 3 };
    var obj3 = { ...obj1 };
    var obj4 = { a: 2, ...obj1 }; // 값 중복이 발생한다. 이 때는 뒤에 온 것이 그 중복된 key 값의 주인이 된다. 즉 key의 value는 뒤에 오는 것이 된다.
    var obj5 = { ...obj1, a: 2 };

    console.log(obj2); // obj1과 합쳐졌다는 것을 알 수 있다.
    console.log(obj3);
    console.log(obj4);
    console.log(obj5);
    
    // ...obj1 // 이것은 에러가 난다. 함수 - (), 객체 - {}, 배열 - []안에서 사용이 가능하다.
</script>



Spread Operator - apply, call

<script>
    // 함수를 실행할 때 인자 전달 용도로 ... spread operator를 사용할 수 있다.
    function sum(a, b, c) {
        console.log(a + b + c);
    }

    var arr = [10, 20, 30];

    console.log(sum(arr[0], arr[1], arr[2]));
    console.log(sum(...arr));

    // 옛날 방식
    sum(arr[0], arr[1], arr[2]);
    sum.apply(undefined, arr);

    // 요즘 방식
    sum(...arr); // 대괄호 []가 제거된 자료들이 인자로 들어가게 된다.
    sum(10, 20, 30);

    // apply
    var person1 = {
        greeting: function () {
            console.log(this.name + "hi");
        },
    };

    // person1의 greeting을 person2에도 적용하고 싶다.
    // 하지만 person2를 변경할 수 없을 때
    var person2 = {
        name: "shin seung jun",
    };
    
    // person의 greeting을 person2에 적용해서 실행해주세요.
    person1.greeting.apply(person2);        // 이 때 this는 person2이다.
    // person2.greeting()과 같으나, person2에는 greeting이 없다.

    person1.greeting();     // undefined hi
    
    // call
    // apply와 비슷하다.
    // greeting에 인자를 넣고 싶을 때
    // apply는 배열 형태로 넣을 수 있다.
    // call은 그럴 수 없다. 이것이 유일한 차이이다.
    person1.greeting.apply(person2, [1, 2]);
    person1.greeting.call(person2, 1, 2)
    
    sum.apply(undefined, arr);      // sum을 하는데, undefined에 적용해서 arr인자를 바탕으로 sum을 실행해주세요. undefined가 아니더라도 상관없다. 큰 의미가 없다 여기서는.
                                    // 여기서 굳이 apply를 쓴 이유는 인자를 늘어놓기보다는 그냥 배열로 넣으면 편하니까. 만약 call을 썼다면 인자를 막 썼어야 한다.
                                    // 어쨋든 요즘엔 apply나 call보다는 ...을 많이 쓴다.
</script>
profile
메타몽 닮음 :) email: alohajune22@gmail.com

0개의 댓글