toggle menu

[JavaScript] JavaScript 정리 - 리터럴부터 클로저까지

2012.08.30 16:23 JavaScript
리터럴
코드상에서 데이터를 표현하는 방식을 리터럴이라고 한다.
var aaa = 2; //숫자
var bbb = "2"; //문자
var bbb = '2'; //얘도 문자
var ccc = true; //얘는 불린
var ddd = [1, 2, 3, 4]; //얘는 배열
var eee = { p1 : 2, p2 : ‘2’ }; //얘는 객체
var fff = null; //null
var ggg = undefined; //undefined
var hhh = function(){ alert('ㅋㅋㅋ'); } //얘는 함수!!



파싱과 이름
자바스크립트는 먼저 파싱 과정을 거치게 된다.
따라서 함수의 위치에 관계없이 호출가능하다.
또한 함수명과 변수명이 같은 경우 순서에 따라 덮혀 씌워진다.


참과 거짓
모든 개체는 true인 것으로 간주.
문자열은 비어 있는 경우 false인 것으로 간주.
null 및 undefined는 false인 것으로 간주.
숫자는 0인 경우 false.
NaN도 false.



typeof, Infinity, NaN

var aaa = 10; typeof aaa; //number를 반환한다. var a = 10*"f"; //잘못된 수식의 결과는 NaN값이 들어간다. NaN == NaN; //이렇게 물어보면 false가 나온다. isNaN(10*"f"); //이건 그래도 정상적으로 true로 나온다. isFinity(10); //유한값이므로 true가 나온다.



비교연산자
//equal 비교
1=='1'; //놀랍게도 true다!! 자바스크립트는 먼저 타입변환을 한 후에 비교하기 때문에 같다고 반환한다.

//identical 비교
1==='1'; //이렇게 비교해야 false가 나온다.

매우 어색하지만 === 를 해야 정상적으로 타입변환없이 비교가 된다. 




디폴트값을 가져오는 꼼수
var aaa = false || '기본값'; //앞에가 false면 뒤에 기본값 스트링이 들어간다.
var bbb = true || '얘는 무시되는 값'; //이런 경우에는 항상 true가 들어간다.
var ccc = "" || "빈 값을 입력하셨네요 ㅠㅠ"; //이런식으로 응용도 가능하다.



try/catch/finally 
try {
	//무언가 수행
}
catch( error ) {
	console.log( error.message );
}
finally {
	//여기에 finally
}
try 이후 catch 혹은 finally 둥 중 하나는 반드시 존재해야 한다.




※argument와 parameter의 차이
argument 함수를 호출할 때 인자로 넣어주는 값
parameter 호출받은 함수가 요소로 받는 값



함수의 인자는 aguments 라는 객체에서 관리한다.

function sumOf(x,y) {
	var total=0;
	for(var i=0; i<arguments.length;i++) {
		total+=arguments[i];
	}
	return total;
}
	
sumOf(1); // 결과 : 1
sumOf(1,2);// 결과 : 3
sumOf(1,2,3);// 결과 :6
sumOf(1,2,3,4,5);// 결과 :15

자바스크립트의 유연함이 드러난다. 함수에서 선언하지 않은 파라메터까지도 모두 받아서 연산이 기능하다.
파라메터들은 arguments 라는 객체가 관리하기 때문에 가능한 일이다.
또한 arguments.callee 는 자기 자신을 참조해서 재귀 호출을 구현할 수 있다.
function factorial(x) {
	if(x<=1) {
		return 1;
	}
	else {
		return x*arguments.callee(x-1);
	}
}



함수는 다른 타입의 값처럼 다른 함수를 호출하는 인자 반환값으로 사용될 수 있고 다른 변수에 할당될 수도 있다.
var test = { name : "Joe", speak : function() { return "Ha Ha Ha"; } };

test.name;
"Joe"

test.speak;
function () { return "Ha Ha Ha"; }

test.speak();
"Ha Ha Ha"



익명함수
var v = function(x,y) { return x+y; };
// 함수를 정의하고 변수에 저장

o.func(function(x,y){ return x+y; });
// 함수를 정의하고 다른 함수에 인자로 전달

var added = (function(x,y) {return x+y;})(1,2);
// 함수를 정의하고 바로 호출한다.

첫번째 경우는 가장 일반적인 경우이다.

두번째의 경우, 함수를 정의하면서 그 함수의 주소값을 바로 인자로 전달하는 형태이다.

세번째는 함수를 정의하면서 바로 실행까지 하는 형태이다.




