생활코딩-자바스크립트-함수지향
위 강의를 듣고 정리한 글입니다.
유효범위(Scope)는 변수의 수명을 의미한다. 아래 예제 결과는 global이다.
var vscope = 'global';
function fscope(){
alert(vscope);
}
fscope();
함수 밖에서 변수를 선언하면 그 변수는 전역변수가 된다. (전역변수: 에플리케이션 전역에서 접근이 가능한 변수)
어떤 함수 안에서도 그 변수에 접근 할 수 있다.
따라서 함수 fscope 내에서 vscope를 호출 했을 때 함수 밖에서 선언된 vscope의 값 global이 반환된 것이다.
아래 예제 결과는 '함수안 local'과 '함수밖 global'이 출력된다.
var vscope = 'global';
function fscope(){
var vscope = 'local';
alert('함수안 '+vscope); //local 출력
}
fscope();
alert('함수밖 '+vscope); //global 출력
즉 지역변수의 유효범위는 함수 안이고, 전역변수의 유효범위는 에플리케이션 전역인데, 같은 이름의 지역변수와 전역변수가 동시에 정의되어 있다면 지역변수가 우선한다는 것을 알 수 있다.
아래 예제 결과는 모두 local이다.
var vscope = 'global';
function fscope(){
vscope = 'local';
alert('함수안'+vscope);
}
fscope();
alert('함수밖'+vscope);
var 뒤에 오는 키워드는 로컬 변수가 됨. var를 빼면 로컬 변수가 아니라 전역 변수가됨.
전역변수는 사용하지 않는 것이 좋다.
첫 번째, 값이 변경 될 수 있기때문에 함수의 동작도 달라지게 된다.
두 번째, 함수를 다른 에플리케이션에 이식하는데도 어려움이 생긴다.
변수를 선언할 때는 꼭 var을 붙이는 것을 습관화해야 한다.
전역변수는 명확히 사용해야하는 이유가 있을때만 사용한다.
변수 i를 지역변수로 사용했을 때와 전역변수로 사용했을 때의 차이점
지역변수의 사용
function a (){
var i = 0;
}
for(var i = 0; i < 5; i++){
a();
document.write(i);
}
01234 //출력 결과
전역변수의 사용
본 예제는 무한반복을 발생
7
function a (){
i = 0;
}
for(i = 0; i < 5; i++){
a();
document.write(i);
}
불가피하게 전역변수를 사용해야 하는 경우는 하나의 객체를 전역변수로 만들고 객체의 속성으로 변수를 관리하는 방법을 사용한다.
MYAPP = {} // 전역변수 하나 생성
MYAPP.calculator = { //전역변수의 소속
'left' : null,
'right' : null
}
MYAPP.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());
전역변수를 사용하고 싶지 않다면 아래와 같이 익명함수를 호출함으로서 이러한 목적을 달성할 수 있다.
(function(){ // 함수로 전체를 감싸기
var MYAPP = {} //함수내의 지역 변수가 됨
MYAPP.calculator = {
'left' : null,
'right' : null
}
MYAPP.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());
}())
위와 같은 방법은 자바스크립트에서 로직을 모듈화하는 일반적인 방법이다.
자바스크립트는 함수에 대한 유효범위만을 제공한다.
많은 언어들이 블록(대체로 {,})에 대한 유효범위를 제공하는 것과 다른 점이다.
아래 예제의 결과는 coding everybody이다.
for(var i = 0; i < 1; i++){
var name = 'coding everybody';
}
alert(name);
자바에서는 아래의 코드는 허용되지 않는다. name은 지역변수로 for 문 안에서 선언 되었는데 이를 for문 밖에서 호출하고 있기 때문이다.
for(int i = 0; i < 10; i++){
String name = "egoing";
}
System.out.println(name);
자바스크립트의 지역변수는 함수에서만 유효하다.
자바스크립트는 함수가 선언된 시점에서의 유효범위를 갖는다.
이러한 유효범위의 방식을 정적 유효범위(static scoping), 혹은 렉시컬(lexical scoping)이라고 한다.
var i = 5; //전역
function a(){ //
var i = 10;
b();
}
function b(){
document.write(i);
}
a();
출력값 : 5
함수 b가 호출된 상황에서 a의 지역변수를 탐색하는 것이 아닌 전역변수 i=5를 사용하게 됨
사용될때가 아니라 정의 될때의 변수를 사용하게 됨 (정적)
JavaScript에서는 함수도 객체다. (값이다.)
JavaScript의 함수가 다른 언어의 함수와 다른 점은 함수가 값이 될 수 있다는 점이다.
function a(){}
var a = function(){}
위의 예제에서 함수 a는 변수 a에 담겨진 값이다.
또한 함수는 객체의 값으로 포함될 수 있다.
객체 안에서 변수 역할을 하고 있는것 = 속성 (property)
이렇게 객체의 속성 값으로 담겨진 함수를 메소드(method)라고 부른다.
a = { //객체 안에 b라는 키의 값이 function
b:function(){
} //method
};
함수는 값이기 때문에 다른 함수의 인자로 전달 될수도 있다.
function cal(func, num){
return func(num)
}
function increase(num){
return num+1
}
function decrease(num){
return num-1
}
alert(cal(increase, 1));
alert(cal(decrease, 1));
10행을 실행하면 함수 increase와 값 1이 함수 cal의 인자로 전달된다.
함수 cal은 첫번째 인자로 전달된 increase를 실행하는데 이 때 두번째 인자의 값이 1을 인자로 전달한다.
함수 increase은 계산된 결과를 리턴하고 cal은 다시 그 값을 리턴한다.
함수는 함수의 리턴 값으로도 사용할 수 있다.
function cal(mode){
var funcs = {
'plus' : function(left, right){return left + right},
'minus' : function(left, right){return left - right}
}
return funcs[mode];
}
alert(cal('plus')(2,1));
alert(cal('minus')(2,1));
당연히 배열의 값으로도 사용할 수 있다.
var process = [
function(input){ return input + 10;},
function(input){ return input * input;},
function(input){ return input / 2;}
];
var input = 1;
for(var i = 0; i < process.length; i++){
input = process[i](input);
}
alert(input);
처리의 위임
값으로 사용될 수 있는 특성을 이용하면 함수의 인자로 함수로 전달할 수 있다.
값으로 전달된 함수는 호출될 수 있기 때문에 이를 이용하면 함수의 동작을 완전히 바꿀 수 있다.
인자로 전달된 함수 sortNumber의 구현에 따라서 sort의 동작방법이 완전히 바뀌게 된다.
function sortNumber(a,b){
// 위의 예제와 비교해서 a와 b의 순서를 바꾸면 정렬순서가 반대가 된다.
return b-a;
}
var numbers = [20, 10, 9,8,7,6,5,4,3,2,1];
alert(numbers.sort(sortNumber)); // .앞에 있는 numbers는 배열객체/ sort는 객체에 속해있기때문에 method라고함(내장,빌트인 객체)
//array, [20,10,9,8,7,6,5,4,3,2,1]
콜백은 비동기처리에서도 유용하게 사용된다.
한 작업이 완료된 후에 처리해야 할 일을 콜백으로 지정하면 미리 등록한 작업을 실행하도록 할 수 있다.
다음 코드는 일반적인 환경에서는 작동하지 않고 서버 환경에서만 동작한다.
동기적(Synchronous)
어떤 작업을 요청했을 때 그 작업이 종료될때 까지 기다린 후 다음 작업을 수행하는 방식
비동기적(Asynchronous)
어떤 작업을 요청했을 때 그 작업이 종료될때 까지 기다리지 않고 다른 작업을 하고 있다가, 요청했던 작업이 종료되면 그에 대한 추가 작업을 수행하는 방식
datasource.json.js
{"title":"JavaScript","author":"egoing"}
demo1.html
<!DOCTYPE html>
<html>
<head>
<script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
</head>
<body>
<script type="text/javascript">
$.get('./datasource.json.js', function(result){
console.log(result);
}, 'json');
</script>
</body>
</html>
클로저(closure): 내부함수가 외부함수의 맥락(context)에 접근할 수 있는 것
내부함수
자바스크립트는 함수 안에서 또 다른 함수를 선언할 수 있다.
아래의 예제의 결과는 경고창에 coding everybody가 출력될 것이다.
function outter(){
function inner(){
var title = 'coding everybody';
alert(title);
}
inner();
}
outter();
함수 inner를 내부 함수라고 한다.
내부함수는 외부함수의 지역변수에 접근할 수 있다.
아래의 예제 결과는 coding everybody이다.
function outter(){
var title = 'coding everybody';
function inner(){
alert(title);
}
inner();
}
outter();
위의 예제는 내부함수 inner에서 title을 호출(4행)했을 때 외부함수인 outter의 지역변수에 접근할 수 있음을 보여준다.
클로저(closure)는 내부함수와 밀접한 관계를 가지고 있는 주제다.
내부함수는 외부함수의 지역변수에 접근 할 수 있는데 외부함수의 실행이 끝나서 외부함수가 소멸된 이후에도 내부함수가 외부함수의 변수에 접근 할 수 있다.
이러한 메커니즘을 클로저라고 한다.
아래 예제 결과는 경고창으로 coding everybody를 출력할 것이다.
function outter(){
var title = 'coding everybody';
return function(){
alert(title);
}
}
inner = outter();
inner();
실행이 8행으로 넘어오면 outter 함수는 실행이 끝났기 때문에 이 함수의 지역변수는 소멸되는 것이 자연스럽다.
하지만 8행에서 함수 inner를 실행했을 때 coding everybody가 출력된 것은 외부함수의 지역변수 title이 소멸되지 않았다는 것을 의미한다.
클로저란 내부함수가 외부함수의 지역변수에 접근 할 수 있고, 외부함수는 외부함수의 지역변수를 사용하는 내부함수가 소멸될 때까지 소멸되지 않는 특성을 의미한다.
아래 예제는 클로저를 이용해서 영화의 제목을 저장하고 있는 객체를 정의하고 있다. 실행결과는 Ghost in the shell -> Matrix -> 공각기동대 -> Matrix 이다.
function factory_movie(title){ // title은 내부함수의 get_title/set_title로만 접근 가능
return {
get_title : function (){
return title;// title : 외부함수의 지역변수
},
set_title : function(_title){
title = _title
}
}
}
ghost = factory_movie('Ghost in the shell');
matrix = factory_movie('Matrix');
alert(ghost.get_title());
alert(matrix.get_title());
ghost.set_title('공각기동대');
alert(ghost.get_title());
alert(matrix.get_title());
위의 예제를 통해서 알 수 있는 것들을 정리해보면 아래와 같다.
클로저는 객체의 메소드에서도 사용할 수 있다. 위의 예제는 함수의 리턴값으로 객체를 반환하고 있다. 이 객체는 메소드 get_title과 set_title을 가지고 있다. 이 메소드들은 외부함수인 factory_movie의 인자값으로 전달된 지역변수 title을 사용하고 있다.
동일한 외부함수 안에서 만들어진 내부함수나 메소드는 외부함수의 지역변수를 공유한다. 17행에서 실행된 set_title은 외부함수 factory_movie의 지역변수 title의 값을 '공각기동대'로 변경했다. 19행에서 ghost.get_title();의 값이 '공각기동대'인 것은 set_title와 get_title 함수가 title의 값을 공유하고 있다는 의미다.
그런데 똑같은 외부함수 factory_movie를 공유하고 있는 ghost와 matrix의 get_title의 결과는 서로 각각 다르다. 그것은 외부함수가 실행될 때마다 새로운 지역변수를 포함하는 클로저가 생성되기 때문에 ghost와 matrix는 서로 완전히 독립된 객체가 된다.
factory_movie의 지역변수 title은 2행에서 정의된 객체의 메소드에서만 접근 할 수 있는 값이다. 이 말은 title의 값을 읽고 수정 할 수 있는 것은 factory_movie 메소드를 통해서 만들어진 객체 뿐이라는 의미다. JavaScript는 기본적으로 Private한 속성을 지원하지 않는데, 클로저의 이러한 특성을 이용해서 Private한 속성을 사용할 수 있게된다.
(*참고 Private 속성은 객체의 외부에서는 접근 할 수 없는 외부에 감춰진 속성이나 메소드를 의미한다. 이를 통해서 객체의 내부에서만 사용해야 하는 값이 노출됨으로서 생길 수 있는 오류를 줄일 수 있다. 자바와 같은 언어에서는 이러한 특성을 언어 문법 차원에서 지원하고 있다.)
var arr = []
for(var i = 0; i < 5; i++){
arr[i] = function(){
return i;
}
}
for(var index in arr) {
console.log(arr[index]());
}
함수가 함수 외부의 컨텍스트에 접근할 수 있을 것으로 기대하겠지만 위의 결과는 아래와 같다.
5
5
5
5
5
위의 코드는 아래와 같이 변경해야 한다.
var arr = []
for(var i = 0; i < 5; i++){
arr[i] = function(id) {
return function(){
return id;
}
}(i);
}
for(var index in arr) {
console.log(arr[index]());
}
결과는 아래와 같다.
0
1
2
3
4
함수에는 arguments라는 변수에 담긴 숨겨진 유사 배열이 있다.
이 배열에는 함수를 호출할 때 입력한 인자가 담겨있다.
아래 예제 결과는 10이다.
function sum(){
var i, _sum = 0;
for(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));
// 자바스크립트는 인자 수가 안맞아도 오류가 발생하지 않음
arguments는 함수안에서 사용할 수 있도록 그 이름이나 특성이 약속되어 있는 일종의 배열이다.
*arguments는 사실 배열은 아니다. 실제로는 arguments 객체의 인스턴스다.
매개변수와 관련된 두가지 수가 있다.
arguments.length : 함수로 전달된 실제 인자의 수를 의미
함수.length : 함수에 정의된 인자의 수를 의미한다. 아래의 코드를 보자.
function zero(){
console.log(
'zero.length', zero.length,
'arguments', arguments.length
);
}
function one(arg1){
console.log(
'one.length', one.length,
'arguments', arguments.length
);
}
function two(arg1, arg2){
console.log(
'two.length', two.length,
'arguments', arguments.length
);
}
zero(); // zero.length 0 arguments 0
one('val1', 'val2'); // one.length 1 arguments 2
two('val1'); // two.length 2 arguments 1
function func(){
}
func();
객체는 속성을 가지고 있다. 속성의 값이 저장되어있으면 속성(property), 속성이 함수가 들어있으면 method.
함수를 객체라고 했다. 위의 예제에서 함수 func는 Function이라는 객체의 인스턴스다.
따라서 func는 객체 Function이 가지고 있는 메소드들을 상속하고 있다.
지금 이야기하려는 메소드는 Function.apply과 Function.call이다.
이 메소드들을 이용해서 함수를 호출하면 결과는 3이다.
function sum(arg1, arg2){
return arg1+arg2;
}
alert(sum.apply(null, [1,2]))
함수 sum은 Function 객체의 인스턴스다.
그렇기 때문에 객체 Function 의 메소드 apply를 호출 할 수 있다.
apply 메소드 두개의 인자 :
첫번째 인자는 함수(sum)가 실행될 맥락이다.
두번째 인자는 배열이다. 이 배열의 담겨있는 원소가 함수(sum)의 인자로 순차적으로 대입된다.
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){
_sum += this[name];
}
return _sum;
}
alert(sum.apply(o1)) // 6
alert(sum.apply(o2)) // 185
우선 두개의 객체를 만들었다. o1는 3개의 속성을 가지고 있다. 각각의 이름은 val1, val2,val3이다. o2는 4개의 속성을 가지고 있고 o1과는 다른 속성 이름을 가지고 있고 속성의 수도 다르다.
그 다음엔 함수 sum을 만들었다. 이 함수는 객체의 속성을 열거할 때 사용하는 for in 문을 이용해서 객체 자신(this)의 값을 열거한 후에 각 속성의 값을 지역변수 _sum에 저장한 후에 이를 리턴하고 있다.
객체 Function의 메소드 apply의 첫번째 인자는 함수가 실행될 맥락이다.
이렇게 생각하자. sum.apply(o1)은 함수 sum을 객체 o1의 메소드로 만들고 sum을 호출한 후에 sum을 삭제한다.
아래와 비슷하다. (실행결과가 조금 다를 것이다. 그것은 함수 for in문으로 객체 o1의 값을 열거할 때 함수 sum도 포함되기 때문이다.)
o1.sum = sum;
alert(o1.sum());
delete o1.sum();
함수 sum에서 this의 값이 전역객체가 아니라 o1이 된다는 의미다.
일반적인 객체지향 언어에서는 하나의 객체에 소속된 함수는 그 객체의 소유물이 된다.
JavaScript에서 함수는 독립적인 객체로서 존재하고, apply나 call 메소드를 통해서 다른 객체의 소유물인 것처럼 실행할 수 있다
만약 apply의 첫번째 인자로 null을 전달하면 apply가 실행된 함수 인스턴스는 전역객체(브라우저에서는 window)를 맥락으로 실행되게 된다.