회고록 블로그

[공부 복습] 생활코딩 JavaScript 입문 강의 복습 (2) 본문

2. 프로그래밍 언어 공부/JavaScript

[공부 복습] 생활코딩 JavaScript 입문 강의 복습 (2)

김간장 2021. 4. 12. 18:22

관련 글 : 

cinnamonc.tistory.com/168

 

[공부 필기] 생활코딩 JavaScript 입문 강의 필기 (2)

cinnamonc.tistory.com/162 자바스크" data-og-host="cinnamonc.tistory.com" data-og-source-url="https://cinnamonc.tistory.com/162" data-og-url="https://cinnamonc.tistory.com/162" data-og-image="https:/..

cinnamonc.tistory.com


함수 _ 유효범위

1. 함수 안에서 사용하는 변수의 유효범위 실습

var temp = 'global';
function funcScope(){
//	var temp = 'local';
    alert(temp);  // 결과 global
}

     * 함수 내에 지역변수가 없고, 전역변수만 있을 때 => 전역변수 출력

var temp = 'global';
function funcScope(){
  var temp = 'local';
  alert(temp); // 결과 local
}

     * 함수 내에 지역변수가 있고, 전역변수도 있을 때 => 함수 내에 있는 지역변수 출력

var temp = 'global';
function funcScope(){
    temp = 'local';
  document.writeln(temp);
}

funcScope(); // 결과 local
document.writeln(temp); // 결과 local

실행결과

     * 함수 내에서 변수 temp를 변경하면 => 전역변수 temp가 변경됨

     * 하지만 함수 내에 지역변수 temp를 만들고(var temp), temp 값을 변경하면 => 지역변수 temp의 값이 변경됨

var temp = 'global';

function funcScope(){
    var temp = 'local';
  document.writeln(temp);
}
funcScope(); // 결과 local
document.writeln(temp); // 결과 global

실행결과

     * 함수 내에서 지역변수 temp를 선언해서 출력했고, 함수 밖에서 변수 temp를 출력하면

        => 함수 내에서는 지역변수 temp가 출력되고 함수 밖에서는 전역변수 temp가 출력됨

 

2. 함수만이 유효범위를 제공

    * for문, if문 등은 유효범위를 제공하지 않음

    * for문 안에서 선언된 sum 변수는 for문을 벗어난다고 해서 사라지지 않음 (지역변수 아님)

 

함수 _ 값으로서의 함수와 콜백

1. 함수를 값으로서 인자로 받아보기

    * alert(cal(increase(1), 1)); 을 실행하면 결과가 출력되지 않음 주의

    * 만약 소괄호를 붙이면 calfn은 function이 아니라는 오류 발생 (함수가 값으로서 전달되기 때문으로 추측됨)

    * 소괄호가 포함되면 '함수'를 의미

// 함수를 인자로 받아보기

function cal(calfn, num){ // 함수는 인자로도 사용할 수 있음
  return calfn(num); // 함수는 함수의 리턴값으로도 사용할 수 있음
}

function increase(num){
  return num+1;
}

function decrease(num){
  return num-1;
}

alert(cal(increase, 1)); // 결과 2 
// 함수를 값으로 전달하기 때문에 increase()와 같이 괄호를 붙이지 않는 것으로 추정
alert(cal(decrease, 1)); // 결과 0

 

2. 함수를 값으로서 배열의 값으로 쓰기

    * 여기에서 소괄호 없이 함수명만 적으면 f(){console.log~~~}의 문법이 출력됨

    * 배열의 함수를 실행시키려면 반드시 소괄호를 붙여줄 것

 

3. 함수를 리턴값으로 사용해보기

    * 동작 순서 (추측) : 

       (1) main() 함수가 쌓임

       (2) 26번째, 27번째 줄에 있는 number1과 number2가 선언 및 초기화됨

       (3) console.log 안에 매개변수로 있는 calfn('plus')가 호출됨

       (4) calfn 함수가 정의된 곳으로 이동 -> 10번째줄 cal 객체를 선언 및 초기화함 -> 23번째 줄 return cal[operator] 명령으로 이동

       (5) cal 객체 안에 있는 두개의 속성(property) 중에서 입력받은 operator(여기에서는 'plus')에 대응하는 값(여기에서는 메소드)을 찾아서 -> 리턴(return) 해줌

       (6) 다시 30번째 줄로 가서 리턴받은 함수를 호출함 (이때 인자는 number1과 number2)

       (7) 해당 함수 실행 결과를 console.log로 처리함

 

 

