===== 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