본문 바로가기

프로그래밍/자바스크립트(javascript)

[자바스크립트/javascript] Function 타입

함수는 자바스크립트에서 가장 흥미로운 점이 있습니다. 함수는 사실 객체라는 점에서 입니다.

모든 함수는 Function 타입의 인스턴스이며 다른 참조 타입과 마찬가지로 프로퍼티와 메서드가 있습니다.

 

함수는 보통 다음과 같이 문법을 선언합니다.

 

function sum (num1, num2) {
	return num1 + num2;
}

 

변수를 선언하고 그 안에 function을 넣는 함수 표현식도 있습니다.

 

var sum = function(num1, num2) {
	return num1 + num2;
};

 

위의 함수 표현식에서 function 키워드 다음에 함수 이름 없이 정의를 하였습니다.

변수 sum으로 함수를 참조하므로 함수 이름은 필요하지 않습니다. 또 마지막에 다른 변수 초기화 문장과 마찬가지로 세미콜론을 쓴 점도 위와는 조금 다릅니다.

 

함수를 정의하는 마지막 방법은 Function 생성자를 이용하는 방법입니다.

Function 생성자에 넘기는 매개변수 숫자에는 제한이 없습니다. 매개변수 중 마지막은 함수 바디로 간주하며 그 이전에 있는 매개변수는 함수의 매개변수로 전달됩니다.

 

var sum = new Function("num1", "num2", "return num1 + num2");

 

이런 문법은 권장하지 않습니다. 자바스크립트 표현식으로서 평가하고 생성자에 전달할 문자열을 다시 평가해야 하므로 성능에 영향이 있기 떄문입니다.

 

 

함수 하나에 이름을 여러 개 붙일 수 있습니다.

 

function sum(num1, num2) {
	return num1 + num2;
}
console.log("sum = " + sum(10, 20) );

var sum2 = sum;
console.log("sum2 = " + sum2(10, 10));

sum = null;
console.log("sum2 = " + sum2(10, 10));

 

 

함수를 선언하고 그 후 변수안에 함수를 선언할 수 있으며, 함수가 null을 할당하더라도 함수와 연결은 사라지지만 sum2은 아무 문제 없이 함수를 호출합니다.

 

 

오버로딩 없음

 

 

함수 두 개에 같은 이름을 지정하면 마지막에 정의한 함수가 이전에 정의한 함수를 덮어씁니다.

 

function addSomeNumber(num) {
	return num + 100;
}

function addSomeNumber(num) {
	return num + 200;
}

var result = addSomeNumber(100);
console.log("result = " + result);

 

 

함수 선언, 함수 표현식

 

 

함수 선언과 함수 표현식(변수에 함수를 넣는 형식) 은 결과값을 같은 것을 나타내지만 실행 컨텍스트에 데이터를 불러올 떄 중요한 차이가 하나 있습니다.

 

함수 선언은 함수 선언은 어떤 코드도 실행하기 전에 이미 모든 실행 컨텍스트에서 접근하고 실행 할 수 있지만 함수 표현식은 코드 실행이 해당 줄까지 진행하기 전에는 사용할 수 없습니다.

 

console.log( sum(10, 20) );
function sum(num1, num2) {
	return num1 + num2;
}

 

 

 

이 코드에는 아무런 에러 없이 실행 되고 '호이스팅' 이란 과정을 통해 선언을 읽고 컨텍스트에 추가하기 때문입니다.

자바스크립트는 코드를 평가할 떄 제일 먼저 함수 선언을 찾은 다음 이들을 맨 위로 올립니다.

 

 

console.log( sum2(10, 30) );
var sum2 = function( num1, num2 ) {
	return num1 + num2;
}

 

 

이 코드는 에러를 냅니다. 함수가 함수 선언이 아니라 초기화 문장의 일부분 이기 때문입니다.

'예기치 못한 식별자' 에러를 내기 때문에 강조한 부분은 절대 실행되지 않습니다.

 

 

 

값처럼 함수 쓰기

 

 

자바스크립트에서 함수 이름은 단지 변수일 뿐이므로 함수도 다른 값이 올 수 있는 곳이라면 어디든 올 수 있습니다.

이런 특징 덕분에 함수를 다름 함수에 매개변수로 넘기거나, 함수가 실행 결과로 다른 함수를 반환하는 일이 가능합니다.

 

function callSomeFunction(someFunction, someArguments) {
    return someFunction(someArguments);
}

function add10(num) {
	return num + 10;
}

var result1 = callSomeFunction(add10, 10);
console.log("result1 = " + result1);

function getGreeting(name) {
	return "Hello, " + name;
}

var result2 = callSomeFunction( getGreeting, "Nick" );
console.log("result2 = " + result2);

 

 

함수를 실행하지 않고 단지 함수에 접근하기만 할때는 괄호는 쓰지 않습니다.

 

function createComparisonFunction( propertyName ) {
	
    return function(object1, object2) {
    	var value1 = object1[propertyName];
        var value2 = object2[propertyName];
        
        if(value1 < value2) {
        	return -1;
        } else if (value1 > value2) {
        	return 1;
        } else {
        	return 0;
        }
    };
    
}

var data = [{name : "Zachary", age: 28}, {name: "Nick", age : 20}];

data.sort(createComparisonFunction("name"));
console.log(data[0].name);

data.sort(createComparisonFunction("age"));
console.log(data[0].age);

 

 

위와 같이 return 연산자 뒤에 내부 함수가 있는 형태로 사용해서 쓸 수 도 있습니다.

 

 

 

 