2. 함수를 함수(값)로서 인자로 전달해보기

    * 함수를 인자로 전달해보기

    * sort 내장 메소드를 이용해볼 것

    * sortfn 함수를 인자로 전달할 때에는 소괄호()를 적지 않는 점 유의

var numbers = [0, 10, 20, 9, 8, 7, 6, 5, 4, 3, 2, 1];
// 값 내림차순 정렬해보기
var sortfn = function(a, b){
    return b-a;
    // b가 a보다 크면 -1 출력
    // b와 a가 동일하면 0 출력
    // b가 a보다 작다면 1 출력
    // 
}

console.log(numbers.sort(sortfn));

 

3. 콜백 실습해보기

   - 비동기적 처리로 인한 문제

     : setTimeout의 처리 결과를 받기 전에 function aPrint가 실행되어버림

     : 숫자 3이 출력되기를 바랬으나 1이 출력됨

     : 마지막 출력결과인 "함수 출력 결과 : 3"은 60000ms(=60초) 후에 나온 결과임

     : 이렇게 출력되는 이유는 아래 링크의 동영상을 한번만 보면 이해할 수 있음

     : 동영상을 확인하고 크롬 개발자 도구에서 디버깅하면서 "Call Stack"을 보는 것도 이해하는데 도움이 됨

www.youtube.com/watch?v=8aGhZQkoFbQ

 

   - 콜백 함수를 이용해서 해결해보기

      : 출력된 순서를 잘 확인 해야함

 

            var numbers = [20, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1];
            var sortfunc = function(a, b){
                console.log(a, b);
                // 버블정렬로 추정
                return a-b;
                // a>b이면 return값이 양수(1)
                // a<b이면 return값이 음수(-1)
                // a=b이면 return값이 0 으로 반환되는 것을 사용자가 기준으로 잡았기 때문에
                // 자바스크립트가 그 기준에 맞춰 정렬을 수행함
            }
            console.log(numbers.sort(sortfunc));
            // sortfunc가 콜백(callback) 함수임

※ 위의 코드는 내장메소드 sort를 응용해서 숫자를 정렬하는 코드

      - 여기에서 sortfunc는 "콜백함수"라고 함

      - 개인적으로 추측해보기로는 sortfunc가 콜백함수인 이유가 (sort 함수를 직접 까본건 아니지만)

         바로 위에 있는 그림의 CallbackA와 동일한 역할을 하고 있기 때문인 것 같음

      - 바로 위에서 CallbackA(sortfunc)라는 함수를 정의하고 a함수(sort함수)의 인자로 넣고 a함수를 호출한 것처럼

         sort 함수도 이와 동일한 원리로 움직이기 때문에 sortfunc가 콜백함수라고 하는 것이 아닐까 조심스럽게 생각해봄..

         (인자로 전달한 CallbackA 함수(sortfunc)는 a함수(sort) 안에서 호출되어 비동기적 처리로 인한 문제를 해결해줌)

 

 

클로저

1. 아래와 같이 외부함수가 소멸된 후 내부함수에 접근해서 외부함수의 지역변수를 사용하려는 경우 실습

     * cal 함수(외부함수)가 리턴값을 반환하고 소멸되었음에도 불구하고 내부함수가 외부함수의 지역변수에 접근할 수 있음

        - 추측: 함수는 실행되는 시점에 변수가 결정되는 것이 아니라 정의(선언)되는 시점에 변수의 유효범위를 결정한다고 했었으므로

                     (= 정적유효범위, 렉시컬 스코프)

          외부함수 cal이 소멸되어도 내부함수들이 외부함수의 지역변수를 사용할 수 있는 것이 아닐까 추측해봄.

function cal(){
    var num1 = 1;
    var num2 = 2;
    var mode = 'plus';

    if(mode == 'plus'){
        return function(){
            console.log(num1+num2);
        }
    }
    else{
        return function minus(){
            console.log(num1-num2);
        }
    }
}

oprt = cal();
oprt();

 

2. 실용적인 클로저 예제

     * 외부함수(factory_movie)가 소멸된 후 내부함수(get_title 속성, set_title 속성의 메소드)는 외부함수의 지역변수(title)에 접근할 수 있음

     * 또한 각각의 내부함수에서 호출했었던 외부함수의 지역변수(title)의 값을 변경할 수도 있음

        (외부함수 factory_movie는 이미 소멸했지만 내부함수(get_title, set_title의 메소드)를 통해서

         외부함수의 지역변수(title)를 읽기, 쓰기가 가능하다는 얘기)

         => 즉, set_title 속성의 메소드를 이용해서 ghost 객체의 title만 변경을 한다고 가정하면

         => ghost 객체의 title만 변경이 되며, matrix 객체에 title에는 영향을 끼치지 않음 (matrix 객체의 title도 함께 변경되지 않음)

