목차

Javascript 객체지향 프로그래밍

클래스 만들기

원칙적으로 말해서 JS에는 class는 존재하지 않는다. 단지, 생성자 함수만 존재할 뿐이다.

아래는 Pet 이라는 클래스를 생성한다.

function Pet(name) {
    this.name = name; // name 필드 생성
}
 
Pet.prototype.age = 0; // age 필드 생성
 
// toString 메소드 생성
Pet.prototype.toString = function() {
    return "Pet name : " + this.name + ", age : " + this.age;
}
 
var mong = new Pet("mong"); // 객체 생성
mong.age = 6;
alert(mong);

상속

상속할 때 중요한 부분은 Dog.prototype = new Pet()Dog.prototype.constructor = Dog 부분이다.

// 위에서 이어서, Pet을 상속하는 Dog 생성
function Dog(name, age, breed) {
    Pet.call(this, name);
    this.age = age;
    this.breed = breed;
}
 
// Dog.prototype이 Pet.prototype으로부터 상속하도록 한다.
Dog.prototype = new Pet();
 
// Pet 함수를 가리키는 생성자를 Dog 함수를 가리키도록 변경한다.
Dog.prototype.constructor = Dog;
 
// toString() 메소드 오버라이드
Dog.prototype.toString = function() {
    // 필요할 경우 부모클래스 메소드도 호출 가능
    return "Dog " + Pet.prototype.toString.call(this) + ", breed : " + this.breed;
}
 
var miro = new Dog("Miro", 7, "Pomeranian");
alert(miro);
 
alert("Is miro a Dog? " + (miro instanceof Dog));
alert("Is miro a Pet? " + (miro instanceof Pet));
alert("Is miro an Object? " + (miro instanceof Object));

Class 없이 객체 생성하고 메소드 추가하기

JavaScript does not need classes를 참조하였다.

Class 객체의 method를 Event Handler Function으로 등록하기

Class 객체의 method는 원칙적으로 this가 객체를 가리켜야 하지만, 이벤트 핸들러로 등록되면 이벤트를 발생시킨 객체를 this로 가지게 되는 현상이 발생한다. 이 문제를 해결하려면 Class methods as event handlers in javascript? - Stack Overflow에 나온 방법을 사용해야 한다.

ClickCounter = function(buttonId) {
    this._clickCount = 0;
    var that = this;
    document.getElementById(buttonId).onclick = function(){ that.buttonClicked() };
}
 
ClickCounter.prototype = {
    buttonClicked: function() {
        this._clickCount++;
        alert('the button was clicked ' + this._clickCount + ' times');
    }
}

핵심은 이벤트 핸들러를 등록할 때 this를 that으로 매핑하고, that.buttonClicked()를 호출하도록 function()으로 감싸는 부분이다. 이렇게 하면 클래스 메소드에서는 this를 원래 객체를 가리키는 것으로써 온전하게 사용할 수 있다.

setInterval / setTimeout

setInterval과 setTimeout에서도 DOM 이벤트 바인딩과 동일한 원리가 적용된다.

var A = function(v) {
  this.value = v;
  this.intervalId = null;
};
 
A.prototype.load = function() {
  console.log(new Date() + " : " + this.value);
};
 
A.prototype.start = function() {
  var that = this;
  this.intervalId = setInterval(function() { that.load(); }, 2000);
};
 
A.prototype.stop = function() {
  clearInterval(this.intervalId);
};
 
var a = new A('hello');
a.start();
 
//... after a minute
 
a.stop();

위에서 A.prototype.start 함수를 보면된다.

John Resig Class.extend

// "use strict"가 가능하도록 살짝 수정
/*
 * Simple JavaScript Inheritance By John Resig http://ejohn.org/
 * http://ejohn.org/blog/simple-javascript-inheritance/ MIT Licensed.
 */
// Inspired by base2 and Prototype
(function () {
    "use strict";
 
    var initializing = false, fnTest = /xyz/.test(function () {
        xyz;
    }) ? /\b_super\b/ : /.*/;
 
    // The base Class implementation (does nothing)
    window.Class = function () { // edited for use strict
    };
 
    // Create a new Class that inherits from this class
    Class.extend = function (prop) {
        var _super = this.prototype;
        // Instantiate a base class (but only create the instance,
        // don't run the init constructor)
        initializing = true;
        var prototype = new this();
        initializing = false;
 
        var name;
 
        // Copy the properties over onto the new prototype
        for (name in prop) {
            // Check if we're overwriting an existing function
            prototype[name] = typeof prop[name] === "function" && typeof _super[name] === "function"
                            && fnTest.test(prop[name]) ? (function (name, fn) {
                    return function () {
                        var tmp = this._super;
 
                        // Add a new ._super() method that is the same method
                        // but on the super-class
                        this._super = _super[name];
 
                        // The method only need to be bound temporarily, so we
                        // remove it when we're done executing
                        var ret = fn.apply(this, arguments);
                        this._super = tmp;
 
                        return ret;
                    };
            }(name, prop[name])) : prop[name];
        }
 
        // The dummy class constructor
        function Class() {
            // All construction is actually done in the init method
            if (!initializing && this.init)
                this.init.apply(this, arguments);
        }
 
        // Populate our constructed prototype object
        Class.prototype = prototype;
 
        // Enforce the constructor to be what we expect
        Class.prototype.constructor = Class;
 
        // And make this class extendable
        Class.extend = window.Class.extend; // edited for use strict
 
        return Class;
    };
}());
// 용례
 
var Person = Class.extend({
  init: function(isDancing){
    this.dancing = isDancing;
  },
  dance: function(){
    return this.dancing;
  }
});
 
var Ninja = Person.extend({
  init: function(){
    this._super( false );
  },
  dance: function(){
    // Call the inherited version of dance()
    return this._super();
  },
  swingSword: function(){
    return true;
  }
});
 
var p = new Person(true);
p.dance(); // => true
 
var n = new Ninja();
n.dance(); // => false
n.swingSword(); // => true
 
// Should all be true
p instanceof Person && p instanceof Class &&
n instanceof Ninja && n instanceof Person && n instanceof Class