JavaScript/Default2010. 7. 12. 13:34

새삼스래 왠 재입문? JavaScriptthe world's most misunderstood programming language (세계에서 가장 잘못 이해되고있는 프로그래밍 언어)에 소개된 것과 같이 의미있는 비판을 받고 있기 때문입니다. 장난감 정도로 비웃음을 사고 있지만, 그 속기쉬운 이면에는 몇가지 강력한 언어 요소를 내재하고 있습니다. 2005년에는 이 기술에 대한 깊은 이해가 웹 개발자 누구에게 있어서도 중요한 능력이 된다는 것을 보여주는 많은 고급 JavaScript 응용 프로그램들이 나타났습니다.

언어의 역사에서 시작하는 것이 이 이야기를 이해하는데 도움이 됩니다. JavaScript는 1995년 Netscape의 엔지니어 Brendan Eich에 의해 만들어졌고, 이른 1996년에 Netscape 2와 함께 처음 릴리즈 된것입니다. 원래 LiveScript로 불리워지기로 되어있었지만, Sun Microsystem의 Java 언어의 성공에 편승해보려고 두 언어 사이의 공통점이 매우 적음에도 불구하고, 이런 불행이 예견된 마케팅 결정에 따라 이름이 바뀌게 됩니다. 이 사실은 사상 유래가 없는 혼란의 근원이 되어버립니다.

Microsoft는 몇달 후 IE3와 함께 JavaScript와 대부분이 호환되는 JScript로 불리워지는 언어를 발표합니다. Netscape는 1997년에 ECMAScript 표준의 첫번째 판이 되는 JavaScript를 유럽 표준화 단체인 Ecma International에 보냅니다. 표준은 1999년에 ECMAScript edition 3에 따라 큰 규모의 개정을 거친 후, 현재 4번째 판이 제정 준비 중에 있지만 유래없이 아주 안정된 상태로 계속 유지되고 있습니다.

이 안정 상태는 다양한 코드 구현을 하는데 충분한 시간이기 때문에 개발자들에게는 더없이 좋은 소식입니다. 저는 대부분 이 3판에 집중하려고 합니다. 친숙함을 위하여 JavaScript 전반에 걸쳐 여기서 사용된 용어를 준수하겠습니다.

대부분의 프로그래밍 언어와는 달리, JavaScript 언어는 입출력 개념이 없습니다. 호스트 환경 아래에서 스크립트 언어로서 동작하도록 디자인 되어있고, 따라서 외부 세계와 통신하기위해 호스트 환경이 제공하는 메커니즘에 의존합니다. 대부분의 경우 일반적인 호스트 환경은 브라우저이지만 JavaScript 인터프리터는 Adobe Acrobat, Photoshop, Yahoo! 위젯 엔진, 등의 제품에서도 발견할 수 있습니다.

개요

어떤 언어에서라도 기초가 되는 부분인 타입을 살펴보는 것부터 시작해봅시다. JavaScript 프로그램은 값을 다루고 해당 값은 모두 타입을 가지고 있습니다. JavaScript의 타입은 다음과 같습니다:

... 오, 그리고 약간 특별한 타입인 정의되지않음(Undefined) 와 널(Null) 이 있습니다. 또한 객체의 특별한 종류인 배열(Array) 객체. 그리고 자유롭게 사용할 수 있는 날짜(Date) 객체정규식(RegExp) 객체가 있습니다. 그리고 기술적으로 정확히 말해 함수(Function)는 단지 객체의 특별한 타입으로 취급됩니다. 따라서 타입 구조도를 정리해보면 다음과 같이 됩니다:

  • 수 (Number)
  • 문자열 (String)
  • 부울 (Boolean)
  • 객체 (Object)
    • 함수 (Function)
    • 배열 (Array)
    • 날짜 (Date)
    • 정규식 (RegExp)
  • 널 (Null)
  • 정의되지않음 (Undefined)

그리고 또 몇 가지 오류 타입이 내장되어 있습니다. 그렇지만 처음 구조도를 기억하고만 있으면 다른 것들도 아주 쉽게 이해할 수 있을 것입니다.

수 (Numbers)

설계 명세서에 의하면 JavaScript에서 수는 "이중정밀도 64비트 형식 IEEE 754 값"으로 정의됩니다. 이것은 몇가지 흥미로운 결과를 가져옵니다. JavaScript에는 정수와 같은 것이 존재하지 않으므로, C 나 Java 에서 수학 계산을 한 경험이 있다면 산술할 때 약간 조심할 필요가 있습니다. 다음과 같은 경우를 주의해야 합니다.

0.1 + 0.2 = 0.30000000000000004

덧셈, 뺄셈, 계수 (또는 나머지) 연산을 포함하는 표준 산술 연산자가 지원됩니다. 또한 앞에서 언급하는 것을 깜박 잊은 고급 수학 함수와 상수를 다루기 위한 수학(Math)으로 불리워지는 내장 객체가 있습니다.

Math.sin(3.5);
d = Math.PI * r * r;

내장 parseInt() 함수를 사용하여 문자열을 정수로 변환할 수 있습니다. 이는 다음과 같이 옵션으로 주어지는 두번째 매개변수를 밑으로 하여 수행할 수 있습니다.

parseInt("123", 10)
123
parseInt("010", 10)
10

밑을 주지 않으면, 다음과 같이 예상치 못한 결과를 얻을 수 있습니다.

parseInt("010")
8

이 같은 결과는 parseInt 함수가 0으로 시작되는 문자열을 8진수로 취급하기 때문에 발생합니다.

만약 이진수를 정수로 변환하고 싶다면, 밑을 바꾸기만하면 됩니다.

parseInt("11",2)
3

문자열이 수가 아닌 경우 NaN ("Not a Number" (수가 아님)을 줄인 약자)로 불리워지는 특별한 값을 돌려줍니다:

parseInt("hello", 10)
NaN

NaN 는 독성을 가지고 있습니다: 어떤 수학 연산의 입력값으로써 주어지면 그 결과는 역시 NaN가 되기 때문입니다:

NaN + 5
NaN

내장 isNaN() 함수를 사용해서 NaN 인지 여부를 검사할 수 있습니다:

isNaN(NaN)
true

JavaScript는 또 특별한 값 Infinity-Infinity를 가지고 있습니다: 

1 / 0
Infinity
-1 / 0
-Infinity

문자열 (Strings)

JavaScript에서 문자열은 문자 하나하나가 연결되어 만들어진 것입니다. 좀 더 정확히 말하자면, 각각이 16비트로 표현된 유니코드 문자들이 길게 이어져있는 것입니다. 이는 국제화(i18n, internationalization) 하려하는 누구에게라도 환영받을만한 소식입니다.

한 개의 문자를 나타내려면 길이가 1인 문자열을 사용하면 됩니다.

문자열의 길이를 알고싶다면, 해당 문자열의 length 속성(해당 객체가 소유하고 있는 성질을 나타내는 값)에 접근하면 됩니다.

"hello".length
5
-1 / 0
-Infinity

