Javascript ICE Breaking 세번째 - Function
- No Return Type(함수 정의시 린턴 타입에 대한 구분을 하지 않는다.)
- Optinal Argument List(동적으로 변경가능한 인자값의 갯수)
- Arguments.length
function a(arg1, arg2) { arg1; //a arg2; //undefined { //a('a') function b() { arguments[0]; //a arguments[1]; //b arguments.length //2 } //b('a', 'b') function c(arg1, arg2) { arg1; //a arg2; //b arguments[2]; //c arguments[3]; //d argumnets.length; //4 } //c('a', 'b', 'c', 'd') |
callee 속성(arguments)
- arguments.callee
- callee 속성은 관련된 함수가 실행될 때만 사용 가능한 arguments 개채의 멤버이다.
- 이름이 없는 함수의 재귀호출에 사용된다.
var factorial = function(x) { if(x <= 1) return 1; retrun x * arguments.callee(x -1); } factorial(5); |
Function.length
- Expected number of arguments(정의된 인자값의 길이를 알 수 있다.)
function f(x, y, z) { var actual = arguments.length; var expected = arguments.callee.length; if(actual != expected) { throw new Error("Wrong number of arguments:") } alert("f("+x +", "+y+", "+z+"")") } f(1, 2) //Error f(1, 2, 3) |
Function : First Class Object(일급 객체)
JavaScript의 함수는 1급 객체(first class object)이다.
1급 객체가 무엇인지 인지 하기전에 먼저 1급 시민(First class citizen)의 정의에 대해 언급한다.
1급 시민(first class citizen)의 정의
Christopher Strachey는 1967년 수업에 사용한 노트1에서 다음과 같이 1급 시민(first class citizen)과 2급 시민(second class citizen)을 소개했다. 아주 정확하게 정의한 것은 아니지만 ALGOL의 실수(real number)와 프로시져(procedure)를 비교하면서 설명하였다.
In ALGOL a real number may appear in an expression or be assigned to a variable, and either may appear as an actual parameter in a procedure call. A procedure, on the other hand, may only appear in another procedure call either as the operator (the most common case) or as one of the actual parameters. There are no other expressions involving procedures or whose results are procedures. Thus in a sense procedures in ALGOL are second class citizens—they always have to appear in person and can never be represented by a variable or expression (except in the case of a formal parameter), while we can write (in ALGOL still)
일반적으로 1급 시민의 조건을 다음과 같이 정의한다.
- 변수(Variable)에 담을 수 있다.
- 인자(Parameter)로 전달할 수 있다.
- 반환 값(Return value)으로 전달할 수 있다.
대부분의 프로그래밍 언어에서 숫자는 1급 시민의 조건을 충족한다. 숫자는 변수에 저장할 수 있으며 인자나 반환값으로 전달할 수 있다.
1급 객체(First class object)
1급 객체(First class object)라는 것은 특정 언어에서 객체(Object)를 1급 시민으로써 취급한다는 뜻이다. 당연히 위의 조건을 모두 충족한다.
1급 함수(First class function)
1급 객체 뿐만 아니라 1급 함수도 존재한다. 함수를 1급 시민으로 취급하는 것이다. 몇몇의 학자들은 1급 시민의 조건과 함께 다음과 같은 추가적인 조건을 요구한다.
- 런타임(Runtime) 생성이 가능하다.
- 익명(Anonymous)으로 생성이 가능하다.
이런 추가조건으로 봤을 때 C의 함수는 1급 함수로 볼 수 없다.
JavaScript의 함수는 1급 함수? 1급 객체?
JavaScript에서는 객체는 1급 시민으로 취급한다. 그리고 사실 JavaScript의 함수도 객체로써 관리되므로 1급 객체라고 볼 수 있다. 동시에 JavaScript의 함수는 1급 함수의 추가조건도 만족한다. 이렇게 1급 객체인 동시에 1급 함수이지만, 보통 1급 객체로 기술하는 편인듯하다. 아마 함수가 객체이기 때문이지 않을까 싶다.
JavaScript에서 함수가 1급 객체인 것이 중요한 이유
함수가 1급 객체라는 사실은 겉으로 봤을 땐 그리 특별하지 않아 보일 수 있다. 함수를 그냥 주고받을 수 있다는 것 뿐이지만 이것이 아주 큰 차이점을 만든다.
가장 중요한 장점은 바로 고차 함수(high order function)가 가능하다는 점이다. JavaScript의 each, filter, map, sort 등의 함수들이 얼마나 편리한지는 잘 알고 있을 것이다. 인자로 목적에 맞는 함수를 하나 넘겨주면 아주 쉽게 처리가 된다. 반면 Java 7의 Collections.sort 함수같은 경우도 비교를 위해 인자를 하나 넘겨주게 되는데, 이것은 함수가 아니라 Comparator interface 타입의 인스턴스(Instance)이다. 함수 하나를 넘겨주기 위해서 새로운 클래스를 만들고 그것의 인스턴스까지 생성해야 하는 것이다. - ES6와 Java 8에서는 람다(lamda)가 지원되면서 훨신 간편해졌다.
1급 객체가 JavaScript의 클로져(Closure)와 만나면 또 하나의 장점이 생긴다. JavaScript의 함수는 생성될 당시의 Lexical Environment를 기억하게 되는데, 함수를 주고받게 되면 이 Lexical Environment도 함께 전달된다. 이것을 이용해서 커링(Currying)과 메모이제이션(Memoization)이 가능해진다. 여기서 적기엔 너무 큰 주제이므로 기회가 될때 따로 다뤄보도록 하겠다.
function a() { var b = function(f) { f(); } return b; } function b(){ //some code } var c = a(); c(b); |
일급 객체에 대한 설명은 하기 웹사이트를 통해 참고되었습니다.
http://bestalign.github.io/2015/10/18/first-class-object/
Acting to an Event
- Element's event attributes
- Use DOM API instead
<html> <head> <script type="text/javascript"> function displayDate() { alert(new Date()); } </head> <body> <h1>My First Web Page</h1> <p id="demo"></p> <button type="button" onclick="displayDate()">Display Date</button> </body> </html> |
Scope & Closure
- Activation Object, Call Object
- Lexical Scoping
실행되는 유효범위가 아닌 선언되어 있는 유효범위에서 실행
- 호출되는 시점에 선언된 범위로 부터 실행객체를 제공
var x = "global" function global() { var x = "local"; return function local() { alert(x); } } var local = global(); local(); |
Closure
- Encapsulation
- Restriction of Inheritance
클로저는 독립적인 (자유)변수를 가리키는 함수이다. 또는, 클로저 안에 정의된 함수는 만들어진 환경을 '기억한다'.
클로저(Closure)는 프로그래머가 창조적이고 인상적이며 간결한 프로그래밍을 할 수 있게 해준다. 클로저는 빈번하게 사용되며 자바스크립트 스킬과 관계없이 자주 마주치게 된다. 물론, 지금 당장 클로저는 복잡해 보일 수 있겠으나, 설명을 이해하게 된다면 자바스크립트 코딩시에 매일 사용하게 될 것이다.
클로저란 무엇인가?
클로저는 외부함수(포함하고 있는)의 변수에 접근할 수 있는 내부 함수를 일컫는다. 스포크 체인(Scope chain)으로 표현되기도 한다. 클로저는 세가지 스코프 체인을 가진다.
- 클로저 자신에 대한 접근(자신의 블럭내에 정의된 변수)
- 외부 함수의 변수에 대한 접근
- 전역 변수에 대한 접근
내부함수는 외부함수의 변수뿐만 아니라 파라미터에도 접근할 수 있다. 단, 내부함수는 외부함수의 arguments 객체를 호출할 수는 없다.(하지만 외부함수의 파라미터는 직접 호출할 수 있다.)
기본적인 클로저 예제 :
1 |
|
function Person(name, age){ this.getName = function(){ return name; } this.getAge = function(){ return age; } } Person.prototype.sayHello = function(){ alert(this.getName() + " is " + this.getAge()); } var p = new Person("lee", 27); p.sayHello(); p.name; //undefined p.age; //undefined |
클로저는 Node.js의 비동기, 논-블럭킹 아키텍처의 핵심기능으로 활용되고 있다. 클로저는 jQuery에서도 빈번하게 사용되며, 거의 모든 자바스크립트 코드에서 볼 수 있다.
jQuery의 전형적인 클로저 사용예:
1 | $(function() { |
클로저 규칙과 부수 효과
클로저는 외부함수가 리턴된 이후에도 외부함수의 변수에 접근할 수 있다.
클로저를 사용하면서 가장 헷갈리는 것 중의 하나는 외부함수가 리턴된 이후에도 여전히 내부함수가 외부함수의 변수에 접근하고 있다는 것이다. 자바스크립트의 함수가 실행되었을 때, 함수는 자신이 생성 되었을때와 동일한 스코프 체인을 사용한다. 그러므로, 내부함수를 나중에 호출할 수 있다.
function celebrityName(firstName) {
var nameIntro = "This is celebrity is ";
// 이 내부 함수는 외부함수의 변수와 파라미터에 접근할 수 있습니다.
function lastName(theLastName) {
return nameIntro + firstName + " " + theLastName;
}
return lastName;
}
var mjName = celebrityName("Michael"); // 여기서 celebrityName 외부함수가 리턴됩니다.
// 외부함수가 위에서 리턴된 후에, 클로저(lastName)가 호출됩니다.
// 아직, 클로저는 외부함수의 변수와 파라미터에 접근 가능합니다.
mjName("Jackson"); // This celebrity is Michael Jackson
클로저는 외부함수의 변수에 대한 참조를 저장한다.
클로저는 실제 값을 저장하지 않는다. 클로저가 호출되기 전에 외부함수의 변수가 변경되었을 경우, 클로저는 더 흥미로워 진다. 이 강력한 기능은 창의적인 방법으로 활용될 수 있다. 아래의 내부(private) 변수예제는 더글라스 크락포드(Douglas Crockford)에 의해 처음 시연되었다.
function celebrityID() {
var celebrityID = 999;
// 우리는 몇개의 내부 함수를 가진 객체를 리턴할것입니다.
// 모든 내부함수는 외부변수에 접근할 수 있습니다.
return {
getID: function() {
// 이 내부함수는 갱신된 celebrityID변수를 리턴합니다.
// 이것은 changeThdID함수가 값을 변경한 이후에도 celebrityID의 현재값을 리턴합니다.
return celebrityID;
},
setID: function(theNewID) {
// 이 내부함수는 외부함수의 값을 언제든지 변경할 것입니다.
celebrityID = theNewID;
}
}
}
var mjID = celebrityID(); // 이 시점에, celebrityID외부 함수가 리턴됩니다.
mjID.getID(); // 999
mjID.setID(567); // 외부함수의 변수를 변경합니다.
mjID.getID(); // 567; 변경된 celebrityID변수를 리턴합니다.
클로저 비꼬기
클로저가 갱신된 외부 함수의 변수에 접근함으로써, 외부함수의 변수가 for문에 의해 변경될 경우 의도치 않는 버그가 발생할 수 있다.
1 |
"Willis", id:0}];
|
위의 예제에서, 익명의 내부함수가 실행될 시점에 i의 값은 3이다.(배열의 크기만큼 증가한 값)
숫자 3은 uniqueID에 더해져 모든 celebritiesID에 103을 할당 한다.
기대했던 결과 값인 (100, 101, 102)와 달리 모든 리턴된 배열의 id 는 103이 된다.
이런 결과가 나타난 이유는, 앞서 언급했듯이 클로저는(이 예제에서 내부의 익명함수) 외부변수에 대한 값이 아닌 참조로 접근하기 때문이다. 즉, 클로저는 최종 갱신된 변수(i)에 대해서만 접근할 수 있으므로, 외부함수가 전체 for문을 실행하고 리턴한 최종 i의 값을 리턴하게 된다. 100 + 3 = 103
이런 부작용을 고치기 위해서 "즉시 호출된 함수 표현식(Immediately Invoked Function Expression, IIFE)"를 사용할 수 있다.
1 | function celebrityIDCreator(theCelebrities) { 그 결과값을 리턴합니다. "Willis", id:0}]; |
Closure에 대한 설명은 하기 페이지를 통해 참조되었습니다.
http://chanlee.github.io/2013/12/10/understand-javascript-closure/
즉시 실행 함수(Immediate Function)
- self-invoking/ self-executing
- 선언과 동시에 실행하는 무명함수
- Module화에 사용되는 패턴
(function(){ alert('hello') })(); //or (function(){ alert('hello') }()); |
Module화 예시
<script type="text/javascript"> var module = (function(){ var i = 0; function increment(){ i = i + 1; }
function getCount(){ increment(); return i; } return { count : getCount } })(); console.log(module.count()); </script> |
'Software > JavaScript & JQuery' 카테고리의 다른 글
[JavaScript] - ICE Breaking(2) (0) | 2016.08.30 |
---|---|
[JavaScript] - ICE Breaking(1) (0) | 2016.08.17 |
[JavaScript] - Prototype 과 Constructor !!! (0) | 2012.03.09 |
[jQuery] - jQuery Mobile (0) | 2012.01.18 |
[DOM] - Div 와 Span (0) | 2012.01.11 |