Callback 함수
//메인 프로그램
function MainProgram {
	var arg;
	LibFunc(arg, CallbackFunc);
}

//콜백 함수
function CallbackFunc(result) {
	//result 이용
}

//라이브러리 함수
function LibFunc(arg, callback) {
	var data; // 작업수행 
	callback(data);
}
메인 프로그램에서는 어떤 작업을 수행한 뒤에 생성된 데이터를 callback 함수에서 결과 처리를 하기를 원할 때 위와 같은 구조로 처리한다.
callback 함수 객체를 인자로 보내면 라이브러리 함수에서 callback함수 객체를 받아서 작업 처리한 결과를 callback함수를 호출해 인자로 보내게 된다. callback 함수는 인자로 받은 결과를 최종적으로 이용해 작업한다.




변수 Scope
자바스크립트는 함수 단위로 변수를 관리한단다.
var a=1;

function f() {
	if(true) {
		var c=2; //if블럭 안에서 c를 선언한다.
	}
	return c; //if블럭 밖에서 c값을 참조해 리턴한다.
}

f(); // 2
매우 특이하지만, if 블럭 안에서 정의된 변수도 함수 내에서는 얼마든지 참조가능하다.


function f() {
	g = "global"; //var없이 변수명에 값을 넣으면 전역변수로 자동선언된다.
}

f();
alert(g); //global이 팝업된다!!
매우 놀랍게도 var를 제외하고 변수를 사용하면 런타임에 전역변수에 값이 할당되어 어디서든 참조가 가능하게 된다.
자바스크립트만의 특징이자 유연함이 보인다. 그러나 되도록 var 로 선언해주어 scope를 명확하게 해주는 습관이 잠재적 오류 발생 원인을 줄여줄 것이다.

즉, 사용할 변수를 되도록 함수 앞부분에 선언해두는 습관이 필요하다.




렉시컬(lexical) 특성
자바스크립트는 컴퓨터의 실행을 고려하는 것이 아니라 '코드 그대로의 환경'을 기준으로 정의한 변수 스코프에서 검색한다.
var x = "global";

function f() {
	alert(x); //엉뚱하게도 global이 안나오고 undefined가 나온다ㅠㅠ
	var x = "local";
	alert(x); //여기선 정상적으로 local이 나온다.
}

f();
매우 특이하지만, 함수 중간에 var x가 존재하므로 전역레벨을 파싱하고 그 후에 함수레벨에서 파싱했을 때 함수레벨 상에 x가 undefined로 생성된다.
이름이 동일하므로 전역레벨보다 함수레벨의 x가 먼저 참조되므로 첫 alert에서 global이 아니라 undefined가 출력된다.
만약 함수부에 var x가 없다면 함수레벨에서 참조할만한 x값이 없으므로 정상적으로 global이 잘 출력된다.