우리의 첫 JavaScript 객체입니다! 문자열도 역시 객체로 취급된다고 언급했던적이 있죠? 다음과 같이 메소드까지 있는 확실한 녀석입니다.

"hello".charAt(0)
h
"hello, world".replace("hello", "goodbye")
goodbye, world
"hello".toUpperCase()
HELLO

이외의 타입들

JavaScript는 의도적으로 값이 없음을 가리키는 '객체' 타입의 객체인 null과 초기화되지 않은 값 — 아직 어떤 값도 주어지않은(할당되지않은) 변수임을 가리키는 '정의되지 않음' 타입의 객체인 undefined로 구분됩니다. 값에 대해서 나중에 언급할 것이지만 JavaScript에서 변수에 값을 주지않고 선언하는 것이 가능합니다. 이럴 경우, 변수의 타입은 undefined이 되는 것입니다.

JavaScript는 truefalse 값 (둘은 모두 키워드로 예약되어있는 값)을 가질 수 있는 부울 타입을 가지고 있습니다. 다음과 같은 규칙에 따라 어떤 임의의 값을 부울값으로 변환할 수 있습니다.

false, 0, 빈 문자열 (""), 수가 아님을 뜻하는 NaN, null, 와 undefined은 모두 false가 됩니다.

다른 모든 값은 true가 됩니다.

이 변환은 Boolean() 함수를 써서 명시적으로 이 작업을 수행하실 수 있습니다.

Boolean("")
false
Boolean(234)
true

하지만 반드시 이렇게 할 필요는 거의 없습니다. JavaScript는 이러한 변환 작업을 if 문 (아래를 보세요)과 같이 부울값이 필요한 경우를 만나게되면 자동으로 사용자가 모르는 사이에 처리해버리기 때문입니다. 이러한 이유로 인해 우리는 가끔 부울 타입으로 변환되었을 때, truefalse이 됨을 의미하는 값들을 각각 "참 값"과 "거짓 값"으로 부를 것입니다. 또는 각각 "참으로 취급되다"와 "거짓으로 취급되다"라는 식으로 불릴 수도 있습니다.

부울 연산자는 && (논리적 와, 그리고), || (논리적 또는), 그리고 ! (논리적 부정)이 지원됩니다. 아래에서 다시 언급하겠습니다.

변수 (Variables)

JavaScript에서 새로운 변수는 var 키워드로 선언됩니다.

var a;
var name = "simon";

만약 변수에 아무런 값을 주지 않고 선언하면 해당 변수의 타입은 undefined가 됩니다.

JavaScript에는 블록 유효 범위가 따로 없습니다. 여기에 대한 것은 블록 문장에서 참고바랍니다.

연산자 (Operators)

JavaScript의 산술 연산자로는 +, -, *, /, %(나머지 연산자)가 있습니다. 값은 = 연산자로 할당할 수 있고, +=-=처럼 다른 연산자를 같이사용해서 할당할 수 있습니다. 이렇게 쓰인 연산자는 x = x 연산자 y와 같은 결과를 나타냅니다.

x += 5
x = x + 5

++-- 를 각각 점진적인 증가와 감소에 사용할 수 있습니다. 이들은 또한 전처리 또는 후처리 연산자로 사용될 수 있습니다.

+ 연산자는 문자열 이어붙이기도 합니다.

> "hello" + " world"
hello world

문자열에 어떤 수 (또는 다른 값)를 더하면 일단 모두 문자열로 바뀌게 됩니다. 다음 예를 보시면 무슨 말씀인지 아실 수 있을겁니다.

> "3" + 4 + 5
345
> 3 + 4 + "5"
75

빈 문자열에 어떤 값을 더하는 것은 해당 값을 문자열로 바꾸는 요령입니다.

JavaScript에서 비교<, >, <=>= 를 통해 가능합니다. 이 연산자들은 문자열과 수 양쪽 모두에서 동작합니다. 상동은 약간 직관성이 떨어지는데 이중 등호 (==) 연산자는 서로 다른 타입을 줄 경우 타입 강제 변환을 수행하기 때문에 다음과 같이 때때로 기대하지 않은 결과를 내보내기 때문입니다.

> "dog" == "dog"
true
> 1 == true
true

타입 강제 변환을 하지 않게 하려면, 삼중 등호 연산자 (===)를 사용해야합니다.

> 1 === true
false
> true === true
true

이와 비슷하게 !=!== 연산자가 있습니다.

JavaScript는 값을 비트로 취급하는 연산자도 가지고 있습니다. 사용하고 싶을 때 언제라도 사용할 수 있도록 말이죠.

제어 구조

JavaScript는 C 계열의 다른 언어들과 비슷한 제어 구조를 가지고 있습니다. 조건문은 ifelse를 지원하는데, 원하시는대로 얼마든지 중첩 시켜서 사용할 수 있습니다.

var name = "kittens";
if (name == "puppies") {
 name += "!";
} else if (name == "kittens") {
 name += "!!";
} else {
 name = "!" + name;
}
name == "kittens!!"

JavaScript는 while 반복문과 do-while 반복문도 사용할 수 있습니다. 첫번째 것은 단순 반복에 유용하게 사용할 수 있고, 두번째 것은 반복문이 반드시 적어도 한번이상 실행 되도록 하고 싶을 때 사용할 수 있습니다.

while (true) {
 // an infinite loop!
}
do {
 var input = get_input();
} while (inputIsNotValid(input))

JavaScript의 for 반복문은 C 와 Java의 그것과 같습니다. 말하자면, 반복문에 필요한 제어 정보를 한줄에 표현할 수 있다는 이야기지요.

for (var i = 0; i < 5; i++) {
 // Will execute 5 times
}

&&|| 연산자는 첫번째 식을 평가한 결과에 따라서 두번째 식을 평가를 실행하는 단축평가(short-circuit) 논리를 사용합니다. 이는 다음과 같이 객체에 접근하기 전에 null 객체인지, 아닌지를 검사하는데 유용하게 사용될 수 있습니다.

var name = o && o.getName();

또는 기본 값 설정을 위해서 다음과 같이 이 성질을 사용할 수 있습니다.

var name = otherName || "default";

JavaScript는 한줄로 조건문을 쓸 수 있게 해주는 삼중 연산자도 가지고 있습니다.

var allowed = (age > 18) ? "yes" : "no";

스위치 문은 숫자나 문자열을 기반으로 다중 분기되는 문장을 작성하는데 사용될 수 있습니다.

var allowed = (age > 18) ? "yes" : "no";<SPAN id=tx_marker_caret></SPAN>
Posted by Jake Kim
JavaScript/Default2010. 6. 8. 14:15
2010.06.08 처음 작성 후, 리터럴 설명이 필요 할 듯 하여 내용 수정합니다.
2010.06.15 리터럴 내용 약간 추가함....

제목 그대로 함수의 값을 넘길 때 객체를 만들어 넘긴다는 말입니다.
이런 방법은 이미 많은 Javascript의 Framework에서 사용되고 있는 방법입니다. 사실 특이할 것도 없습니다.
그리고 파라메터 변수를 객체로 넘긴다고 했는데... 정확히는 리터럴 형식으로 사용한다는 표현이 더 정확 할 것입니다.

