탱구탱구 개발자 일기

1. 함수란

  • 수학 : 주어진 입력 값 x에 대한 출력 값 y를 대응시키는 규칙
  • JavaScript : 기본적으로 수학과 동일하다. 일련의 처리를 하나로 모아 언제든 호출할 수 있도록 만들어 둔 객체. 수학과 달리 입력을 받은 후 함수 안 로직을 통해 특정 작업을 수행한다는 점. 함수의 입력 값을 인수(argument), 출력 값을 반환 값이라 명칭

2. 함수 선언문으로 함수 정의

자바스크립트는 기본적으로 function 키워드와 그 옆에 인수를 담는 괄호 기호를 통해 정의한다. 이때 함수 정의문의 인수를 인자(parameter)라고 부른다. 인수는 복수가 될 수 있다. 또한 반환값으로 return문을 사용한다.

function f() {
    return;
}

 

이밖에도 함수를 정의하는 방법은 다양하다. ES6 이후에는 화살표 함수 표현식을 통해 function 키워드를 생략하는 방법도 추가되었다.

 

3. 함수의 실행 흐름

  • 호출한 코드에 있는 인수가 함수 정의문의 인자에 대입
  • 함수 정의문의 중괄호(블록이라 부름) 안에 작성된 프로그램이 순차적으로 실행된다.
  • return 문이 실행되면 호출한 코드로 돌아간다. return 문의 값은 함수의 반환 값
  • return 문이 실행되지 않은 상태로 마지막 문장이 실행되면, 호출한 코드로 돌아간 후에 undefined가 함수의 반환 값이 된다.

 

4. 함수 선언문의 끌어올림(Hoisting)

자바스크립트 엔진은 변수 선언문과 마찬가지로 함수 선언문을 프로그램의 첫머리로 끌어올린다. 이것을 호이스팅된다라고 표현하는데 예시는 다음과 같다.

  • DOM이 그려지고 addEventListner에 의해 안에 작성한 프로그램이 실행
  • 함수 선언문으로 선언한 hoist 함수는 프로그램 첫머리로 끌어올려짐(호이스팅)
  • 코드 상 console 보다 아래 있지만 console이 정상적으로 출력될 수 있다.
<script>
    document.addEventListener("DOMContentLoaded", function () {
        console.log(hoist(10));
        function hoist(arg) { return arg * arg; }
    });
</script>

변수 선언문도 마찬가지로 호이스팅 되는데 이 부분은 선언자인 var, {let, const}에 따라 실제로 동작 방식에 있어서 차이가 있다. 이 부분은 변수의 hoisting에서 알아보도록 하자.

 

5. 값으로서의 함수

함수 정의에서도 말했듯이 자바스크립트에서 함수는 객체이다. 함수 선언문으로 함수를 선언했을 때 내부적인 동작에 의해 함수 이름을 변수 이름으로 한 변수와 함수 객체가 만들어진다. 그리고 그 변수에 함수 객체의 참조가 저장된다.

따라서 이 변수를 다른 변수에 할당할 수 있다. 또한 다른 함수의 인수로 사용도 가능하다.

<script>
    document.addEventListener("DOMContentLoaded", function () {
    	// 다른 변수에 할당
        console.log(hoist(10));
        function hoist(arg) { return arg * arg; }

        let assign = hoist;
        console.log('assign value:',assign(100)); // assign value: 10000 
		
        // 다른 함수의 인수로 사용
        function assignHoist(arg) {
            return arg;
        }
		
        let assignFunction = assignHoist(hoist(20));
        console.log('assignFunction value:', assignFunction); // assignFunction value: 400
    });
</script>

 

내부적으로 hoist는 아래와 같은 형태이다.

[변수 hoist가 함수 객체를 참조하고 있음]

hoist function(arg) {return arg * arg; }


6. 값에 의한 호출과 참조에 의한 호출

함수는 원시 값을 인수로 넘겼을 때와 객체를 인수로 넘겼을 때 다르게 동작한다.

 

  • 원시 값이 인수일 때 => 값의 전달
    • 변수 x의 복사본이 함수 f1의 인자 a에 할당됨 => 원시 값을 넘기면 그 값 자체가 인자에 전달된다.(참조 아님)
<script>
    document.addEventListener("DOMContentLoaded", function () {
        function f1(a) {
            return a = a + 1;
        }

        let x = 3;

        let y = f1(x);
        console.log('x :', x, 'y :', y); // x : 3,  y : 4
    });
</script>

