JavaScript - 클로저, Scope

|
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>0905실습</title>
<script type="text/javascript">

//1. 단어 s의 가운데 글자를 반환하는 함수 (단어의 길이가 짝수라면 가운데 두 글자를 반환)

function calculate1(s) {
	var input = prompt("가운데 글자 반환함수", "글자 s를 입력해주세요.");
	if (input.length % 2 == 0){
		alert(input.substring((input.length/2)-1, (input.length/2)+1));		
	}else{
		alert(input.substring((input.length/2), (input.length/2)+1));
	}
}


//2. 배열 arr의 각 원소는 숫자 0부터 9까지로 이루어져 있습니다. 
// 배열 arr에서 연속적으로 나타나는 숫자는 하나만 남기고 전부 제거하여 리턴하는 함수
function calculate2(arr) {
	var arr = [0,1,1,2,3,4,5,7,7,9];
	var answer = [];
	var length = arr.length;
	// for문에서 arr.length 사용하면 왜곡이 되므로 
	// length 변수 받아서 쓰던가 var i in arr 로 써라.
	for(i=0; i<length; i++){ // var i in arr 로 쓰면 작동안됨.
		if(arr[i] !== arr[i+1]){
			answer.push(arr[i]);
		}
	}
	alert(answer);	
}


//3. 두 정수 a, b가 주어졌을 때 a와 b 사이에 속한 모든 정수의 합을 리턴하는 함수
function calculate3(a, b) {
	var input = prompt("a와 b 사이에 속한 모든 정수의 합을 리턴하는 함수", "a를 입력해주세요.");
	var input1 = prompt("a와 b 사이에 속한 모든 정수의 합을 리턴하는 함수", "b를 입력해주세요.");
	var arr = [input, input1];
	var answer = 0;
	if(arr[0] == arr[1]){
		answer = arr[0];
	} else {
		for(var i = arr[0]; i<=arr[1]; i++){
			answer += Number(i); // Number로 변환해야 숫자로 인식됨.
		}
		
	}
	alert(answer);
}


//4. 정수를 담고 있는 배열 arr의 평균값을 return하는 함수
function calculate4(arr) {
	var arr = [1,2,3,4,5,6,7,8,9,10];
	let result = 0; // let은 변수에 재할당이 가능
	for(let i = 0; i<arr.length; i++){
		result += arr[i];
	}
	alert(result/arr.length);
}

/* ★★★★★★★★★★★★★★★★ 클로저 ★★★★★★★★★★★★★★★ */
// 실행이 끝난 함수의 스코프를 참조할 수 있는 함수
function parent() {
  var a = 'Parent is done';
  function child() {
    console.log(a);
  }
  return child;
}
var closure = parent();
closure();

위 내부함수의 정의대로라면 parent 의 내부함수인 child() 는 외부에서 접근이 불가능하다.
하지만 return 값에 child 를 넘김으로써 외부에서도 child 를 호출할 수 있게 된다.
따라서, child() 에서 parent 의 값을 참고하고 있다면,
child() 를 밖에서 호출함으로써 parent() 의 변수에 접근이 가능하게 된다. 이것이 클로져

// 클로저 : 지역변수의 범위를 벗어나서 사용가능하게 해줌.
function test(name){
	var output = 'Hello' + name + '...!'; //private 멤버변수
	//내부 메소드에서는 private 멤버변수 사용가능
	return function(){ // 내부메소드를 통해서 메모리 할당됨
		alert(output);
	};
}

// 지역변수 output은 test()가 호출될때 생성(메소드가 끝났음에도 사용가능)
test('JavaScript')(); // 결과값 : HelloJavaScript...! ( 함수가 리턴되므로)

// 전역변수는 var문을 생략해도 되지만 지역변수는 반드시 var문을 써야한다.
scope = "global";
function checkscope(){
	scope = "local"; // 전역변수를 바꾸어버림
	myscope = "local"; // 새로운 전역변수 생성
}

//클로저를 사용하여 Counter 만들기
//아래소스는 외부에서 counter값을 변경할 수 있는 문제 존재
uniqueInteger.counter = 0;
function uniqueInteger(){
	return uniqueInteger.counter++;
}	
//클로저를 이용해 해결한 소스
var uniqueID = (function(){
	var id = 0; // private 속성을 가진다. 함수만이 접근가능
	// uniqueID에 저장되는 것은 값이 아닌 중첩된 함수이다.
	return function(){return id++;} // 값을 증가시켜서 반환
})();

for(var i=0; i<3; i++){
	alert(uniqueID()); // 결과값 : 0, 1, 2 
}

// 클로저 : 중첩함수
function makefunc(x){
	return function(){return x;}
}
	//리턴된 함수를 배열요소에 저장
	var a = [makefunc('x'), makefunc('y'), makefunc('z')];
	alert(a[0]()); // x출력
	alert(a[1]()); // y출력
	alert(a[2]()); // z출력

// 클로저를 사용한 private 멤버변수
function makeProperty(o, name, predicate){ // o는 object
		var value; //private 변수
		//getter메소드
		o['get'+ name] = function(){return value;};
		//setter메소드
		o['set'+ name] = function(v){
			if(predicate && !predicate(v)) // 메소드가 존재하고 false를 리턴하면
				throw 'set'+name+': invalid value' + v;
			else
				value = v;
		}
	}
	var o = {};
	//Name Property 추가, set은 string만 가능
	makeProperty(o, 'Name', function(x){return typeof x == 'string';});
	o.setName('Frank'); // 값 설정
	console.log(o.getName()); // 값 출력 : Frank 찍힘.

// 클로저 문제
var counter = (function(){
		var a = 0;
		return {
		inc : function(){return a++;}, // 메소드 선언시 ':' 을 사용한다.
		dec : function(){return a--;},
		val : function(){return a;}
		};
	})();
counter.inc(); // 1증가
counter.inc(); // 1증가
counter.dec(); // 1감소
console.log(counter.val()); // 1출력

/* Scope 예제 */
// 함수단위 유효범위
function scopeTest(){
	var a = 0;
	if(true){
		var b = 0;
		for(var c=0; c<5; c++){
			console.log("c1= " + c); // 0, 1, 2, 3, 4, 5
		}
		console.log("c2 = " + c); // 5
	}
	console.log("b = " + b); // 결과값 0. block외부에서 b에 접근가능 => 블록단위 유효범위가 없다.
}
scopeTest();

// 변수명이 동일할 때는 가장 가까운 범위의 변수를 참조한다.
var scope = 10;
function scopeExam(){
	var scope = 20;
	console.log("scope = " + scope);
}
scopeExam(); // 20. 20이 더 가까움

// var 키워드를 생략하면 전역변수로 설정된다.
function scopeExam(){
	scope = 20; // var가 없으므로 전역변수화 됨.
	console.log("scope = " + scope);
}
function scopeExam2(){
	console.log("scope = " + scope);
}
scopeExam(); // 20
scopeExam2(); // 20

// 변수 선언문을 끌어 올린다.
function hoistingExam(){
	console.log("value = " + value); // 아래 선언된 변수를 사용, undefined 출력
	var value = 10;
	console.log("value = " + value); // 10출력
}
hoistingExam();
	

</script>
</head>
<body>

	<input type="button" value="문제1번" onClick="calculate1()"><br><br>
	<input type="button" value="문제2번" onClick="calculate2()"><br><br>
	<input type="button" value="문제3번" onClick="calculate3()"><br><br>
	<input type="button" value="문제4번" onClick="calculate4()"><br><br>
	
	<input type="button" value="실습" onClick="makeProperty()"><br><br>

</body>
</html>
And