일단 예제를 보면 아래와 같습니다.

function tTest1(param1,options){
	var print = "param1 : " + param1;
	print += "ZIP1 : " + options.zip1;
	print += "ZIP2 : " + options.zip2;
	print += "ADDRESS : " + options.address;
	print += "CNT : " + options.cnt;
    alert(print);
}
함수는 이렇게 만들고 이 함수에 값을 넘길 때 options에 객체를 넘기면 됩니다. 물론 options에 들어갈 properties는 알려 줘야 겠죠.(참고로 일반적인 Framework의 함수에 값을 넘길 때 이런 방법을 사용합니다. 그리고 저 options에 해당되는 명을 API로 알려줍니다.)

위와 같이 함수를 만들었다면 함수 호출을 아래와 같이 합니다.
fTest1(“값”,{
    zip1:’100’,
    zip2:’200’,
    address:’우리집’,
    cnt:3
});

어디서 많이 보던 방법이죠? var val = {x:1, x:2}; 바로 리터럴 형식입니다.
리터럴에 관해서는 간단히 언급 하고 넘어 가려고 했는데.... 이왕 언급이 되었으니 한번 집고 넘어가도록 하겠습니다.
2010.06.15 원래 여기 까지 언급 안하려고 했는데 ㅠ.ㅠ 쓰다보니 점점 늘어나네요.

http://www.terms.co.kr/literal.htmliteral ;

리터럴

리터럴이란, 컴파일시 프로그램 내에 정의되어 있는 그대로 정확히 해석되어야 할 값을 의미한다. 이에 비해, 변수란 프로그램의 실행 중에 상황에 따라 다른 값들을 표현할 수 있으며, 상수는 프로그램 실행 중 늘 같은 값을 표현한다. 그러나 리터럴은 명칭이 아니라, 값 그 자체이다. 예를 들어 아래의 수식에서 "x"는 변수이며 "7"은 리터럴이다.

x = 7

리터럴은 숫자 뿐 아니라, 문자 또는 문자열일 수 있다.

용어상으론 이렇습니다.
일단 현재 작성하고 있는 내용이 자바스크립트이니 자바스크립트의 리터럴 형식에 대해 몇자 언급 하겠습니다. 사실 리터럴 형식에 대해 작성 하자면 그 내용 자체가 이 글과 성격이 맞지 않기 때문에 간단히 자바스크립의 리터럴 형식으로 설명하겠습니다.

자바스크립트에서 일반적으로 통용되고 있는 형식은 총 3가지 입니다.
배열 리터럴, 객체 리터럴, 혼합 리터럴 입니다.

소스 예제를 보여드리자면....
1. 배열 리터럴 형식

// 자바스크립트 배열 Array 생성자 이용
var aValues = new Array("string" , 24 , true , null);
// JSON 배열 표현식
var aNames = ["Benjamin" , "Michael" , "Scott"];

alert(aNames[0]); // Benjamin 출력
alert(aNames[1]); // Michael 출력
alert(aNames[2]); // Scott 출력

// 여러 가지 데이터 형식 저장
var aValues = ["string" , 24 , true , null ];
2. 객체 리터럴
// 자바스크립트 객체 Object 생성자 이용
var oCar = new Object();
oCar.color = "red";
oCar.doors = 4;
oCar.paidFor = true;

// JSON 객체 표현식
var oCar = {
  "color" : "red",
  "doors" : 4,
  "paidFor" : true
};

alert(oCar.color); // "red" 출력
alert(oCar.doors); // "4" 출력
alert(oCar.paidFor); // "true" 출력

// 또는

alert(oCar["color"]);
alert(oCar["doors"]);
alert(oCar["paidFor"]);
3. 혼합 리터럴
var aCars = [
  {
    "color" : "red",
    "doors" : 2,
    "paidFor" : true
  },
  {
    "color" : "blue",
    "doors" : 4,
    "paidFor" : true
  },
  {
    "color" : "white",
    "doors" : 2,
    "paidFor" : false
  }
];

일단 리터럴 형식까지 보여드렸고 다시 원점으로 돌아와서 이와같은 리터럴형식으로 값을 넘겼을때 좋은 점은 매개변수 활용성이 높아집니다. 뿐만 아니라 사람이 읽기도 편해지죠.... 그리고 함수 값의 변경시 사용하고 있는 함수에서 변경이 용이 하다는 점입니다.

예를 들어 위와 같이 리터럴형식으로 넘기지 않는다면 함수의 매개변수는 총 5개가 되고 만약 중간에 필요 없는 값이 있을 경우 null처리를 해서 값을 보내야 한다는 점입니다.

객체가 아닌 값을 넘겼다면 함수는 이렇게 만들어야 합니다.
function fTest1(param1, zip1,zip2,address,cnt){
//code...
}
fTest1(“값”,”100”,”200”,”우리집”,3)

뿐만 아니라 만약 함수의 매개변수 순서가 바뀐다면 그 함수를 부른 모든 값을 변경해야 합니다. 즉 함수를 선언한 매개변수 순서를 꼭 지켜야 한다는 말이죠. 하지만 리터럴 형식으로 값을 넘기면 그럴 필요도 없고 값이 빠지면 빠진 값만 undefined가 나옵니다.

만약 Jquery를 사용하면 extend라는 util을 통해서 객체로 넘어온 값을 합병해서 사용할 수도 있습니다.

Jquery를 사용한 객체 합병은 다음 포스트에서 다루도록 하겠습니다.

Posted by Jake Kim
JavaScript/Default2010. 5. 31. 09:37

문자열로 넘어오는 값을 Date형태로 바꿔야 할 때 참조 하기 바람...

//[IE only]
//Variable with Date Format as MM-dd-yyyy:
var dateString = "03-20-2008"	//MM-dd-yyyy

//[IE, FF]
//Variable with Date Format as yyyy/MM/dd:
var dateString = "2008/03/20";  // yyyy/MM/dd

//[IE, FF]
//Variable with Date Format as MM/dd/yyyy:
var dateString = "03/20/2008";  // MM/dd/yyyy

//[IE, FF]
//Variable with Date Format as MMMM dd, yyyy:
var dateString = "March 20, 2008";  // MMMM dd, yyyy

//[IE, FF]
//Variable with Date Format as MMM dd, yyyy:
var dateString = "Mar 20, 2008";  // MMM dd, yyyy

var myDate = new Date(dateString);
document.write("Date :" + myDate.getDate());
document.write("Month : " + (myDate.getMonth()+1));
document.write("Year : " + myDate.getFullYear());
물론 날짜형식을 바로 비교하는것도 가능함...
var date1 = new Date();
var sDate1 = date1.setFullYear(2010,4,31);
var sDate2 = date1.setFullYear(2010,5,31);
if(sDate1>sDate2){
	document.write("sDate1 더크다.");
}else{
	document.write("sDate2 더크다.");
}
Posted by Jake Kim
JavaScript/Default2010. 5. 28. 13:27