function factory_movie(title){
    // 1) 매개변수 title은 함수의 지역변수
    return {
        // 2) 아래 함수는 내부함수에 해당
        get_title : function() {
            return title; //4) 내부함수가 외부함수(factory_movie)의 지역변수(title)에 접근
        },
        set_title : function(_title){
            title = _title;
        }
    }
}

ghost = factory_movie('Ghost in the shell'); // 3) 외부함수(factory_movie) 값 리턴 후 소멸
matrix = factory_movie('Martix');

console.log("처음 : ", ghost.get_title());
console.log("처음 : ", matrix.get_title());

ghost.set_title('공각기동대');
console.log('공각기동대로 변경 완료하였습니다.');

console.log("변경 후 : ", ghost.get_title());
console.log("변경 후 : ", matrix.get_title());

실행결과: ghost 객체의 title만 변경된 것을 확인

※번외. 위의 factory_movie 코드는 아래와 같이 변경 가능

...
function factory_movie(title){
    var titleObj = {
        get_title : function(){
            return title; 
        },
        set_title : function(_title){
            title = _title;
        }
    }

    return titleObj;
}
...

 

3. 클로저에 대해서 공부할 때 예제로 많이 사용되는 아래의 코드를 유의할 것 (자주하는 실수 유형)

    * 아래 글에서 "클로저" 필기 내용 중 "(4) 클로저에 대해서 공부할 때 예제로 많이 사용되는 아래의 코드를 유의할 것 (자주 발생하는 실수 유형)"에 대하여 연결해서 글을 적기로 함..

cinnamonc.tistory.com/168

 

[공부 필기] 생활코딩 JavaScript 입문 강의 필기 (2)

cinnamonc.tistory.com/162 자바스크" data-og-host="cinnamonc.tistory.com" data-og-source-url="https://cinnamonc.tistory.com/162" data-og-url="https://cinnamonc.tistory.com/162" data-og-image="https:/..

cinnamonc.tistory.com

     * 문제의 코드

         => 0부터 4까지 숫자가 출력되기를 기대했으나 실제로는 5가 다섯번 출력

var arr = []
for(var i = 0; i < 5; i++){
    arr[i] = function(){
        return i;
    }
}
for(var index in arr) {
    console.log(arr[index]());
}

※ 사실 아래와 같이 코드를 수정하면 해결됨

// 클로저가 아닌 코드
var arr = [];
for(var i=0; i<5; i++){
    arr[i] = function(){
        return i;
    }();
    // function 정의 후 바로 실행 = 익명 즉시실행함수
}

console.log('i의 값 출력 : ', i);
for(var index in arr){
    console.log('arr 값 순차적으로 출력 : ', arr[index]);
}

하지만 이 코드는 클로저가 아님..

위의 그림에서 "배열 arr에 저장된 값"을 보면 알 수 있음 (그림에서 첫번째 결과 참고)

 

만약 클로저였다면

1. 배열 arr의 데이터에 function이 저장되고

2. 그 function 안에는 외부함수의 지역변수에 접근하는 코드가 있어야 했으며,

3. 외부함수가 소멸된 후에 그 배열 arr에 저장된 function을 호출해서 외부함수의 지역변수에 접근했어야 함

 

하지만 위의 그림에서 첫번째 결과는 그저 함수를 실행시켜서 연산을 처리하고 그 결과로 "숫자"를 배열 arr에 대입했을 뿐임.


     * 다시 본론으로 돌아와서, 이 문제가 발생하는 원인은 이러함

        - for(var i=0; i<5; i++) 코드 블록 

           => 반복문(for)을 이용해서 변수 i의 값을 1씩 올리면서 배열 arr 안에 값을 대입함

           => 이때 arr[0], arr[1], ... , arr[4]에는 각각   function() { return i; }   가 대입됨

           => i가 5까지 카운트가 되면 for문을 벗어남

        - for(var index in arr) 코드 블록

           => arr[0]부터 arr[4]까지 하나씩 배열에 들어있는 함수를 호출함

           => 이때, 앞에서 배열의 데이터는   function() { returni; }   가 저장되어 있다고 언급함

           => 즉, 이 arr를 실행시킬 때 i의 값을 읽어오고 -> 이미 i가 5이기 때문에 숫자 "5"가 다섯번 리턴됨

var arr = []
for(var i = 0; i < 5; i++){
    arr[i] = function(){
        return i;
    }
}
for(var index in arr) {
    console.log(arr[index]());
}

 

