INSIDE JAVASCRIPT – 5. 클로저 + 6. 객체지향 프로그래밍 – 지난 5장발표...



INSIDE JAVASCRIPT – 5. 클로저 + 6. 객체지향 프로그래밍 – 지난 5장발표...

0 0


JsPPT5-6


On Github Lunatical / JsPPT5-6

INSIDE JAVASCRIPT

5. 클로저 + 6. 객체지향 프로그래밍

2015.01.05

Kim-down

지난 5장발표...

망했스! 답이없스! 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 를 참조하는 클로저.

그러면.

3개의 클로저가 서로 다른 환경을 가지도록 작성해보자

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 가 서로 다른 환경을 가지고 있는것을 확인할 수 있다.

클로저는 거의 이런 용도로 사용됩니다.

객체지향 프로그래밍

  • 클래스, 생성자, 메서드
  • 상속
  • 캡슐화

클래스, 생성자, 메서드

c++, Java 같은경우는 class라는 키워드를 이용하여 클래스를 만들수 있다. 하지만 자바스크립트엔 그딴거없다. 그래도 함수객체로 많은것을 구현해낼 수 있고 클래스, 생성자, 메서드도 함수로 구현이 가능하다.!
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함수의 구현이 좋지못하다

Why?

각자 자기영역에서 공통적으로 사용할 수 있는 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()함수는 인자로 들어온 객체를 부모로하는 자식 객체를 생성하여 반환한다.
새로운 빈 함수객체 F를 만들고 F.prototype 프로퍼티에 인자로 들어온 객체를 참조시킨다. 이렇게 반환된 객체는 부모객체의 프로퍼티에 접근할 수 있고. 자신만의 프로퍼티를 만들 수 있다. 이렇게 프로토타입의 특성을 이용해서 상속시키는것이 프로토타입 기반의 상속이다. 참고로 create_object()는 ECMAScript 5 에서 Object.create() 함수로 제공되므로.. 구현할 필요는없다. 이해를 위한 코드일 뿐이다.
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(){...};
                        
이렇게 구현할순 있지만.. 코드가 지저분해지기 십상이다.. 보다 깨끗한 방법을 생각해보자!.
자바스크립트에서는 범용적으로 extend() 함수를 추가해서 객체에 자신이 원하는 객체 혹은 함수를 추가시킨다!.
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
                        
extend() 함수를 이용하여 student 객체를 확장시켰다!. extend()함수는 사용자에게 유연하게 기능을 확장시킬수 있게 하는 주요 함수이고 상속에서도 자식 클래스를 확장할때 유용하게 사용되므로 기억해두자.

클래스 기반의 상속

클래스의 역할을 하는 함수로 상속을 구현한다.
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을 실행시킨다 이런방식으로 자식 클래스의 인스턴스에서도 부모 클래스의 생성자를 실행 시킬 수 있다.

좀더 발전시켜보자!

현재는 자식클래스가 부모클래스의 객체를 프로토타입 체인으로 직접 접근한다! 부모클래스의 인스턴스와 자식 클래스의 인스턴스는 서로 독립적일 필요가 있다.! 자식 클래스의 prototype이 부모클래스의 인스턴스를 참조한다. 이 구조라면 자식 클래스의 prototype에 메소드를 추가할 때 문제가 된다. 이는 부모 클래스의 인스턴스인 you와 자식 클래스의 인스턴스 me 가 독립적일 필요가 있음을 의미한다. 두 클래스의 프토토타입 사이에 중개자를 하나 만들어보자
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 가 독립적이게 되었다.

캡슐화

c++과 Java에서는 public, private 멤버를 선언함으로서 해당 정보를 외부로 노출시킬것인지 결정. 자바스크립트에서는 키워드를 지원하지않지만 불가능한것은 아니다.
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 의 프로토타입에 메서드가 담겨있어 확장도 용이하다.

이러한 패턴을 모듈패턴이라고 부르며 자주사용되는 유용한 패턴이다.

지금까지 자바스크립트에서 객체지향 프로그래밍을 할 수있는 방법을 알아보았지만. 사실 자바스크립트의 객체지향적인 구현을 기존의 전통적인 방법에 맞추는것은 잘못된 방법이다. 객체지향 프로그래밍 자체가 재사용성 및 유지보수의 용이성을 높이려고 연구된 방법이지만

자바스크립트 역시 이러한 목적을 달성하려는 자바스크립트만의 특성이 있으므로 이를 잘 활용하여 기존 틀에 얽매이지 않는다면 보다 효율적인 프로그래밍을 할 수 있다.

END

Thanks!