제목은 자바스크립트 이벤트에 대해서 라고 했지만, 거창한 내용을 쓰는 글을 아니며 그냥 가볍게 쓰는 글이니 읽는 분들도 가벼운 마음으로 봐주시기 바랍니다.

HTML에서 엘리먼트에 이벤트를 거는 경우 아래와 같은 형식을 본적이 있을 겁니다.
<a href="link" onclick="return func()"></a>
<form action="link" method="post" onsubmit="return func(this)"></form>

소스를 보시면 제가 의도하는 바를 눈치 채셨을 겁니다.
onclick="return func()", onsubmit="return func()" 즉 함수앞의 return 구문입니다.

만약 이 부분까지 보시고 내용을 파악 하셨다면 아래 내용은 안보시길 바랍니다. 위 내용에서도 언급했듯이 가볍게 쓰는 글이다 보니 더 이상의 깊이는 없습니다.

위와 같이 return 구분을 사용하면 func()에서 return값을 넘겨주기 전까지 대기 상태가 됩니다.
return false를 하면 이벤트 자체가 취소가 됩니다.

좀더 정확한 소스를 통해서 사용법에 대해 알아 보겠습니다.

function func(formObj){ try{ if(formObj.a.value == ''){ alert("값이 없잖아....!!!!"); return false; } }catch(var e){ alert(e.message); return false; } }


위와 같이 onsubmit 이벤트가 발생시  func함수를 호출하고 이 함수에서 돌려주는 값이 false일 경우 submit은 일어 나지 않습니다.

일단 위와 같은 방법으로 사용 할 수 있으며 특정 함수에서도 값 체크를 통해서 대기 상태를 만들어야 할 경우 함수 앞에 return 문을 써주시면 됩니다.
Posted by Jake Kim
JavaScript/Default2010. 5. 28. 11:25

스크립의 작성 위치는 기본적으로 <head>역역과 <body>영역에 작성할 수 있습니다.
<head>영역에 적성할 경우 문서가 로딩되기 전에 스크립트 코드가 전달 되기 때문에 문서의 로딩과 동시에 스크립트 실행이 가능합니다. 하지만 <body>영역에 스크립트 코드가 작성되어 있다면 로딩 되면서 스크립트 코드가 실행 됩니다.

이 부분은 스크립트의 실행에도 중요한 부분을 차지 하지만 스크립트 파일을 불러올 경우 head와 body역영의 위치에 따라 불러오는 차이도 있습니다.

무슨말인가 하면 head에 스크립트 파일이 위치 한다면 화면상 내용이 렌더링이 안되고 있다는 말입니다. 이 렌더링 부분은 브라우저 마다 약간의 차이는 있지만 보통 body 안의 내용을 순차적으로 보여줍니다.

근데 만약 head에 무거운 자바파일이 존재 할 경우(처음 파일을 받기전) body의 렌더링은 head안의 스크립트가 모두 전달 된 후 시작 된다는 말입니다.

즉 당장 렌더링 되는 과정에 필요한 함수가 있다면 또는 렌더링 되는 과정에 인벤트를 발생 할 필요가 있다면 그 함수는 head에 넣어 두는게 좋으며, 그 외의 스크립트는 body 하단 부분에 두는게 렌더링 측면에서 효과가 있을 것입니다.

