[JS] 함수 - 유효범위, 콜백, 클로저, arguments, 호출
[강의 출처] opentutorials.org/course/743/6583
함수지향 - 생활코딩
함수지향 카테고리의 하위 수업들은 함수형 언어로서 자바스크립트의 면모를 다룬다. 자바스크립트의 핵심적인 도구는 함수다. 자바스크립트의 함수는 매우 강력하다. 함수에 대한 이해 없이��
opentutorials.org
유효범위(Scope):
-변수를 선언할 때 'var'는 해당 변수가 지역변수(local variable)임을 의미
-한번 지역변수로 선언한 이후에는 해당 범위(지역) 내에서는 지역변수로 사용됨
-자바스크립트는 함수 단위로 유효범위를 제공(if, for, while등의 블록 단위에는 제공 X)
-유효범위는 선언 맥락에 따라 유효범위가 결정되는 Lexical Scoping 방식을 따른다
var vscope = 'global'; // 전역변수
function fscope(){
var vscope = 'local'; // 지역변수
document.write(vscope);
};
function fscope2(){
document.write(vscope);
vscope = 'local';
};
function fscope3(){
document.write(vscope);
};
fscope(); // 'local': 가깝게 선언된 변수(지역변수) 우선 이용
fscope2(); // 'global': 지역변수가 없기 때문에 전역변수 호출
fscope3(); // 'local'; fscope2()에서 vscope 재정의 했기 때문에 현재 vscope 값은 'local'
유효범위 관련하여 발생할 수 있는 이슈
-예상치못한 다른 맥락에서 변수가 변경되어 무한루프 등의 상황 발생 가능
function a (){
i = 0;
};
for(var i = 0; i < 5; i++){
a();
document.write(i);
};
/* 무한루프
* for문은 외부에 있기 때문에 for문의 i는 전역변수로 선언됨
* 함수 a()는 (var가 없기 때문에) 전역변수 i의 값을 0으로 재할당함
* 계속 i = 0 상태이므로 루프가 완료되지 않음 */
전역변수를 사용해야 할 때 중복을 피하려면
-전역변수를 생성 후 나머지 변수를 해당 변수의 프로퍼티 형태 생성하거나
-변수가 필요한 경우 익명함수 내에 선언해서 전역변수를 만들지 않기
(function(){
var MYAPP = {}; /* 전역변수 하나를 생성해서 */
MYAPP.calculator = { /* 나머지 변수를 그 변수의 소속으로 넣어주거나 */
'left': null,
'right': null
}
MAPP.coordinate = {
'left': null,
'right': null
}
MYAPP.calculator.left = 10;
MYAPP.calculator.right = 20;
function sum(){
return MYAPP.calculator.left + MYAPP.calculator.right;
}
document.write(sum));
}()) /* 아예 해당 변수를 익명함수 내에 선언해서 전역변수를 만들지 않거나 */
JS는 Static Soping/Lexical Scoping (↔Dynamic Scoping)을 따르기 때문에
함수를 호출한 위치는 해당 함수가 참조하는 지역변수에 영향을 주지 않는다 [참고]
var i = 5;
function a(){
var i = 10;
b();
}
function b(){
document.write(i); // 정의될 때의 i는 var i = 5이므로
}
a(); // 5: 사용시점에 따라 영향을 받지 않는다(Lexical Scoping)
값으로서의 함수와 콜백
-JS에서 함수는 객체로 처리되므로, 다른 객체나 배열에 포함되거나, 다른 함수의 인자나 리턴값으로 전달될 수 있다.
-이런 특성을 first-class citizen or first-class object, first-class entity, first-class value라고 부른다.
-콜백(callback): 함수를 호출하는 작업을 콜백이라고 하고, 그렇게 호출한 함수를 콜백함수라고 한다.
function cal(mode){
var funcs = {
'plus': function(left, right){return left + right},
'minus': function(left, right){return left - rigth}
}
return funcs[mode];
}
console.log(cal('plus')(2,1)); // 3
/* cal('plus')는 funcs[mode]를 리턴하므로,
* cal 내부의 프로퍼티인 funcs에 mode로 입력된 'plus'를 전달하여
* funcs['plus']가 실행되고, 인자로 (2,1)이 전달된다 */
-콜백은 비동기처리에 유용하게 사용된다. 자바스크립트의 경우 Ajax를 주로 이용한다.
- 비동기처리(Asynchronous): To-do처럼, 특정 연산을 바로 진행하지 않고 모아뒀다가 여건이 될 때 처리하는 방식
클로저(Closure)
-내부함수가 외부함수의 맥락(Context: 지역변수 등) 에 접근가능한 특성
-내부함수란 함수 내에 선언된 함수를 뜻함. 이때 내부함수를 갖고 있는 함수를 외부함수라고 부름
function outter(){ // 외부함수
var word = 'hello world';
function inner(){ // 내부함수
alert(word);
}
inner();
}
outter();
/* 특정한 함수 내에서만 사용되는 함수가 있을 경우,
* 따로 떨어뜨려놓으면 군집성이 떨어지므로 내부함수 형태로 선언하기도 함 */
/* 위 함수의 경우 변수 word는 inner()의 입장에서는 전역변수이기 때문에
* word가 outter()의 지역변수이더라도 inner() 안에서 접근이 가능함 */
-특이하게 외부함수가 종료되더라도 내부함수가 외부함수의 맥락에 접근 가능한데, 이런 특성을 클로저라고 부름
function outter(){
var word = 'hello world';
return function(){
alert(word);
}
}
inner = outter();
inner(); // 'hello world'라고 정상 출력됨
-클로저를 통해 아래와 같은 (python class 스타일) 객체 구현이 가능
function bread(element){
return {
get_element: function(){ // 함수 bread는 종료되었으나
return element; // get_element는 bread의 맥락에 접근가능
},
set_element: function(_element){
element = _element // 함수 set_element는 bread의 지역변수 element를 변경함
}
}
}
console.log(cookie.get_element()); // 'Cheese'
console.log(scone.get_element()); // 'Cheese'
scone.set_element('Chocolate');
console.log(cookie.get_element()); // 'Cheese'
console.log(scone.get_element()); // 'Chocolate'
/* 외부함수인 bread는 return 시점에서 종료되었으므로
* 변수 element에 대한 접근은 메소드 get_element, set_element를 통해서만 가능하고,
* 따라서 데이터 접근 채널이 한정되므로 안전해짐 */
-클로저 관련하여 발생할 수 있는 이슈
var arr = [], arr2 = []
for(var i = 0; i < 5; i++){
arr[i] = function(){ // 이 맥락에서 i는 외부변수에서 정의된 지역변수가 아니기 때문에
console.log(i); // 이 구문 이후로도 값이 변할 수 있음
},
arr2[i] = function(id){
return function(){ // function(id)는 function()를 리턴하는 시점에서 종료되어
return id; // id는 해당 시점의 i값을 가리키는 외부변수로 고정됨
}
}(i);
}
// 만약 여기에서 i = 7;처럼 i값을 재할당한다면 arr[index]()는 7을 리턴함
for(var index in arr){
console.log(arr[index]()); // 5 5 5 5 5
console.log(arr2[index]()); // 0 1 2 3 4
}
arguments
-함수의 매개변수(parameter)를 정의하지 않고 사용하거나, 인자의 수가 정의한 것과 달라도 에러가 나지는 않음
-함수 내부에서 arguments라는 객체를 통해 인자에 접근 가능
-함수.length -> 함수 정의시에 입력한 인자의 개수
-arguments.length -> 호출시 입력받은 인자의 개수
function sum(){
var _sum = 0;
for(var i = 0; i < arguments.length; i++){
document.write(i+' : '+arguments[i]+'<br />');
_sum += arguments[i];
}
return _sum;
}
document.write('result : ' + sum(1, 2, 3, 4, 5));
function one(arg1){
console.log(
'one.length: ', one.length,
'arguments.length: ', arguments.length
);
}
one('val1', 'va12'); // one.length : 1 arguments.length: 2
함수를 호출하는 다른 방법
-함수는 function 객체의 인스턴스이므로, function의 apply 메소드로도 호출이 가능
o1 = {val1:1, val2:2, val3:3}
o2 = {v1:10, v2:50, v3:100, v4:25}
function sum(){
var _sum = 0;
for(name in this){ // 이 경우 this의 맥락은 호출할 때 정해짐
_sum += this[name];
}
return _sum;
}
console.log(sum.apply(o1)) // 6
console.log(sum.apply(o2)) // 185
/* apply 메소드의 첫번째 인자에 입력된 o1, 혹은 o2는
* var this = o1; 처럼 정의된 것과 같은 효과,
* 함수가 마치 o1.sum, o2.sum처럼 적용된 것과 같은 효과를 냄 */