2015.01.05
Kim-down망했스! 답이없스! Pt하는사람이 모른대!
Helpful notes will appear here
E-mail:
Name:
Age:
반복문 안에서 클로저를 생성함으로서 생긴문제. 어떤 필드에 포커스를 주더라도 나이에 관한 도움말이 표시된다.
function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
function setupHelp() {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
];
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus = function() {
return showHelp(item.help);
}
}
}
setupHelp();
onfocus이벤트에 지정한 함수가 클로저라서. 세개의 클로저가 만들어졌지만 각 클로저는 하나의 환경 (item) 을 공유한다. 반복문이 끝나고 onfocus콜백이 실행될때 item변수는 리스트의 마지막요소를 가르킬것이다.
onfocus에 들어간것은 showHelp(item.help) 을 리턴하는 함수지만 이것은 아직 실행되지않았다 해당 콜백이 실행되어야 onfocus에 showHelp이 리턴되기때문에 item은 리스트의 마지막요소를 참조한다.
setupHelp의 help 를 참조하는 클로저.Helpful notes will appear here
E-mail:
Name:
Age:
이전 코드와 달리 Help 메세지가 정상적으로 변경되는것을 볼 수 있다.
function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
function makeHelpCallback(help) {
return function() {
showHelp(help);
};
}
function setupHelp() {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
];
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
}
}
setupHelp();
전 함수와 크게 다른점은 makeHelpCallback 함수가 추가되어서 3개의 makeHelpCallback이 자유변수인 help를
지니고
각각의 showHelp 는 스코프체인으로 makeHelpCallback 함수의 help를 참조하게된다.
function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
function setupHelp() {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
];
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus = (function(help) {
return function() {
showHelp(help);
}
})(item.help);
}
}
setupHelp();
클로저를 이용해 캡슐화를 구현하는것을 다시한번 정리합니다. Private
var Counter = (function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
})();
alert(Counter.value()); /* 0 */
Counter.increment();
Counter.increment();
alert(Counter.value()); /* 2 */
Counter.decrement();
alert(Counter.value()); /* 1 */
익명함수 안에서 사용된 privateCounter 는 외부에서 접근할 수 없다 이 예제에서 세개의 public함수는 같은 환경을 공유하는 클로저이다.
익명함수가 카운터를 정의하고 이것을 Counter변수에 할당한다는것을 알수있을것이다. 이 함수를 다른 변수에 저장하고 여러개의 카운터를 만들 수도 있다.
var makeCounter = function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
};
var Counter1 = makeCounter();
var Counter2 = makeCounter();
alert(Counter1.value()); /* 0 */
Counter1.increment();
Counter1.increment();
alert(Counter1.value()); /* 2 */
Counter1.decrement();
alert(Counter1.value()); /* 1 */
alert(Counter2.value()); /* 0 */
Counter1, Counter2 가 서로 다른 환경을 가지고 있는것을 확인할 수 있다.
function Person(arg) {
this.name = arg;
this.getName = function() {
return this.name;
}
this.setName = function(value) {
this.name = value;
}
}
var me = new Person("down");
console.log(me.getName()); // Result : down
me.setName("Kimdown");
console.log(me.getName()); // Result : Kimdown
이 형태는 기존 객체지향 프로그래밍 언어에서 한 클래스 인스턴스를 생성하는 코드와 유사하다. Person 이 클래스이자 생성자의 역할을 한다. 자바스크립트에서의 클래스 기반의 객체지향은 기본적인 형태가 이와 같다. 클래스 및 생성자의 역할을 하는 함수가 있고. new 키워드로 인스턴스를 생성해 사용할 수 있다.
하지만 이 예제는 Person함수의 구현이 좋지못하다
각자 자기영역에서 공통적으로 사용할 수 있는 setName 함수와 getName 함수를 따로 생성하고 있다.
function Person(arg) {
this.name = arg;
}
Person.prototype.getName = function () {
return this.name;
}
Person.prototype.setName = function (value) {
this.name = value;
}
var me = new Person("down");
var you = new Person("you");
console.log(me.getName());
console.log(you.getName());
각 함수의 prototype 프로퍼티에 getName과 setName를 정의하여 각 객체가 따로 함수객체를 생성할 필요가 없게 만들어두었다!. 각 함수는 프로토타입 체인으로 setName 과 getName에 접근가능하다!
다음과 같이 자바스크립트에서 클래스 안의 메서드를 정의할 떄에는 프로토 타입 객체에 정의한 후 new 를 이용하여 생성한 객체에서 접근할 수 있게 하는것이 좋다.
열라좋다 그래도..좀 더 개선해보자.
Function.prototype.method = function(name, func) {
this.prototype[name] = func;
}
function Person(arg) {
this.name = arg;
}
Person.method("setName", function(value) {
this.name = value;
});
Person.method("getName", function() {
return this.name;
});
var me = new Person("me");
var you = new Person("you");
console.log(me.getName());
console.log(you.getName());
더글라스 크락포드가 제시한 메서드를 정의하는방법을 적용시킨 코드이다. 훨씬 더 보기 편해진것 같지않은가
여기까지 객체지향 프로그래밍에서 제시하는 개념중 클래스, 생성자, 메서드를 자바스크립트에선 어떻게 구현할 수 있는지 알아보았다.
이제 본격적으로 객체지향의 주요특성을 알아보자
function create_object(o) {
function F() {}
F.prototype = o;
return new F();
}
이 코드는 더글라스 크락포드가 자바스크립트 객체를 상속하는 방법으로 소개한 코드이다. 이 세줄의 코드를 이해한다면 자바스크립트에서 프로토타입 기반의 상속을 문제 없이 이해할 수 있다.
create_object()함수는 인자로 들어온 객체를 부모로하는 자식 객체를 생성하여 반환한다.
var person = {
name : "down",
getName : function() {
return this.name;
},
setName : function(arg) {
this.name = arg
}
};
function create_object(o) {
function F() {}
F.prototype = o;
return new F();
}
var student = create_object(person);
student.setName("me");
console.log(student.getName()); // Result : me
클래스에 해당하는 생성자 함수를 만들지도, 그 클래스의 인스턴스를 따로 생성하지도 않았다.
단지 부모객체에 해당하는 person 객체와 이 객체를 프로토타입 체인으로 참조가능한 student 자식객체를 만들어서 사용하였다. 이런방식으로 객체의 상속의 개념을 구현할 수 있다.
부모객체의 메서드를 그대로 상속받아 사용하는 방법을 알아보았다. 그리고 자식 객체는 자신의 메서드를 재정의하거나 추가로 확장 시킬수 있어야한다.
student.setAge = function(age){...};
student.getAge = function(){...};
이렇게 구현할순 있지만.. 코드가 지저분해지기 십상이다..
보다 깨끗한 방법을 생각해보자!.
var person = {
name : "down",
...
};
function create_object(o) {
...
}
function extend(obj, prop) {
if (!prop) { prop = obj; obj = this; }
for ( var i in prop ) obj[i] = prop[i];
return obj;
}
var student = create_object(person);
var added = {
setAge : function(age) {
this.age = age;
},
getAge : function() {
return this.age;
}
};
extend(student, added);
student.setAge(25);
console.log(student.getAge()); // Result : 25
function Person(arg) {
this.name = arg;
}
Person.prototype.setName = function(value) {
this.name = value;
};
Person.prototype.getName = function() {
return this.name;
};
function Student (arg) { }
var you = new Person("you");
Student.prototype = you;
var me = new Student("me");
me.setName("me");
console.log(me.getName());
Student 함수 객체를 만들어서, 이 객체가 Person함수의 객체 인스턴스를 참조하게 하였다.
이렇게하면 me 의 [[Prototype]] 링크가 생성자의 프로토타입인 Student.prototype을 가르키고
new Person()으로 만들어진 객체의 [[Prototype]] 링크는 Person.prototype을 가르키는
프로토타입이 형성된다.
따라서 me 는 Person.prototype 프로퍼티에 접근 가능하다.
그런데 Student는 빈객체이다..!
function Student(arg) {
Person.apply(this, arguments);
}
Student 함수 안에서 새롭게 생성된 객체를 apply 함수의 첫번째 인자로 넘겨 Person을 실행시킨다
이런방식으로 자식 클래스의 인스턴스에서도 부모 클래스의 생성자를 실행 시킬 수 있다.
function Person(arg) {
this.name = arg;
}
Person.prototype.setName = function(value) {
this.name = value;
};
Person.prototype.getName = function() {
return this.name;
};
function Student(arg) {}
function F() {};
F.prototype = Person.prototype;
Student.prototype = new F();
var me = new Student();
me.setName("down");
console.log(me.getName()); // down
이전 코드와 크게 다르지 않다. Person의 프로토타입을 지니는 빈 객체를 F() 생성자를 통해 만들고
그 빈 객체를 me 가 참조하게된다.
자식 클래스의 prototype 객체가 빈 객체이다.
따라서 이곳에 자식 클래스의 확장 메소드와 데이터가 들어갈 수 있다.
각 클래스 객체인 me 와 you 가 독립적이게 되었다.
var Person = function(arg) {
var name = arg ? arg : "down";
return {
getName : function() {
return name;
},
setName: function(arg) {
name = arg;
}
};
}
var me = Person();
console.log(me.getName());// Result: down
이것이 자바스크립트 에서 할 수 있는 기본적인 정보 은닉 방법이다.
하지만 이 코드의 경우 리턴받은 객체가 Person함수의 프로토타입에는 접근할 수 없다는 단점이 있다 (리턴받은것이 함수가 아님.)
이는 Person을 부모로하는 프로토타입을 이용한 상속을 이용하기 힘들다는것을 의미한다.
이를 보완하기위해 함수를 반환하는 코드로 바꾸어보자.
var Person = function(arg) {
var name = arg ? arg : "down";
var Func = function() {}
Func.prototype = {
getName : function() {
return name;
},
setName : function(arg) {
name = arg;
}
};
return Func;
}();
var me = new Person();
console.log(me.getName());
클로저를 이용해 name 에 접근 불가능하게 설정하였다. 즉시 실행함수로 반환되는 Func가 클로저가되고 name 이 자유변수가 된다. me 변수가 리턴받는 Func 의 프로토타입에 메서드가 담겨있어 확장도 용이하다.
이러한 패턴을 모듈패턴이라고 부르며 자주사용되는 유용한 패턴이다.지금까지 자바스크립트에서 객체지향 프로그래밍을 할 수있는 방법을 알아보았지만. 사실 자바스크립트의 객체지향적인 구현을 기존의 전통적인 방법에 맞추는것은 잘못된 방법이다. 객체지향 프로그래밍 자체가 재사용성 및 유지보수의 용이성을 높이려고 연구된 방법이지만
자바스크립트 역시 이러한 목적을 달성하려는 자바스크립트만의 특성이 있으므로 이를 잘 활용하여 기존 틀에 얽매이지 않는다면 보다 효율적인 프로그래밍을 할 수 있다.