ABOUT ME

-

오늘
-
어제
-
-
  • [JS] 함수 알아보기
    Front-end/Javascript 2020. 11. 29. 22:06

    함수

    함수는 자바스크립트 프로그래밍의 꽃이라고 볼 수 있으며, 그만큼 중요하고 활용성이 뛰어나다고 할 수 있습니다. 기초부터 차근차근 알아보도록 하겠습니다.

    함수 선언문

    function plus(x, y) {
      return x + y;
    }

    이 함수는 x, y의 인자값을 가지고 있으며 가져온 인자값을 + 연산자를 통해 return을 통해 반환하는 함수입니다.
    여기서 주의할 부분은 return문은 절대 줄 바꿈 문자를 넣으면 안됩니다.

    만약 줄 바꿈을 넣게 되면...

    function plus(x, y) {
      return;
      x + y;
    }

    위처럼 값이 없이 반환이 되기 때문입니다.

    함수명

    위에서 든 예시는 plus라는 함수명을 가지고 있습니다. 지금은 하나의 함수만 있어서 바로 구별이 가능하지만 몇십, 백개의 함수와 많은 코드들이 있다면 저 plus라는 함수가 어떤 역할을 하는지 바로 알아챌 수 있을까요?

    물론 알아챌 수는 있지만 시간이 처음보단 오래 걸릴 것입니다. 그래서 함수를 선언하는 것 뿐만 아니라 함수명을 정하는 것도 매우 중요합니다.

    그렇다면 내가 만드는 함수가 과일의 갯수를 더하는 함수라면?

    function getFruitsPlus(x, y) {
      return x + y;
    }

    예시가 조금 추상적이긴 하지만 plus를 가진 함수명보다 훨씬 명확해 진 것을 확인할 수 있습니다. 이처럼 통상적으로 함수명을 지을 떄는 동사 혹은 동사로 시작되는 어휘로 시작합니다.

    함수 호출

    지금까지 함수를 만들어봤는데 그럼 저 함수를 어떻게 사용하냐구요?

    본인이 선언했던 함수명을 가져와 소괄호로 인자를 묶어서 호출합니다.

    getFruitsPlus(5, 15); // 20

    소괄호 안에 5와 15가 들어간 것을 확인할 수 있습니다. 저 5와 15의 인자들은 x와 y의 값을 받아 둘을 함수의 반환값에 맞춰 5 + 15가 되어 20인 결과가 반환됩니다.

    인수

    함수를 선언할 때 인수는 아주 중요한 부분입니다. 인수가 없을 수도, 하나만 있을 수도, 여러개가 있을 수도 있습니다.

    var classA = { name: "A", score: 60 };
    var classB = { name: "B", score: 80 };
    
    function getScoreAvg(a, b) {
      var totalScore = a.score + b.score;
      return totalScore / 2;
    }
    
    getScoreAvg(classA, classB);

    위 예제처럼 객체를 받아서 사용할 수도 있으며

    function makeItLouder() {
      var name = "junjang";
      alert(`${name} 소리질러어어어어어`);
    }
    
    makeItLouder();

    인수를 아예 넘기지 않고 만들 수도 있습니다.

    함수 끌어올림(호이스팅)

    끌어올림(호이스팅)에 관해선 변수 주제에서도 나왔던 개념입니다. 변수와 마찬가지로 프로그램의 원칙을 따르지 않고 함수 선언문을 첫 줄로 끌어올립니다.

    isWorking(); // 잘 동작하나?
    
    function isWorking() {
      console.log(`잘 동작하나?`);
    }

    즉, 함수 선언문을 어디에서 사용하든 상관없이 똑같이 동작한다는 것입니다.

    함수에서의 변수

    함수를 선언할 때 변수에 접근하여 사용하는 일이 굉장히 많을 것입니다. 하지만 이 변수를 사용 할 수 있는 범위와 사용하지 못하는 범위가 있습니다. 이를 유효범위(scope)라고 합니다.

    자바스크립트는 어휘적 범위(lexical scope)를 사용하고 있으며 범위는 크게 두 가지로 나뉩니다.

    • 전역변수 : 함수 바깥에서 선언된 변수로 유효범위가 프로그램 전체
    • 지역변수 : 함수 내부에서 선언된 변수로 유효범위는 함수 내부

     

    var global = "전역변수";
    function inner() {
      var local = "지역변수";
      console.log(`저는 ${global} 입니다.`); // 저는 전역변수 입니다.
      return local;
    }
    
    inner();
    console.log(`저는 ${local} 입니다.`); // undefined

    inner 함수에서 local 변수를 반환했음에도 불구하고 값이 할당이 안된 이유는 지역 변수이기 때문에 함수 내부에서만 사용이 가능했기 때문입니다.

    변수의 충돌

    위 예제에서 우린 서로 다른 변수명을 가진 변수들을 다루었습니다. 하지만 변수가 같은 이름이라면 어떻게 될까요?

    var korean = "한국사람";
    function inner() {
      var korean = "한국인";
      console.log(`저는 ${korean} 입니다.`); // 저는 한국인 입니다.
      return korean;
    }
    
    inner();
    console.log(`저는 ${korean} 입니다.`); // 저는 한국사람 입니다.

    보면 같은 korean이라는 변수를 전역, 지역 둘 다 가지고 있는 상태입니다. 서로 변수명은 같지만 다른 위치에서 메모리를 차지하고 있는 상태입니다. 따라서 함수 내부에서는 한국인, 함수 외부에서는 한국사람이 출력됩니다.

    함수 내부 끌어올림(호이스팅)

    자바스크립트 엔진은 함수 안의 변수 선언부를 함수의 첫머리로 끌어올리게 됩니다.

    function hello() {
      console.log(hello); // undefined
      var hello = "안녕하세요";
      console.log(hello); // 안녕하세요
    }

    즉, 함수 끌어올림과 마찬가지로 함수 내부에서도 동일하게 호이스팅이 발생합니다.

    함수 리터럴

    함수 리터럴은 이름이 없는 함수이므로 익명 함수 또는 무명 함수로 불립니다. 함수 리터럴을 사용할 때는 몇 가지 중요한 점이 있습니다.

    • 끝에 반드시 세미콜론을 붙인다.
    • 변수에 함수 객체의 참조를 저장한다.
    • 함수 선언문은 호이스팅이 되지만 함수 리터럴은 호이스팅이 일어나지 않는다.
    • 변수에 할당되어야 그 이름으로 호출할 수 있다.

     

    위 네 가지 점을 꼭 알아두고 사용하여야 합니다.
    간단한 예제로 알아보겠습니다.

    var hello = function () {
      console.log("안녕하세요");
    };

    hello라는 변수에 함수 객체의 참조를 저장하였습니다. 원래라면 이 함수는 어디서든 사용할 수 있는 함수였을 것입니다.(호이스팅이 일어나기 때문)

    hello(); // Uncaught TypeError: hello is not a function
    var hello = function () {
      console.log("안녕하세요");
    };

    위에서 말씀 드렸듯이 함수 리터럴은 호이스팅이 일어나지 않기 때문에 타입 에러가 발생합니다.

    객체의 메서드

    객체의 key(프로퍼티) 중에서 함수 객체의 참조를 값으로 담고있는 key를 메서드라고 부릅니다. 쉽게 말하면 함수를 value로 가지고 있는 key가 메서드 입니다.

    var person = {
      name: "junjang",
      age: 28,
      about: function () {
        return `안녕하세요 제 이름은 ${this.name}이고 나이는 ${this.age}입니다. 잘부탁드립니다 :)`;
      },
      changeInfo: function (name, age) {
        this.name = name;
        this.age = age;
      },
    };

    여기서 about, changeInfo 메서드에서 가리키는 this는 그 함수를 메서드로 가지고 있는 객체를 가리킵니다. 현재 예제에서는 person을 가리킨다고 보면 됩니다.

    보통 메서드는 일반적으로 메서드가 속한 객체의 내부 데이터(프로퍼티 값)를 바꾸는 용도로 사용이 됩니다.

    즉시 실행 함수

    일반적으로 익명 함수를 실행할 때는 익명 함수의 참조를 변수에 할당한 후 ()를 붙여 실행합니다.

    var anonymous = function () {
      // do something...
    };
    anonymous(); // 익명 함수 실행

    하지만 꼭 변수에 참조를 할당한 후 따로 실행하지 않더라도 즉시 실행할 수 있는 방법이 있습니다.

    (function () {
      // do something...
    })();

    즉시 실행 함수의 구문에서는 함수 정의식을 그룹 연산자인 ()로 묶습니다. 위 ()에는 인수 값을 넘길 수 있습니다.

    (function (a, b) {
      return a + b;
    })(1, 2); // 3

    a, b에 대한 값을 즉시 실행 함수 ()에 넣어서 사용 가능합니다.

    함수의 인수

    함수를 호출할 때 인수를 생략하거나 더 많이 넘길 수 있습니다. 이러한 경우에는 함수가 어떻게 실행되는지 알아보겠습니다.

    인수의 생략

    function param(a, b) {
      console.log(`a = ${a}, b = ${b}`);
    }
    param(5); // a = 5, b = undefined

    만약 함수에 정의된 인수보다 적게 넘긴다면 넘기지 않은 인수 값은 undefined가 출력됩니다.
    하지만 실제 현업에서 undefined가 뜨는 일은 절대 없어야겠죠? 그래서 이전에 배운 논리합(||) 연산자를 사용하여 초깃값을 설정해줍니다.
    논리합 연산자를 사용하면 null, undefined를 체크하기 용이하다고 했었죠?

    function addNumber(a, b) {
      b = b || 1;
      return a + b;
    }
    addNumber(5); // 6;

    만약 b가 들어오지 않으면 undefined로 false값이 되어 다음 값이 초깃값이 됩니다.

    Arguments

    모든 함수에서 사용할 수 있는 지역 변수로는 arguments 변수가 있습니다. 인수에 n개를 넘기면 arguments[n-1] 로 저장이 됩니다.

    function args() {
      var array = [];
      for (var i = 0; i < arguments.length; i++) {
        array.push(arguments[i]);
      }
      console.log(array);
    }
    args("a", 1, "b", 2); // ["a", 1, "b", 2]

    함수에는 인수를 따로 선언하지 않았지만 함수 호출 시 인수를 넘겼을 경우 arguments가 해당 인수를 가져와 배열에 추가하는 모습을 볼 수 있습니다.

    클로저

    클로저는 자바스크립트가 가진 강력한 기능으로 이를 활용하여 변수를 은닉하여 지속성을 보장하는 등 캡슐화를 구현할 수 있습니다. 어원 자체가 열려있는 것을 닫는다인 만큼 뭔가 은밀하게 변수를 사용하려는 느낌을 받을 수 있습니다.

    function makeCounter() {
      var count = 0;
    
      return plusCount;
    
      function plusCount() {
        return count++;
      }
    }
    
    var counter = makeCounter();
    console.log(counter()); // 0
    console.log(counter()); // 1
    console.log(counter()); // 2
    1. 외부에 있는 makeCounter는 중첩 함수 plusCount의 참조를 반환합니다.
    2. 중첩 함수 plusCount는 외부함수 makeCounter의 지역 변수 count를 참고한다.

     

    위 두 가지 사항이 이번 예제의 핵심입니다.

    먼저 1번 사항으로 counter 변수는 plusCount 객체를 참조하게 됩니다. 그렇다함은 counter 변수가 실행되면 plusCount 객체에 접근 할 수 있겠다는 것을 추측할 수 있겠죠?

    1번에 이어서 2번 사항으로 plusCount도 count 변수에 접근 할 수 있게됩니다.
    즉, counter 변수를 실행할 때마다 plusCount 객체에 접근하게 되고 실행하여 makeCounter의 지역 변수를 조작한 것입니다.

    count는 외부 함수의 지역 변수이기 때문에 외부에서 접근이 불가해 캡슐화를 구현하고 있으며, plusCount 함수가 클로저의 내부 상태를 바꾸는 메서드역할을 하고 있다는 것을 알 수 있습니다. 꼭 객체와 비슷하다는 것을 느낄 수가 있죠

    function makeCounter() {
      var count = 0;
    
      return function () {
        return count++;
      };
    }
    
    var counter = makeCounter();
    console.log(counter()); // 0
    console.log(counter()); // 1
    console.log(counter()); // 2

    기존 예제는 설명을 위해 이름을 붙였었지만 보통은 익명 함수를 반환하는 방법을 사용합니다.

    여러 메서드를 반환하는 클로저

    기존에 프로퍼티로 생성했던 메서드를 가진 객체를 클로저를 활용해 메서드를 여러개 가진 객체를 반환하는 함수를 만들어보겠습니다.

    function Wizard(name, skill) {
      var _name = name;
      var _skill = skill;
      return {
        getName: function () {
          return _name;
        },
        getSkill: function () {
          return _skill;
        },
        setSkill: function (x) {
          _skill = x;
        },
      };
    }
    
    var wizard = Wizard("goodWizard", "energy Bolt");
    console.log(wizard.getName()); // goodWizard
    console.log(wizard.getSkill()); // energy Bolt
    wizard.setSkill("fire Ball");
    console.log(wizard.getSkill()); // fire Ball

    각각의 프로퍼티에 맞는 메서드를 만들고, 외부 함수인 Wizard가 반환된 내부 함수들을 실행합니다. 내부 함수 또한 외부 함수의 지역 변수를 사용하여 조작을 하고 있습니다.

    네임스페이스

    전역 유효 범위의 오염을 방지하기 위한 수단으로서 객체, 함수를 네임스페이스로 이용하는 방법입니다.

    전역 변수와 전역 함수를 전역 객체에 선언하는 행위는 전역 유효 범위의 오염시킨다고 하는데 그 이유는 자바스크립트 엔진이 호이스팅 하여 변수 또는 함수를 단 하나만 생성하기 떄문에 해당 변수, 함수를 공유하는 코드는 올바르게 동작하지 않을 수 있습니다.

    때문에 이러한 일을 방지하기 위해 여러 기법을 사용합니다.

    객체 네임스페이스

    객체를 값으로 가지는 전역변수를 생성합니다.

    var myApp = myApp || {};

    이 객체에서는 프로그램 전체에서 사용하는 모든 변수와 함수를 프로퍼티로 정의합니다. 만약 myApp이라는 객체가 있지 않을 경우 빈 객체를 초깃값으로 넣습니다.

    myApp.name = "junjang";
    myApp.showName = function() {...};
    myApp.view = {} // 부분 네임스페이스

    이처럼 객체를 네임스페이스로 이용하면 변수, 함수를 계층적으로 관리할 수 있습니다.

    함수 네임스페이스

    함수 안에서 선언된 변수함수 내부에서만 쓸 수 있기 때문에 이 성질을 활용하여 네임스페이스로 활용 가능합니다.

    var x = "global x";
    (function () {
      var x = "local x";
      var y = "local y";
    })();
    
    console.log(x); // global x
    console.log(y); // Uncaught ReferenceError: y is not defined

    내부에 선언된 x와 y는 함수 외부에서 사용하려고 할 때 참조 에러가 발생합니다. 이유는 지역 변수이므로 전역 변수와 충돌하지 않기 때문입니다. 따라서 일시적인 처리를 하고자 할 때 즉시 실행 함수 안에 작성하면 효율적입니다.

    네임스페이스 모듈패턴

    즉시 실행 함수를 사용하여 모듈을 정의해보겠습니다.

    var Module = Module || {};
    (function (_Module) {
      var name = "NoName";
    
      function getName() {
        return name;
      }
    
      _Module.showName = function () {
        console.log(getName());
      };
    
      _Module.setName = function (x) {
        name = x;
      };
    })(Module);
    Module.setName("junajang");
    Module.showName();

    각 메서드는 getName(), name을 참조하고 있기 때문에 클로저가 생성되며 즉시 실행 함수의 지역변수 name과 지역 함수 getName은 클로저의 내부 상태로 저장됩니다.

    일급 객체

    자바스크립트에서는 함수도 일종의 객체입니다. 따라서 함수는 값을 처리할 수 있고 프로퍼티와 메서드도 가지고 있습니다.
    그런 의미에서 다음과 같은 특즹을 가지고 있습니다.

    • 함수는 변수나 프로퍼티나 배열 요소에 대입할 수 있다.
    • 함수는 함수의 인수로 사용할 수 있다.
    • 함수는 함수의 반환값으로 사용할 수 있다.
    • 함수는 프로퍼티와 메서드를 가질 수 있다.
    • 함수는 이름 없는 리터럴로 표현할 수 있다(익명 함수).
    • 함수는 동적으로 생성할 수 있다.

     

    이러한 특징을 가진 객체를 일급 객체라고 부르며 일급 객체인 함수는 일급 함수라고 합니다.

    함수의 프로퍼티

    함수는 Fuction 생성자의 prototype 객체의 프로퍼티를 상속 받아서 사용하게 됩니다. Function.prototype의 프로퍼티들을 알아보겠습니다.

    apply

    apply는 선택한 this와 인수를 사용하여 함수를 호출하며 인수는 배열 객체입니다. 첫 번째 인수는 함수의 this, 두 번째 인수는 배열입니다.

    function say(greeting, age) {
      console.log(`${greeting}! my name is ${this.name} i'm ${age}`);
    }
    
    var jun = { name: "jun" };
    say.apply(jun, ["Hello", 28]); // Hello! my name is jun i'm 28

    함수에서 this.name을 받아 출력을 해주고 있는데 apply에서 첫 번쨰 인자로 jun의 name을 가리키고, 인수를 배열로 Hello , 28을 선언했기 때문에 위와 같은 값이 출력됩니다.
    여기서 실제 배열 객체가 아닌 arguments, 유사 배열 객체를 넘겨도 동일하게 실행됩니다.

    call

    call은 apply와 동일한 방식이지만 두 번째 인수가 배열이 아닌 쉼표로 구분됩니다.

    function say(greeting, age) {
      console.log(`${greeting}! my name is ${this.name} i'm ${age}`);
    }
    
    var jun = { name: "jun" };
    say.call(jun, "Hello", 28); // Hello! my name is jun i'm 28

    apply와의 차이는 인수의 차이만 있습니다.

    bind

    bind는 객체에 함수를 묶는 역할을 합니다

    function say(greeting, age) {
      console.log(`${greeting}! my name is ${this.name} i'm ${age}`);
    }
    
    var jun = { name: "jun" };
    var sayToJun = say.bind(jun);
    sayToJun("Hello", 28); // Hello! my name is jun i'm 28

    say 함수에 bind를 jun으로 해줌으로써 sayToJun을 호출하게 되면 항상 this는 jun 객체를 가리키게 됩니다.
    즉, say 함수에 jun이라는 객체를 고정시켰다고 볼 수 있습니다.

    'Front-end > Javascript' 카테고리의 다른 글

    [JS] 이벤트 알아보기  (0) 2020.11.29
    [JS] 연산자 알아보기  (0) 2020.11.29
    [JS] 객체 알아보기  (0) 2020.11.29
    [JS] 변수 알아보기  (0) 2020.11.29
    완전 초 단순하게 캐러셀(Carousel) 구현해보기  (1) 2020.10.27

    댓글