Posted by Jake Kim
JavaScript/Default2010. 2. 26. 11:08
출처: Advanced JavaScript(http://www.slideshare.net/stoyan/advanced-javascript-presentation) 프리젠테이션 36페이지 내용에서 인용했습니다.



   Use this    Not That
   var obj = {};    var obj = new Object();
   var arr = [];     var arr = new Array();
   var reg = /[a-z]/gmi;    var rec = new RegExp('[a-z]','gmi');
   var fnc = function(a,b){a+b}    var fnc = new Function('a,b','return a+b');


두가지 방법 모두 의미적으로는 동일합니다. 위 프리젠테이션에서 첫번째 방법을 선호한 이유는 일단 사용하기가 편하기 때문이죠.
초기 자바스크립트에선 객체를 구문적으로 엄밀히 구분할수 없어 두번째 방법으로 코딩 했으나... 지금은 첫번째 방법으로 코딩해도 객체를 구분으로 파악 할 수 있기 때문에 사용상 편한 첫 번째 방법을 권하고 있는것 같습니다.
Posted by Jake Kim
JavaScript/Default2010. 2. 22. 12:07

요즘 어지간한 API에 String Format의 메서드가 있어서 %d, %3d, %5d처럼 형식을 만들 수 있지만 String Format이 없는 경우 간단한 방법을 통해 해결 할 수 있다.
(사실 지금 사용하고 있는 자바버전이 1.3이라서  String Format이 없다.)

일단 예를 들자면 숫자를 0000의 4자리로 표현 한다고 가정하자.

0005 -> 0005
05 -> 0005
55 -> 0055
7895 -> 7895

이런 경우 아래 처럼 substring과 length를 이용하면 쉽게 해결 할 수 있다.

String value = "78";
value = value.substring(value.length - 3);

아래는 바로 확인 해 볼수 있도록 javascript로 만든 버전입니다.

function fillZero(val, len)
{
    val = "00000000" + val;
    return val.substring(val.length - len);
}
Posted by Jake Kim
JavaScript/Default2010. 2. 20. 20:09


워드 파일은 아래 내용을 단순히 프린트용으로 만든 버전입니다.
(잉크 절약을 위해 검은색으로 된 부분을 흰색으로 바꿨습니다.)

자바스크립트의 객체, 참조, 범위에 대해서 알아보고자 합니다. 조금은 넓게 , 얇게 그러나 깊게 파보도록 하겠습니다.

자바스크립트는 기본적으로 객체 기반 스크립트 언어입니다. 라고 하면 의아해 하실 분들도 계실거고 동의하지 않으시는 분들도 계실 겁니다. 그래서 좀 더 정확하게 말하자면 C++, JAVA의 Class 기반의 인스턴스를 활용하는 것과는 다르게 객체 그 자체와 Prototype 기반의 인스턴스를 활용 합니다. 이 말은 이 글을 보시면서 이해하시게 될 것입니다.

이 글에서 자바스크립트가 OOP로서 적당한가, 아닌가에 대해서 다룰려고 하는 게 아닙니다. 그냥 그렇다는 겁니다. 차설하고, 본격적으로 이야기를 해보겠습니다.

Built-in, Native, Host object

자바스크립트는 기본적으로 ECMA Script를 기반으로 합니다. ECMA 스크립트에서 Object는 크게 3가지 종류(type(또는 form)이 아니라 template 라고 할까요)가 있습니다. 

    Native Object
    Built-in Object
    Host Object

이렇게 세 가지가 되겠습니다.

[Native Object]는 ECMA 스크립트에 정의된 내용에 기반해 스크립트가 작동되는 중에 정의되는 객체입니다. 이 객체 중에서 일부는 Built-in으로 제공됩니다. Native는 Host(개발자가 작성한 스크립트에 의한 것)가 정의하는 것 보다 우선시 됩니다.

[Built-in Object]도 역시 ECMA에 정의된 것을 기반으로 하지만, ECMA 스크립트 프로그램이 구동되는 시점부터 바로 제공되는 객체, 즉 쉽게 말하면 기본 객체 입니다. Native는 기본 객체이긴 하지만 스크립트가 구동되는 환경에서 제작(construct)되어 제공되는 점이 다릅니다.

[Host Object]. 이것이 바로 우리가 스크립트 프로그램을 제작하면서 만들어지게 되는 객체가 되겠습니다.

즉 기본적으로 자바스크립트가 구동되는 과정은, 브라우저에 기본적으로 내장된 ECMA built-in이 구성되고 Native가 구성이 됩니다. 그리고 사용자 스크립트를 읽어들여서 Host 가 구성이 되는 것입니다.

이제, 우리가 실제로 자바스크립트에서 다루게 되는 객체에 대해서 알아보도록 하겠습니다. 자바스크립트는 위에서도 이야기했듯이 prototype과 객체 그 자체를 기반으로 하여 객체가 구성이 됩니다.

자바스크립트(ECMA)에서 Built-in 객체는 Object, String, Number, Boolean, Date, Array, Math, RegExp, Error 등이 있습니다. 그리고 Native 객체가 Host 보다 우선시 된다고 하였는데, 그것은 스크립트 환경이 구성되는 동안 먼저 built-in 객체가 구성되고 native 객체가 구성된 후 host 객체가 구성되기 때문입니다.

    Number; // function Number() { [native code] }
    Number = {};
    Number; // Object {}
    var q = 1;
    q; // 1
    typeof q; // "number"
    q.constructor === (1).constructor; // true
    Number = (1).constructor;
    Number; // function Number() { [native code] }

즉 Host 스크립트에 의해서 name 이 Host로 넘어갈 수는 있지만 그 자체가 없어지는 것은 아닙니다. 그렇기 때문에 Number를 새로 할당한다고 해도 아래와 같은 게 가능합니다.

    Number.prototype.multi = function(m) { return this * m; };
    Number = {};
    var q = 5;
    q.constructor.adding = function (a) { return this + a; }
    Number = (1).constructor;
    q.multi(5); // 25
    var w = 6;
    w.multi(5); // 30
    w.adding(4); //10
    Number; // function Number() { [native code] }

이게 가능한 이유는 prototype을 통해서 확장된 기능은 built-in 객체가 확장되고 native 객체는 name(Number, String, Object 등등)이 없어진다고 바뀌지는 않습니다. 이것을 간단한 예로 이해를 해보자면 아래와 같습니다.

    var obj = function () { this.a = 1; this.b = 'a'; };
    var o = new obj;
    obj = null;
    o.constructor; // function () { this.a = 1; this.b = 'a'; };
    var p = new o.constructor;
    p.constructor === o.constructor; // true

우리가 가끔 프로그램을 제작하면서 함수로 전달받은 인자, 또는 참조하는 변수의 값을 확인하는 과정에서 많이 사용하는 게 'typeof' 와 '===', '==' 가 있습니다. 이중에서 우리를 혼동하게 하는 것이 'typeof' 입니다.

    var x = [1, 2, 3];
    typeof x; //"object"
    x instanceof Array; // true
    x instanceof Object; // true

우리는 x를 배열로 선언하고 x가 배열이 맞는지 확인하기 위해서 typeof로 Array가 나오길 기대하지만 object가 나옵니다. Date도 마찬가지로 "object"가 반환이 됩니다. 그 이유는 ECMA에 정의된 Type이  Undefined, Null, Boolean, String, Number, Object, Function 이기 때문입니다.

즉 쉽게 이해하자면, 변수에 할당되는 값의 종류는 위의 Type을 벗어나지 않는다는 것입니다. 그런데 여기서 또 하나 문제가 되는 것이 Null 입니다.

    var x = null;
    var y;
    typeof x; // "object"
    typeof y; // "undefined"
    x instanceof Object; // false

x를 null로 할당을 하면 Type이 "null"로 나와야 하는데 "object"로 나옵니다. 그래서 인스턴스를 알아보면 Object는 아니라고 합니다. 이 부분에 대해서는 ECMA에서도 "null"로 바꾸자는 의견이 나왔었던 것 같은데 그냥 'typeof null' == "object"로 하기도 했다고 합니다.

자바스크립트의 모든 변수의 값은 객체입니다. 그 객체의 Type은 위에서 언급했던 것들입니다. 물론 그것은 Type이고 정확하게는 Built-in 객체가 되겠죠. 이 Built-in 객체가 되는 건, 객체가 생성되는 과정에 Class Type 으로 설정됩니다. 물론 자바스크립트 레벨의 Class는 아닙니다.

이렇게 ECMA 기술레벨에서 생성되는 객체들에 대해서는 몇 가지 설정을 할 수 있습니다. 수정이 가능한가? 같은 것들 말입니다. 예를 들자면

    Number.MIN_VALUE == 5E-324

라고 나옵니다. 소수점 아래로 0이 324개 하고 5로 끝나는 어마어마 하게 작은 숫자입니다. 우리가 알고 있는 자바스크립트 설정이라면 저 MIN_VALUE를 못바꿀 것도 없습니다. 왜냐면 자바스크립트 레벨에서 protect 키워드도 없고 constant 설정도 안 되기 때문입니다. 그래서 다음과 같이

    Number.MIN_VALUE = 0;

하게 되면 오류없이 실행이 됩니다. 하지만

    Number.MIN_VALUE; // 5E-324

이러한 결과가 나옵니다. 이처럼 자바스크립트 레벨에서 typeof 로는 "number" 이지만 native 객체인 Number를 인스턴스 하지 않고 built-in 객체 Number로 바로 생성되는 경우가 있습니다. Built-in 객체는 위의 예에서와 같이 좀 더 자세한 설정이 가능합니다. 하지만 그것은 각 브라우저의 스크립트 엔진 측면에서 일어나는 일이니 실제 자바스크립트를 다루는 우리와는 별 인연은 없습니다.

앞서 Number, String, Object 등과 같은 Native 객체와 Built-in 객체가 다르다고 하였는데, 좀 더 자세히 알아보도록 하겠습니다. 코드가 길고 지저분해 보이실지 모르겠지만, firebug나 크롬 디버그툴, 오페라 개발자 패널 등에서 해보시면 이해하기 쉬우실 겁니다.

    /* native object, Number 확인 */
    Number; // Number() ==> function Number() { [native code] }
    
    /* a 를 native Number 를 인스턴스 */
    var a = new Number(55);
    a; // 55 { }
    typeof a; // "object"
    a instanceof Number; // true
    a instanceof Object; // true
    
    /* b 를 인스턴스 없이 선언 */
    var b = 55;
    b; // 55
    typeof b; // "number"
    b instanceof Number; // false
    b instanceof Object; // false
    
    /* a와 b를 비교 */
    a == b; // true
    a.constructor === b.constructor; // true
    
    /* a의 프로퍼티 확장 및 확인 */
    a[0] = 'This is a number object';
    a[1] = 'Instance of Number';
    a; // 55 { 0: 'This is a number Object', 1: 'Instance of Number' }
    
    a == b // true
    
    /* native object, Number 의 프로토타입 설정 */
    Number.prototype.multi = function(m) { return m * this; };
    
    /* a, b 확인 및 확장된 메소드 실행여부 확인 */
    a; // 55 { 0: 'This is a number Object', 1: 'Instance of Number', multi: function() }
    b; // 55
    a.multi(5); // 275
    b.multi(5); // 275
    
    /* native object, Number 의 프로토타입 설정 */
    Number.prototype.push = Array.prototype.push;
    
    /* a 확인 */
    a.length = 2;
    a.push('This is pushed text');
    a; // 55 { 0: 'This is a number Object',
            1: "Instance of Number",
            2: :"This is pushed text",
            length: 3,
            multi: function() ,
            push: function() }
    /* b 확인 */
    b.push('This is b!');
    b; // 55
    typeof b[0] == "undefined"; // true

