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 의 프로토타입에 메서드가 담겨있어 확장도 용이하다.
이러한 패턴을 모듈패턴이라고 부르며 자주사용되는 유용한 패턴이다.지금까지 자바스크립트에서 객체지향 프로그래밍을 할 수있는 방법을 알아보았지만. 사실 자바스크립트의 객체지향적인 구현을 기존의 전통적인 방법에 맞추는것은 잘못된 방법이다. 객체지향 프로그래밍 자체가 재사용성 및 유지보수의 용이성을 높이려고 연구된 방법이지만
자바스크립트 역시 이러한 목적을 달성하려는 자바스크립트만의 특성이 있으므로 이를 잘 활용하여 기존 틀에 얽매이지 않는다면 보다 효율적인 프로그래밍을 할 수 있다.