값의 전달 : 변수 a와 x는 다른 영역의 메모리에 위치한 별개 변수. a값이 변경되더라도 x값은 바뀌지 않음

 

  • 객체가 인수일 때 => 참조 전달
    • 변수 xx의 복사본이 함수 f2의 인자 obj에 할당됨 => 객체를 넘기기 때문에 그 객체의 참조 값이 전달된다.
    • 원시 값이 인수인 상황과 다르게 변수 xx에 객체 {a : 10}의 참조가 저장되어 있다.
    • 그리고 이 참조 값이 인자 obj에 전달하는 것이다.
    • 따라서 변수 xx와 인자 obj는 동일한 객체를 참조하고 있다.
    • 그러므로 함수 f2 안에서 arg.a를 수정하는 것은 xx.a를 수정하는 것과 같다.
<script>
    document.addEventListener("DOMContentLoaded", function () {
        function f2(arg) {
            arg.a = arg.a + 1;
            return arg;
        }

        let xx = { a:10 };
        let yy = f2(xx);
        console.log('xx:'xx, 'yy:'yy); // xx : Object {a=11}, yy : Object {a=11}
    });
</script>

 

※ 함수에서 복수의 인수를 넘기게 될 경우 인수 개수가 추가되거나 변경되면 함수 자체를 바꿔야 하므로 유지 보수에 힘들다. 이 경우 인수를 객체의 property로 담아서 객체를 생성하고 그 객체를 인자로 넘긴다면 훨씬 더 코드가 정돈될 수 있다.

 

[변경 전]

인수가 추가되거나 순서가 변경되면 인자 받는 부분 및 내부 로직, 즉 함수 전체를 다시 정의해야 한다.

document.addEventListener("DOMContentLoaded", function () {
  // 복수의 인수일 때 깔끔하게 인자로 넘기는 방법

  // 변경 전

  let a = 1;
  let b = 2;
  let c = 3;

  function beforeSetProperties(a, b, c) {
  	return a + b + c;
  }

  beforeSetProperties(a, b, c);
});

[변경 후]

인수가 추가되면 함수의 인자로 받는 객체를 찾아서 property만 추가하고 내부 로직만 수정하면 된다.

이렇게 할 경우 함수에 필요한 인수를 객체 형태 묶어서 관리할 수 있다.

document.addEventListener("DOMContentLoaded", function () {
  // 복수의 인수일 때 깔끔하게 인자로 넘기는 방법
  
  // 변경 후
  let parameters = {
    a : 1,
    b : 2,
    c : 3
  }

  function setProperties(params) {
  	return params.a + params.b + params.c;
  }

  setProperties(parameters);
});

 

7. 변수의 유효 범위(scope)

 

유효 범위: 변수에 접근할 수 있는 범위

 

[유효 범위를 결정하는 방법]

  • 어휘적 범위(lexical scope) : 프로그램 구문만으로 유효 범위를 결정
  • 동적 범위(dynamic scope) : 프로그램 실행 중에 유효 범위를 결정

자바스크립트는 어휘적 범위를 채택하고 변수의 유효 범위에 따라 전역 변수 지역 변수로 나눠진다.

  • 전역 변수 : 함수 바깥에서 선언된 변수, 전체 프로그램이 유효 범위
  • 지역 변수 : 함수 안쪽에서 선언된 변수와 함수 인자, 변수가 선언된 함수 내부

자세한 건 코드를 통해 알아보자

let parameters = { // 전역변수
    a : 1,
    b : 2,
    c : 3
}

function setProperties(params) {
    let abc = 10; // 지역변수
	
    console.log('abc:', abc); // abc : 10
    console.log('params:', params) // params : {a:1, b:2, c:3}
    console.log('parameters:', parameters); // parameters : {a:1, b:2, c:3}
	
    return params.a + params.b + params.c;
}

console.log('parameters:', parameters); // parameters : {a:1, b:2, c:3}
console.log('abc:', abc); // Uncaught ReferenceError: abc is not defined
console.log('params:', params); // Uncaught ReferenceError: params is not defined

console.log('setProperties:', setProperties(parameters)); // setProperties: 6

함수를 기준으로 볼 때 parameters 변수는 함수 밖에 선언되어 전역 변수로 프로그램 전체를 유효 범위로 갖기 때문에

setProperties 함수 안, 밖 모두 참조가 가능하다.

 

반면에 setProperties 함수 안에 선언된 abc 변수는 해당 함수 블록 안에서만 유효한 지역변수이다.

따라서 함수 안에선 abc에 대해 참조가 가능하지만 함수 밖에서 참조하려고 하면 해당 변수가 remove 되었기 때문에

'abc is not defined'라는 참조 오류가 발생한다.

 

마찬가지로 setProperties의 인자로 쓰인 params도 함수 내부에서 참조가 가능하지만 밖에서 참조할 때 참조 오류가 발생하므로 지역변수인 것을 알 수 있다.

 

8. 함수 리터럴로 함수 정의

 

함수 선언문으로 정의한 것을 함수 리터럴로 바꾸면 다음과 같다.