a 는 native Number 객체를 인스턴트 했고 b는 그렇지 않았습니다. 그렇지만 결국 둘은 같은 기능으로 동작을 합니다. 계산도 되고 말입니다. native 객체로 인스턴스 하게 된 a는 확장이 가능한 객체로 반환된 반면 b는 확장이 될 수 없습니다. 이유는 native Number 가 Object를 인스턴스 하였기 때문입니다.

native를 인스턴스하게 되면 built-in 수준에서 설정이 됩니다. 하지만 그렇지 않은 경우는 기본 built-in 설정이 되게 됩니다. 그러나 인스턴스를 하지 않을 뿐 constructor 함수를 기본으로 사용하여 built-in 객체가 반환이 됩니다. 다음의 예를 보시면

    /* 변수 설정 */
    var a1 = new String('asdf');
    var q1 = new Number(5);
    var a2 = 'asdf';
    var q2 = 5;
    var a3 = String('asdf');
    var q3 = Number(5);
    
    /* 문자열 변수 생성자 및 인스턴스 확인 */
    a1.constructor === a2.constructor; // true
    a1.constructor === a3.constructor; // true
    a1 instanceof String; // true
    a2 instanceof String; // false
    a3 instanceof String; // false
    
    /* 숫자 변수 생성자 및 인스턴스 확인 */
    q1.constructor === q2.constructor; // true
    q1.constructor === q3.constructor; // true
    q1 instanceof Number; // true
    q2 instanceof Number; // false
    q3 instanceof Number; // false
    
    /* 문자열 변수 프로퍼티(또는 메소드) 확장 */
    a1.push = a2.push =a3.push = Array.prototype.push;
    a1.push; // function push() { [native code] }
    a2.push; // undefined;
    a3.push; // undefined;
    
    /* 문자열 변수 length 프로퍼티 값 다루기 */
    a1.length; // 4
    a2.length; // 4
    a3.lengthl; // 4
    a1.length = 0;
    a1.length; // 4
    a2.length = 0;
    a2.length; // 4
    a3.length = 0;
    a3.length; // 4
    
    /* 확장된 프로퍼티(또는 메소드)로 실험 */
    a1.push('g');
    a1; // "asdf"
    a1.length; // 4
    
    /* 문자열 변수 직접 접근 및 변경 */
    a1[0] = 'g';
    a1[0]; // 'a'
    
    /* 숫자 변수 프로퍼티(또는 메소드) 확장 */
    q1.push = q2.push = q3.push = Array.prototype.push;
    q1.push; // function push() { [native code] }
    q2.push; // undefined
    q3.push; // undefined
    
    /* 숫자 변수 length 프로퍼티 확인 및 변경 */
    q1.length; // undefined
    q1.length = 3;
    q1.length; // 3
    
    q2.length; // undefined
    q2.length = 3;
    q2.length; // undefuned
    
    q3.length; undefined
    q3.length = 3;
    q3.length; // undefined
    
    /* 확장된 메소드를 사용한 경우 */
    q1.push('asdfg');
    q1; // 5 { 3: "asdfg", length: 4, push: function() }
    
    /* 직접 접근한 경우 */
    q1[0]; // undefined
    q1[0] = 'asdf';
    q1[0]; // 'asdf';

즉 native는 built-in 객체를 생성하기 위한 class 라고 보시면 됩니다. 대신 자바스크립트 레벨에서 native의 인스턴스 여부에 따라서 생성 조건이 달리지는 것입니다. 그렇기 때문에 자바스크립 상에서 var a = 1; 했을 경우 a 는 객체가 아닌 것 같지만 염연히 construct를 가진 객체라고 할 수 있습니다.

간단히 지금까지의 내용을 요약하자면, 자바스크립트의 모든 변수의 값은 객체이며 그것은 브라우저 엔진의(ECMA 기술레벨) 설정을 따르며, native 객체를 인스턴스 하느냐, 하지 않느냐에 따라서 반환된 객체의 확장성 여부 등이 달라진다. 라는 것입니다.

이제 별로 도움이 안 되었던 그리고 뭔 소린지 잘 모르겠던 built-in 객체 등등의 이야기는 그냥 그런가보다 하며 넘어가고 (사실 작성하면서 저도 용어도 혼동되고 내용도 혼동되고 그럽니다.) 객체 내에서의 참조에 대해서 알아보도록 하겠습니다.

Object Reference

자바스크립트의 기본객체는 window 입니다. 그렇기 때문에 우리가 스크립트를 작성하면서 선언하는 전역변수들은 사실상 window 객체에 종속(bind)됩니다. 쉽게 말하자면 전역 네임스페이스(namespace)로 window가 사용된다는 것입니다. (사실 네임스페이스와는 개념이 다르더라도 쉽게 생각하자는 의미에서)

    var x = null;

    window.x = null;

는 같은 것입니다. 그렇지만 브라우저 별로 두 개를 다루는 속도의 차이는 제각각이라는 걸 염두하셔야 합니다. 이와 관련해서는 예전에 작성한 Speed up Javascript 라는 게시물을 참조하시면 됩니다.

자바스크립트에서 객체의 범위는 객체 그 자신의 프로퍼티(property)까지만 한정됩니다. 이 말은 우리가 자주 사용하게 되는 this와 관련이 있습니다. 즉 this의 범위는 객체 그 자신을 가리키는 것입니다.

    var obj1 = { a: function() { return this.b; }, b: 'text message'};
    obj1.a(); // "text message"
    
    var obj2 = { a: { q : function() { return this.b }, b: 'child'}, b: 'parent'};
    obj2.a.q(); // 'child'

obj1의 경우 메소드 a는 함수로 함수 내부의 this는 obj1을 가리킵니다. 왜냐면 a가 obj1에 종속되어 있기 때문입니다. 반면 obj2의 메소드 q의 this는 obj2가 아닌 a를 가리킵니다. 보시다시피 q가 a에 종속되었기 때문입니다.