함수의 내부 구조

 

함수 내부에는 arguments, this 라는 특별한 객체가 있습니다. 

arguments 객체는 배열과 비슷한 객체이며 전달된 매개변수를 모두 포함합니다.

 

function factorial(num) {
	if ( num <= 1 ) {
    	return 1;
    } else {
    	return num * factorial(num - 1);
    }
}

 

이처럼 재귀 함수로 구현한 것이 있습니다. 재귀 함수란 함수 내부에서 함수를 호출하여 특정한 조건이 있을때 까지 계속 진행 되는 로직입니다.

이 처럼 함수 이름이 바뀌지 않을 때는 않을때는 문제가 없습니다.

 

arguments.callee를 쓰면 재귀 함수가 함수 이름에 의존 하는 약점을 극복할 수 있습니다.

 

function factorial(num) {
	if (num <= 1 ) {
    	return 1;
    } else {
    	return num * arguments.callee(num - 1);
    }
}

 

 

this 역시 특별한 객체인데 함수가 실행 중인 컨텍스트 객체에 대한 참조입니다.

함수를 웹페이지의 전역 스코프에서 호출했다면 this 객체는 window를 가리킵니다.

 

 

window.color = "red";
var o = { color: "blue" };

function sayColor() {
	console.log(this.color);
}

sayColor();

o.sayColor = sayColor;
o.sayColor();

 

 

함수의 호출하는 시점에 따라 값 역시 달라집니다.

처음에 this가 window를 가리키므로 window.color가 출력이 되고 o 객체에서 호출하면 o.color로 평가가 됩니다.

 

 

caller 프로퍼티는 해당 함수를  호출한 함수에 대한 참조를 저장하며 전역 스코프에서 호출했다면 null이 저장됩니다.

 

function outer() {
	inner();
}

function inner() {
	console.log("inner == " + inner.caller);
}

outer();

 

 

outer 함수가 inner 함수를 호출하므로 inner.caller는 outer() 를 가리킵니다.

 

함수와 이름 사이의 의존성을 제거하려면 arguments.callee.caller를 쓸 수 있습니다.

 

function outer() {
	inner();
}

function inner() {
	console.log(arguments.callee.caller);
}

outer();

 

 

스트릭트 모드에서는 에러가 나오는데 arguments.caller 와 caller 를 구분하고 보안에 대한 목적으로 나타납니다.

 

 

함수 프로퍼티와 메서드

 

 

자바스크립트의 함수는 객체이며 따라서 프로퍼티와 메서드를 가집니다. 모든 함수에 공통인 프로퍼티는 length 와 prototype입니다.

length 프로퍼티는 함수가 넘겨받을 것을 예상하는 이름 붙은 매개변수 숫자입니다.

 

function test1(num1) {
	return num1;
}

function test2(num1, num2) {
	return num1 + num2;
}

function test3() {
	return "1111";
}

console.log( "test1.length = " + test1.length);
console.log( "test2.length = " + test2.length);
console.log( "test3.length = " + test3.length);

 

 

이름 붙은 매개변수 숫자는 함수마다 다르게 표현이 됩니다.

 

prototype 프로퍼티는 모든 참조 타입의 인스터스 메서드가 존재하는 곳입니다.

즉, toStirng() 이나 valueOf() 같은 메서드는 prototype에 존재하며 객체 인스턴스는 이에 접근합니다.

prototype은 열거할 수 없는 프로퍼티 이므로 for-in 문에서 나타나지 않습니다.

 

함수에는 apply() 와 call() 두 가지 메서드가 더 있습니다.

이들 메서드는 모두 소유자인 함수를 호출하면서 this를 넘기는데, 결과적으로 함수 내부에서 this 객체의 값을 바꾸는 것이나 마찬가지 입니다.

 

function sum(num1, num2) {
	return num1 + num2;
}

function callSum1(num1, num2) {
	return sum.apply(this, arguments);
}

function callSum2(num1, num2) {
	return sum.apply(this, [num1, num2]);
}

console.log( " callsum1 = " + callSum1(10, 20));
console.log( " callsum2 = " + callSum2(10, 20));

 

 

callsum1은 arguments 객체를 매개변수로 넘겼습니다. callsum2은 배열로 넘겼습니다. 둘다 올바르게 실행이 됩니다.

 

 

call() 메서드도 매개변수를 전달하는 동작을 하지만 방식이 다릅니다.

 

function sum(num1, num2) {
	return num1 + num2;
}

function callsum(num1, num2) {
	return sum.call(this, num1, num2);
}

console.log("callsum(10,10) = " + callsum(10, 10));

 

 

apply() 와 call() 중 어느 쪽을 쓸지는 순전히 개발자의 재량이며 매개변수를 전달하기 편리한 방식을 택하면 됩니다.

 

물론 apply() 와 call() 의 진가는 매개변수를 전달해 호출하는 것이 아니라 this를 바꾸는데 있습니다.

 

window.color = "red";
var o = { color: "blue" };

function sayColor() {
	console.log( "color = " + this.color);
}

sayColor();

sayColor.call(this);
sayColor.call(window);
sayColor.call(o);

 

 

 

bind() 라는 메서드도 있는데 이 메서드는 새 함수 인스턴스를 만드는데 그 this는 bind()에 전달된 값입니다.

 

window.color = "red";
var o = { color: "blue" };

function sayColor() {
	console.log( "color = " + this.color);
}

var objectSayColor = sayColor.bind(o);
objectSayColor();

 

 

objectSayColor는 전역에서 함수를 호출하더라도 blue를 표시합니다.