// 함수 선언문
function ff(x) { return x; }

// 함수 리터럴
let ff = function(x) { return x; };

함수 리터럴은 이름이 없기 때문에 익명 함수라고 칭한다. 또한 두 방식의 차이점은 함수 선언문 같은 경우 블록을 닫을 때 세미콜론이 필요 없지만 리터럴로 선언할 경우 반드시 세미 콜론이 필요하다.

 

사용법 또한 ff(x)라고 사용하면 함수가 실행

 

두 방식의 차이점은 함수 선언문 같은 경우 프로그램 첫머리로 Hoisting이 된다고 했지만 함수 리터럴로 선언할 경우 Hoisting이 되지 않는다. 즉, 변수에 할당한 후에 비로소 ff라는 이름을 갖게 되고 호출할 수 있다는 것이다.

 

// 함수 리터럴
console.log(ff(3)); //Uncaught TypeError: ff is not a function
var ff = function(x) {
  return x;
}

이러한 익명 함수에는 이름을 붙일 수 있는데 위의 코드를 아래와 같이 이름을 붙여보았다.

var ff = function fff(x) {
  return x;
}

 

이렇게 익명 함수에 이름을 붙이게 되면 코드에서 fff라는 이름은 함수 안에서만 유효하고 함수 밖에선 fff라는 이름으로 함수를 호출할 수가 없다. 이럴 경우 디버거를 할 때 이름이 붙은 익명 함수이기 때문에 구별할 수 있다는 장점이 있다.

var ff = function fff(x) {
  console.log(fff.arguments); // Arguments [3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
  return x;
}
console.log(fff); // Uncaught ReferenceError: fff is not defined
ff(3);

9. 객체의 메서드

 

앞서 변수를 객체 형태로 만드는 것도 알아봤는데 이 객체의 property 값에 함수 객체의 참조를 대입할 수 있다. 이럴 경우 해당 property를 메서드라고 부른다. 메서드를 정의할 때 해당 함수 객체는 함수 리터럴, 즉 익명 함수여야 한다. 또한 객체 생성 이후 나중에 추가할 수 있다.

// 객체의 매서드
let customProperties = {
  a : -10,
  b : 2.6,
  c : 3.4,
  calc : function () {
    return (Math.abs(this.a) - Math.round(this.b)) + Math.ceil(this.c);
  }
}

console.log('customProperties.a:', customProperties.a); //customProperties.a: -10
console.log('customProperties.b:', customProperties.b); //customProperties.b: 2.6
console.log('customProperties.c:', customProperties.c); //customProperties.c: 3.4
console.log('customProperties.calc:', customProperties.calc()); //customProperties.calc: 11

함수 객체 안에 적힌 this는 디스 바인딩 컴포넌트에 의해 이 함수를 메서드로 가지고 있는 객체인 customProperties를 가리킨다. 즉 this.a == customProperties.a이다. 메서드를 호출할 때는 일반 함수 실행과 동일하게 소괄호를 붙여야 한다.

 

일반적으로 메서드는 해당 메서드가 속한 객체의 다른 property의 데이터와 상태를 변경할 때 사용한다.

// 객체를 참조값으로 갖는 d라는 이름의 property와 change라는 이름의 메서드 추가
let customProperties = {
  a : -10,
  b : 2.6,
  c : 3.4,
  d : { x : 100, y : 200 },
  calc : function () {
      return (Math.abs(this.a) - Math.round(this.b)) + Math.ceil(this.c);
  },
  change : function (i, j) {
      this.d.x = this.d.x + i;
      this.d.y = this.d.y + j;
  }
}

customProperties.change(10, 20);
console.log('customProperties.d:', customProperties.d); // customProperties.d: {x: 110, y: 220}

아래처럼 change 메서드를 통해 객체 내부의 다른 property d의 데이터를 변경시켰다.

 

데이터와 데이터의 상태를 변경할 수 있는 메서드를 객체 안에 담아서 이러한 객체들이 모이고 사용해 프로그램을 제작하는 방식을 객체 지향 프로그래밍이라고 한다.

 

10. 함수를 활용하면 얻는 장점

  • 재사용이 용이하다.
  • 코드 분석이 쉽다.
  • 유지 보수할 때 좋다.

11. 함수는 객체

 

자바스크립트 함수는 Function 객체이다. 또한, 아래와 같은 특징이 있는 객체를 일급 객체라고 부르고. 일급 객체인 함수는 일급 함수라고 부른다. C, Java의 프로그래밍 언어의 함수는 일급 함수가 아니다. 때문에 스킴 등의 함수형 언어처럼 함수형 프로그래밍이 가능하다.

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

이 글을 공유합시다

facebook twitter kakaoTalk kakaostory naver band
loading