이러한 종속의 관계와 기본 네임스페이스(객체)인 window로 인해서 알면 당연한 것이고 모르면 황당한 결과가 나오기도 합니다.


    (x=[].reverse)() === window; // true

[] 는 기본적으로 Array 객체이고 reverse는 Array 메소드 입니다.

    x=[].reverse;

는 결과적으로 x는 reverse 함수가 됩니다. 그리고 앞서 말한바와 같이 기본 네임스페이스는 window 입니다.

    x === window.x; //true
    x === Array.prototype.reverse; // true

reverse가 this 로 Array 객체를 다룬다고 한다면 함수 끝에 return this; 이렇게 하게 되면 window에 바인드 된 x 는 window를 반환하게 됩니다. 이것을 다음과 같이 하면 확실하게 알 수 있습니다.

    var b = {a:[].reverse};
    b.a() === b // true

이 경우에는 a가 b에 바인딩 된 상태기 때문에 this가 b를 가리키기 때문에 b를 반환하게 됩니다.

이렇게 되는 이유는 프로퍼티에 연결되는 모든 값들은 객체이고 객체는 생성되어진 후 참조가 되어지는 것이기 때문입니다. 이 참조에 대한 쉬운 예를 들면 아래와 같습니다.

    var arr = [1, 2, 3];
    var q = arr;
    q[0] = 4;
    q; // [4, 2, 3]
    arr; // [4, 2, 3]
    arr === q; // true

단, 숫자나 문자열, 부울형은 객체 그 자신을 변경하게 되므로 새로운 객체가 할당되고 참조의 연결은 끊어지게 됩니다. 그것은 객체나 배열에서도 마찬가지입니다.

    var q = [1, 2, 3];
    var a = q[0];
    a === q[0]; // true
    q[0] = 4;
    q; // [4, 2, 3]
    a === q[0]; // false
    
    var z = false;
    var x = z;
    z === x; // true
    z = true;
    z === x; // false

참조가 끊어졌는데 왜 다른 변수는 안 끊어졌는가 하면 앞서 built-in 객체를 생각하시면 됩니다. 즉 변수들은 built-in 객체를 참조하다가 새로운 객체가 할당되면 해당 built-in 객체의 참조를 끊고 새로운 built-in 객체를 참조하게 됩니다.

아시겠지만 함수의 기능 중에 apply 라는 것이 있습니다. 간단히 말하자면 참조의 범위를 공유하게 되는 것입니다.

    var arr = [1, 2, 3];
    var obj = { a: function() { return this.length; } };
    obj.a(); // undefined
    obj.a.apply(arr); // 3

코드에서 보이는 바와 같이 obj 객체에는 length 프로퍼티가 없기 때문에 obj.a()를 호출하면 undefined 가 뜹니다. 하지만 obj.a.apply(arr) 하게 되면 arr의 length가 출력이 됩니다. 저 apply를 아예 종속해서 이렇게도 할 수 있습니다.

    var arr = [1, 2, 3];
    var obj = {
        a: function() {
             return (function(sl) { return this.length + sl; }).apply(arr, [this.length]);
        }
        , length: 4 };
    obj.a(); // 7

또는 function 자체를 확장해서

    var arr1 = [1, 2, 3];
    var arr2 = [4, 5, 6, 7, 8];
    var fn = function(v) { return this.length + v; };
    fn.binding = function(o) {
        var s = this;
        return function(bo) {
            return s.apply(bo ? bo : o, [this.length]);
        };
    };
    var obj = { a: fn.binding(arr1), length: 7 };
    obj.a(); // 10
    obj.a(arr2); // 12

이렇게 할 수도 있습니다. 여기서 주의할 점은 fn.binding 입니다. 우리가 아래와 같이 사용을 하게 된다면

    fn.binding()();

fn.binding 내의 this는fn을 가리키게 되고, this.length 는 window.length와 같은 효과를 지니게 됩니다. 이유는 fn이 윈도우에 종속되어 window.fn 과 같이 되므로 fn의 this는 window가 되고, 결과적으로 fn.binding의 this.length는 fn내에서 this.length와 같은 것이 됩니다.

객체를 찾게 되는 순서는 지역(함수 내) 변수가 우선시 되고 없으면 전역변수에서 찾게 됩니다. 그래서 보통 빠른 속도를 내기 위해서는 함수내에서 자주 사용되는 전역 변수를 지역변수에 할당하기도 합니다. 문제는 클로저(closure)에 있습니다.

Closures

클로저는 지역변수, 전역변수도 아닌 버려진 변수라고 할 수 있습니다. 클로저는 유용하게 사용할 수 있지만, 보통은 의도하지 않은 상황(클로저라는 개념을 모르거나 사용하지 않으려 했을 경우)에서 연출되는 경우에 많이 발생한다는 것입니다. 그리고 이것은 메모리 관리에 있어서 문제가 되기도 합니다.

자바스크립트는 사용하지 않는 변수들을 처리하는 GC(Garbage collection)가 일어나 메모리를 정리합니다. 문제는 클로저로 처리된 변수들은 함수의 수행이 끝나도 남아있게 된다는 것입니다. 방금 function을 확장하던 부분에서

     fn.binding = function(obj) {
        var s = this;
        return function(bo) {
            return s.apply(bo ? bo : obj, [this.length]);
        };
    };


이 부분을 보시면 반환되는 함수에 's'가 있습니다. 결론적으로


    obj.a; // function(bo) { return s.apply(bo ? bo : obj, [this.length]); }
    s; // undefined

obj.a 는 매 실행시 s 라는 함수를 실행하게 되는데, 실제로 이 함수는 전역(global)으로 명시된 상황은 아닙니다. 하지만 함수는 실행시에 s를 참조할 수 있습니다. 보통이라면 함수가 수행된 뒤에는 지역변수는 없어지는 게 맞지만 이 경우에는 반환되는 값에서 지역변수를 참조하기 때문에 메모리상에 built-in 객체가 남게 됩니다. 물론 어떤 특수한 목적으로 계산된, 한정된 범위 내에서의 클로저의 사용은 매우 유용합니다.

클로저의 유용함이란 바로, 자바스크립에서 private 멤버(method, property)를 구현하는데 이용할 수 있다는 것입니다.


    var animal = function(n, a) {
        var nick = n;
        var age = a;
        var growth = function() { return ++age; }
        return {
            setNick: function(n) { nick = n; },
            getNick: function() { return nick; },
            setAge: function(a) { age = a; },
            getAge: function() { return age; },
            getAll: function() { return [nick, age]; },
            newYear: function() { return nick + ' is now ' + growth() + ' years old'; }
        };
    };
    var dog = new animal('happy', 1);
    
    dog.nick; // undefined
    dog.age; // undefined
    
    dog.nick = 'song';
    dog.nick; // "song"
    dog.getNick(); // "happy";
    dog.setNick('MungMung');
    dog.nick; // "song"
    dog.getNick(); // "MungMung"
    
    dog.newYear(); // "MungMung is now 2 years old"
    dog.getAge(); // 2

