===== Javascript 객체지향 프로그래밍 =====
* [[http://msdn.microsoft.com/en-us/magazine/cc163419.aspx|Advanced Web Applications With Object-Oriented JavaScript]] JavaScript OO에 관한 매우 깊이 있는 소개. 필독 할 것.
* [[https://developer.mozilla.org/en/Introduction_to_Object-Oriented_JavaScript|Introduction to Object-Oriented JavaScript - MDN]] : JavaScript OO에 관한 쉬운 소개
* [[http://dev.naver.com/tech/ajaxui/ajaxui_3.php|Javascript와 객체지향 프로그래밍 - 네이버 개발자센터]] : new 관련 설명에 오류 있음.
===== 클래스 만들기 =====
원칙적으로 말해서 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 없이 객체 생성하고 메소드 추가하기 =====
[[http://www.2ality.com/2011/11/javascript-classes.html?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+2ality+%282ality+%E2%80%93+technology%2C+life%29&utm_content=Google+Reader|JavaScript does not need classes]]를 참조하였다.
* 마치 class를 통해 생성한 객체처럼 필드와 메소드를 둘 수 있다. 메소드에서는 ''this.field''로 필드 값을 읽으면 된다.
var jane = {
name: "Jane",
describe: function() {
return "Person called " + this.name;
}
};
console.log(jane.describe()); // Person called Jane
* 클래스 없이 상속도 쉽게 가능하다.
var PersonProto = {
describe: function () {
return "Person called "+this.name;
}
};
var jane = {
__proto__: PersonProto,
name: "Jane"
};
var tarzan = {
__proto__: PersonProto,
name: "Tarzan"
};
console.log(jane.describe());
console.log(tarzan.describe());
===== Class 객체의 method를 Event Handler Function으로 등록하기 =====
Class 객체의 method는 원칙적으로 this가 객체를 가리켜야 하지만, **이벤트 핸들러로 등록되면 이벤트를 발생시킨 객체를 this로 가지게 되는 현상이 발생한다.**
이 문제를 해결하려면 [[http://stackoverflow.com/questions/229080/class-methods-as-event-handlers-in-javascript|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 =====
[[http://www.elated.com/articles/javascript-timers-with-settimeout-and-setinterval/|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 =====
* [[http://ejohn.org/blog/simple-javascript-inheritance/|John Resig - Simple JavaScript Inheritance]] : ''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