루트객체
모든 객체는 전역 객체라고도 하며 전역 변수 스코프라고도 한다.  웹 페이지 실행환경에서는 Window가 루트 객체이다. (this === window //true)
window.String 등 객체들은 루트객체에 속해있다.




클로저
매우 중요한 클로저는 어떤 눈에 보이는게 아니라 '구조' 혹은 '패턴'이다.
function outer() {
	var x=0;
	return function() { return ++x; }
}

var x=-1;
var f= outer();

console.log(x); // -1
console.log(f()); //1
console.log(f()); //2
console.log(f()); //3
마치 자바에서 private 된 함수 내의 x 변수에 접근하기 위해 getter와 같은 역할을 하는 return function() { return ++x; } 를 사용하게 된다.


function outer() {
	var x=0;
	return function(){ return ++x; }
}

var x=-1;
outer(); //function(){ return ++x; }

console.log(x); // -1
console.log(outer()()); //1
console.log(outer()()); //1
console.log(outer()()); //1
이렇게 하면 항상 초기화 된다.


function outer() {
	var x=0;
	return function() { return ++x; }
}

var x=-1;
var f= outer(); //인스턴스 하나를 선언하는 효과다!!
var g= outer(); //이렇게 하면 별도의 인스턴스로 생성된다.

console.log(x); // -1
console.log(f()); //1
console.log(f()); //2
console.log(f()); //3

console.log(g()); //1 //새롭게 1부터 시작하는 것을 볼 수 있다.
console.log(g()); //2
console.log(g()); //3


클로저는 비공개 내부 변수를 갖는 함수 인스턴스 생성자이다.
클로저로 생성한 독립된 변수 공간을 가진 인스턴스를 클로저 인스턴스라고 한다.



객체를 생성하는 함수
선언한 함수 내의 this는 새로 생성된 객체를 가리킨다.
function Person(name,age) {
	this.name = name;
	this.age = age;
	this.increaseAge = function(i){ this.age = this.age + i; }
}

var newObject = new Person("홍길동", 30);

//age: 30
//increaseAge: function (i){ this.age = this.age + i; }
//name: "홍길동"




멤버 관리 구조와 prototype
function Person(name) {
	var firstName = "홍";
	this.name = name;
	this.speak = function(){ alert(firstName + this.name); }
}

Person.firstName; //undefined 접근할 수 없다.

var p = new Person("길동"); //객체 생성

p.firstName; //undefined 역시 접근할 수 없다.
p.name; //"길동" 접근 가능하다.

Person.prototype.tellme = function(){ alert('zzz'); }; //메서드 추가!!
p.tellme(); //zzz라는 팝업이 뜬다!! 즉 객체 프로토타입 변경 가능!!

p["firstName"]; //undefined 마찬가지로 접근할 수 없다.
p["name"]; //"길동" 접근 가능하다.




객체 멤버의 추가/제거
var obj = { propertyB : ‘b’, propertyC : ‘c’ };

obj.propertyA = new Date(); // 멤버 추가
delete obj.propertyB; // 멤버 삭제
obj.propertyC = ‘cc’; // 멤버 대체




생성자 개념이 많이 다르면서 어렵네..
진짜 어렵다..


생성자.. 프로토타입.. constructor.
p.constructor.prototype == Person.prototype // true..




비공개 멤버 구현

function A() { //내부 지역 변수 var _localX = 7; //공개 접근 메소드 this.getX = function(){ return _localX; }; //10qhek 작은 수만 속성값으로 입력받는다. this.setX = function(x) { if(x<10) { _localX = x; return _localX; } }; } var aaa = new A(); // 객체 생성 aaa.getX(); // 7 aaa.setX(0); // 0 할당 aaa.getX(); // 0


실제 JAVA의 private 멤버 변수를 참조하는 getter, setter와 비슷하다는 느낌을 받는다.
자바스크립트로도 private 등 자바의 접근 제한자적 특성을 부여할 수 있는 것 같다.






클로저를 통한 접근
function outer() {
	var _x = 0;

	function _privateA() {
		return ++_x;
	}

	function _privateB() {
		return (_x +=2);
	}

	return { publicA : _privateA, publicB : _privateB }; //객체를 리턴!!
}

var o1 = outer(); //새로운 인스턴스가 생성되어 반환된다.
o1.publicA(); //1 : 1씩 증가시킨 값을 반환
o1.publicB(); //3 : 기존값에서 2 증가시킨 값을 반환

var o2 = outer(); //새로운 인스턴스가 생성되어 반환된다.
o2.publicA(); // 다시 1
o2.publicB(); // 다시 3
기본적으로 자바 스크립트는 정보 은닉이 되지 않지만, 함수 내의 변수는 함수 안에서만 통용된다는 것을 활용해서 정보 은닉을 구현할 수 있다. 클로저!!




프로토타입 체인★

인스턴스는 내부에 자신의 프로토타입 객체를 가리키는 숨겨진 멤버 _proto_ 를 가진다. 이 멤버를 이용해 프로토타입 체인이 구성된다.
모든 객체의 최상의 부모는 Object다. 자바스크립트의 상속은 프로토타입 기반의 상속이다.

A 라는 생성자( 뭐 대충 function으로 만드는 객체나.. { }로 만드는 객체 로 생성한 ..)로 인스턴스를 생성하면 멤버를 찾을 경우 먼저 생성자에서 찾을 것이고 못찾으면 A.prototype 으로 가고,
A.prototype == new Object 이므로 여기 없으면 더 상위인 Object로 가고 Object에서 그 다음으로 Object.prototype으로까지 가게 된다.

재미있게도.. Object를 재정의할 수도 있다는데.. 이건뭐..





Object 인스턴스 생성절차
생성된 인스턴스, this에 할당
생성자 Person을 호출
인스턴스 멤버 초기화
비공개 _proto_ 속성 추가
this가 가리키는 최종 인스턴스를 생성자의 반환값으로 반환 



Person.prototype = new Korean();
 이런식으로 하면 자바스크립트에서도 상속이 가능하다.

function Person(name){ this.name = name }; //사람 객체 선언
function Korean(age){ this.age = age; }; //한국인 객체 선언

Person.prototype.species = "Human"; //이렇게 하면 Person 원형에 species 변수가 추가된다.
Korean.prototype.nationality = "Korea"; //마찬가지로 Korean 원형에 변수가 추가된다.

var kildong = new Person("Kil-Dong"); //길동이라는 사람 객체 생성
kildong.species; //"Human" 이라는 결과가 나온다!

//이렇게 하면 원형에 함수가 추가된다.
Person.prototype.speak = function(){ console.log(this.name + "입니다."); };

kildong.speak(); //Kil-Dong입니다. 라는 결과가 출력된다. 바꾼 것이 바로 적용된 것에 주목!!

var boy = new Korean(29); //소년이라는 한국인 객체 생성
boy.age; //29 가 출력된다.

Korean.prototype = new Person(); //new로 생성해서 넣어주네? 이러면 상속을 받게 된다.

boy.species; // undefined 상속 받기 전에 생성한 객체에는 적용이 안된다. ㅠㅠㅠㅠㅠ

var newBoy = new Korean(18); //그래서 새로 생성
newBoy.species; //"Human" 이제는 상속받은 뒤에 새로 생성했기 때문에 정상적으로 출력된다.
newBoy.name; //undefined 이건 아직 안넣어 줬기 때문에 ㅋㅋ

Korean.prototype
	▶Person
		▶name: undefined
		▶__proto__: Person
			▶constructor: function Person(name){ this.name = name }
			▶speak: function (){ console.log(this.name + "입니다."); }
			▶species: "Human"
			▶__proto__: Object




call / apply
func.call(객체, 인자값);
객체에는 컨텍스트를 전달하고,  나머지 인자값은 함수 호출시에 인자와 같다.
예를 들어, add(1,2) 로 호출하던 함수라면, add.call(null, 1, 2)와 같은 효과를 갖는다.
null 자리에는 컨텍스트이므로 여기에 객체를 넣어주면 해당 객체를 this로 인식한다.

 
func.apply(객체, 인자값 배열);
위의 예에서 Person을 상속받았지만 name은 선언할 때 넣어주어야하는 값이므로 넣어주기가 뭐하다.
이때 이걸 사용해서 Person.apply(this, 이름값) 이란 코드를 넣어주면, 해당값을 할당해주는게 가능하다!!
function Person(name){ this.name = name; }
Person.prototype.species = “human”;

function Korean(name,city) {
	Person.apply(this,[name]);
	this.city = city;
}
Korean.prototype.nationality = “korea”;

Korean.prototype = new Person();
Korean.prototype.constructor = Korean;

delete Korean.prototype.name;
var jay = new Korean(“jay”,”bunddang”);



멤버확장
자바스크립트에서는 유연하게 메서드를 프로토타입에 추가할 수 있다는 사실을 활용해서 부모 객체 타입의 모든 속성을 자식 객체에 복사해 넣는 메서드를 추가해 일종의 상속 개념처럼 활용하는 방법이다.
Object.prototype.extend = 
function(parent) {
	for(var property in parent) {
		this[property] = parent[property];
	}
};



JSON
JavaScript Object Notation은 말 그대로 자바스크립트에서 사용하는 객체를 평이한 텍스트로 표현하는 방식이다.


네임스페이스 구현
var People = People || {}; //루트 객체 생성

People.createNamespace = 
	function(namespace) {
		var parts = namespace.split('.');
		var current = this;
		
		for (var i in parts) {
			if(!current[parts[i]]) { //A.a 는 A[a]로 표현할 수 있다!!
				current[parts[i]] = {}; //없으면 네임스페이스(=객체) 생성
			}
			
			current = current[parts[i]];
		}
};

People.createNamespace("People.Korea.Seoul");



메서드 체인 패턴 구현
각 메서드가 자기 자신을 반환해서 각 메서드를 연속해서 수행할 수 있도록 구현하는 것.

var a = {
		method1 : function() { console.log("method1"); },
		method2 : function() { console.log("method2"); }, 
		method3 : function() { console.log("method1"); } 
	};

a.method1(); // method1
a.method2(); // method2

//위와 같은 코드를,

var a = {
		method1 : function() { console.log("method1"); return this; },
		method2 : function() { console.log("method2"); return this; }, 
		method3 : function() { console.log("method1"); return this; } 
	};

a.method1().method2().method3(); //이렇게 한번에 호출할 수 있다!!

//method1 method2 method1




JavaScript 관련 포스팅 더보기