위와 같이 구현을 하였을 때, animal 안의 nick과 age는 분명 외부에서 접근할 수 없습니다. 하지만 클로저가 형성이 되면서 외부에서는 접근이 안 되는 private 멤버가 만들어지게 됩니다.

클로저는 이런 유용함이 있으면서도 주의를 요하는 것은 비단 메모리 누수와 관련된 문제만이 아닙니다. 메모리 누수와 관련된 이야기는 많이 들어보셨을 것이고, 그 이유는 클로저가 GC에서 회수가 안 되고, 해당 구분이 지속적으로 실행되면서 계속해서 쌓이게 될 수 있기 때문입니다.

그리고 또 다른 이유는 네이밍 참조, 그러니까 전역변수를 참조하기 위해 nick 를 썼는데, 그게 클로저를 참조하는 경우도 생길 수 있다는 것입니다. 물론. window.nick 와 같은 방법을 사용하여 피할 수도 있습니다.

클로저가 우리가 생각하지 못했던, 즉 의도하지 않게 일어나서 메모리 문제를 일으키는 경우의 부분은 setTimeoue 설정이나 이벤트설정에서 많이 발생합니다. 이유는 우리가 이 둘에 할당하는 함수가 미리 지정된 함수를 쓰는 경우도 있지만, 그 때의 상황마다 익명함수를 할당하는 경우도 많기 때문입니다. 예를 들자면


    var el = document.getElementById('test');
    el.onclick = function() { …… };

이런 경우가 있다고 하면, 저 한 번의 경우는 별 문제가 될 것이 업습니다. 하지만 반복적인 어떤 작업을 통해서 저와 비슷한 작업이 많이 발생하게 된다면 문제가 됩니다. 저게 어째서 클로저인가? 하시는 분들이 계실지도 모르겠습니다. 왜냐하면 el.onclick 이 window.el.onclick 이라면 그럴 수 있습니다만,


    function setFn(id)
    {
        var el = document.getElementById(id);
        el.onclick = function() { alert(el.id); }
    }

이런 경우에는 명백히 클로저가 됩니다. 물론 window.el 도 사실상 DOM에 접근한 것이기 때문에 사실상 자바스크립트로 명명된 것이라 보기는 힘듭니다. 무슨 말인가 하면


    var el = document.getElementById('test');
    el.onclick = function() { …… };
    delete el;

이렇게 하여도 'test' 라는 문서개체(element)는 여전히 익명함수를 갖고 있습니다. 앞서도 언급하였지만, 자바스크립트는 객체 참조를 하기 때문에 어느 하나라도 참조가 되고 있는 객체는 메모리에서 수거가 되지 않습니다.

그렇지만 클로저의 사용은 분명 관리만 잘 한다면 속도 부분에서 큰 이득을 안겨줄 수 있습니다.


    var i = 0;
    function test(){
        var i = 0; // case(3)
        return function() {
            //var j = window.i++; //case (1)
            var j = i++; // case(2)
        };
    };
    
    var fn = test();
    var t1 = new Date;
    var k = l = 0;
    
    for ( ; k++ < 200000; )
    {
        for ( ; l++ < 200000; )
        {
            fn();
        };        
    };
    alert((new Date) - t1);

위 예제에서 각각 다음과 같은 설정으로 실험을 해보시면 알게 되실 겁니다.

        1) case1 주석처리
        2) case 1, 3 주석처리
        3) case 2 주석처리
        
브라우저 별로 상황이 다르기는 하겠지만 보통 window.i 식으로 접근하는 것 보다 클로저가 빠를 것입니다. (해보시면 아시겠지만 IE ㅤㅅㅞㅅ 소리 나옵니다.) 또 브라우저에 따라서 클로저보다 전역 i를 참조하는 게 더 빠른 경우도 있습니다.

Fin

하다보니 클로저까지 하게 되었습니다. 클로저에 대한 이해는 좀 많이 부족할 수도 있습니다. 그 부분에 대해서는 구글링만 살짝하여도 충분한 얘기들이 나오니 그것을 참조하셔도 좋을 것입니다.

자바스크립트는 스크립트언어라는 특성상 가볍게 보일 수도 있지만, 그 특유의 유연함은 정말 환상적이라고 할 수 있습니다. 다만 그 유연함이 많은 불안정성을 내포하고 있다는 것, 그것을 염두해야 할 것 입니다.

이상으로 별 영양가 없는 강좌 아닌 강좌, 팁 아닌 팁을 마치도록 하겠습니다.

Posted by Jake Kim
JavaScript/Default2009. 12. 29. 10:15

참고 : http://www.crockford.com/javascript/memory/leak.html

Internet Explorer DOM 객체에 대하여 메모리 누수가 발생한다. (IE8에서는 개선 되었음)

Div Layer에 동적으로 Flash를 바꿔 낀다고 가졍한다.

1번 Flash는 작업관리자 IExplorer에서 메모리 확인 결과 100mb
2번 Flash는 작업관리자 IExplorer에서 메모리 확인 결과 200mb

이 나온다고 가정하면... 코드는 아래와 같다.


Div Layer "FlashObject"에 초기에 b.swf를 로딩하게 되면 작업관리자 IExplorer메모리는 200mb까지 할당된다.

그 상태에서 Div Layer "FlashObject"에 초기에 a.swf를 로딩하게 되면 작업관리자 IExplorer메모리는 순수 a.swf에 해당하는 메모리 100mb을 할당하지 않고 b.swf에 해당하는 메모리 + a.swf에 대항하는 메모리 총 300mb을 할당하는 모습을 볼수 있다. 이 구조를 계속 반복하게 되면 메모리 해제는 하지 못하고 누적되어 브라우저가 죽어버릴 것이다. 

위 사항을 해결할 수 있는 방법으로는 purge함수를 만들어 사용할 수 있다.

purge 함수는 DOM Element 인자로 받는다. Element의 Attributes를 루프를 돌면서, 함수를 찾으면 null로 만들어 순환구조를 끊고 메모리가 교정될 수 있도록 한다. 함수는 또한 모든 하위 Element에도 같은 작용을 해서 closure가 잘 제거 되도록 한다. purge함수는 모질라와 오페라에는 아무런 해가 없으며 IE에서만 작용한다. purge함수는 Element가 removeChild메소드나 innerHTML속성으로 삭제되기 전에 호출되어야 한다.

Posted by Jake Kim
JavaScript/Default2009. 11. 6. 00:09

자바스크립트에서 객체의 개수를 알아 오는 방법으로 object.length가 있다면 좋겠지만... 이런 방법은 없다...
대신 반복문을 돌려서 객체의 개수를 알아올수 있다.

var obj = {
	value0: {
		key: "a0",
		value: "JAVA"
	},
	value1: {
		key: "a1",
		value: "C#"
	},
	value2: {
		key: "a2",
		value: "C++"
	},
	value3: {
		key: "a3",
		value: "JSP"
	},
	value4: {
		key: "a4",
		value: "PHP"
	}
};
var size = 0;
for (var i in obj) {            
	size++;           
}
document.writeln(size);
Posted by Jake Kim