※ 참고로, 함수가 정의(선언)될 때 결정되는 것은 변수의 유효범위인 것이지 (지역변수, 전역변수 등)

      변수의 값(i의 값)이 결정되는 것은 아님

      이 부분에 대해서 잠깐 헛갈렸는데 (ㅜㅜ)

      만약 함수가 정의(선언)될 때 변수가 결정되게 되면 함수를 선언한 후에는 변수의 값을 변경할 수 없을 것임..

      즉, 아래 코드의 결과를 보면 함수가 호출될 때 변수의 값(i의 값)을 읽어오는 것을 알 수 있음..

 

     * 이 문제를 클로저로 해결할 수 있음. 아래 코드와 같이.

        => 하지만 꼭 클로저가 아니더라도 처음에 위에서 말한 코드나, const, let 등으로 해결할 수 있는 것 같음

        => 어쨌든 클로저 실습이니까 클로저로 해결을 해야함

              * 다시 한번 정리하면, 클로저는 "내부함수가 외부함수의 맥락(context)에 접근할 수 있는 것"을 가리킴

var arr = [];
for(var i=0; i<5; i++){
    arr[i] = function(id){
        return function(){
            return id;
        }
    }(i);
}

console.log('i의 값 출력 : ', i);
for(var index in arr){
    console.log('arr 값 순차적으로 출력 : ', arr[index]());
}

     * 외부함수를 만들고 그 안에 내부함수를 넣어야함

     * 위의 코드는 아래와 같이 풀어서 쓸 수 있음

        => 풀어서 보면 클로저가 더 잘 보임

var arr = [];
for(var i=0; i<5; i++){
    function outter(id){
        // 매개변수 id는 outter의 지역변수와 동일
        function inner(){
            return id;
            // 내부함수 inner는 외부함수 outter의 지역변수 id를 반환함
        }
        return inner;
    }
    arr[i] = outter(i);
    // outter 함수는 정의하고 바로 실행시킬수도 있음(가명 즉시 실행 함수)
}
...

     * 위의 코드는 함수 outter 안에 inner 라는 내부함수를 넣어서 최종적으로 inner 함수가 배열 arr에 저장될 수 있도록 하는 코드임

        => 아까 위에서도 언급했듯이 "(내부)함수"가 아니라 "숫자"를 배열 arr에 대입할 수도 있지만 그건 클로저가 아님

     * arr[i] = outter(i) 코드 블록

         - outter 함수를 호출(실행)하면서 i의 값을 읽고 인자로 전달함 (때문에, i=5가 아니라 그 순간 i의 값인 i=0,1,2,3,4...가 전달됨)

         - outter 함수의 지역변수인 id에 i의 값이 전달되어 대입됨

         - outter 함수의 안에 inner 함수를 정의(선언)함

         - 정의된 inner 함수를 리턴함

         - 배열 arr에 outter 함수가 리턴한 값을 저장함

     * 그래서 맨 처음 문제의 코드와 현재 고쳐진 코드를 디버깅하면 아래와 같이 차이가 남

 


     * 최근에는 const, let을 사용해서 중괄호({}) 안에서 지역변수처럼 변수를 사용할 수 있게 된 것 같음

        => 원래 자바스크립트는 함수 스코프임

              # 쉽게 말해서 함수 안에서 중괄호 안에 var을 붙여서 임의의 변수를 선언하면 지역변수가 되지만 #

              # for문, if문과 같은 명령의 중괄호 안에서 var을 붙여서 임의의 변수를 선언하면 전역변수가 됨 #

        => const와 let은 블록 스코프임

              # 즉, 함수 뿐만 아니라 for문, if문 등 중괄호 안에서 let, const를 사용해서 임의의 변수를 선언하면 놀랍게도 그 중괄호 안에서만 유효한 값이 됨(마치 지역변수처럼) #

              # 자세한 사항은 아래 능력자님 글 참고 #

poiemaweb.com/es6-block-scope

 

let, const | PoiemaWeb

ES5까지 변수를 선언할 수 있는 유일한 방법은 var 키워드를 사용하는 것이었다. var 키워드로 선언된 변수는 아래와 같은 특징이 있다. 이는 다른 언어와는 다른 특징으로 주의를 기울이지 않으면

poiemaweb.com

var arr = [];
for(let i=0; i<5; i++){
    arr[i] = function(){
        return i;
    }
}


for(var index in arr){
    console.log('arr 값 순차적으로 출력 : ', arr[index]());
}

     * 이 부분에 대한 원리는 아직 잘 모르겠음 (좀 더 공부가 필요함)

 


복습 내용이 길어져서 한번 여기서 끊고 글을 나눠 쓰기로 했다.

 

Comments