Class Template 1
아래의 template 은 DataTables 의 plugin 인 TableTools 의 source 에서 가져왔다.
var TableTools; TableTools = function( oDT, oOpts ) { /* Santiy check that we are a new instance */ if ( ! this instanceof TableTools ) { alert( "Warning: TableTools must be initialised with the keyword 'new'" ); } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Public class variables * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /** * @namespace Settings object which contains customisable information for TableTools instance */ this.s = { "that": this, "dt": dtSettings }; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Public class methods * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /** * Retreieve the settings object from an instance * @method fnSettings * @returns {object} TableTools settings object */ this.fnSettings = function () { return this.s; }; /* Constructor logic */ TableTools._aInstances.push( this ); this._fnConstruct( oOpts ); return this; }; TableTools.prototype = { root : this, _selectBox : null, /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Public methods * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /** * Retreieve the settings object from an instance * @returns {array} List of TR nodes which are currently selected * @param {boolean} [filtered=false] Get only selected rows which are * available given the filtering applied to the table. By default * this is false - i.e. all rows, regardless of filtering are selected. */ "fnGetSelected": function ( filtered ) { ... return out; }, /** * Get the data source objects/arrays from DataTables for the selected rows (same as * fnGetSelected followed by fnGetData on each row from the table) * @returns {array} Data from the TR nodes which are currently selected */ "fnGetSelectedData": function () { var out = []; var data=this.s.dt.aoData; var i, iLen; ... return out; } }; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Static variables * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /** * Store of all instances that have been created of TableTools, so one can look up other (when * there is need of a master) * @property _aInstances * @type Array * @default [] * @private */ TableTools._aInstances = []; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Static methods * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /** * Get an array of all the master instances * @method fnGetMasters * @returns {Array} List of master TableTools instances * @static */ TableTools.fnGetMasters = function () { ... return a; }; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Constants * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ TableTools.buttonBase = { // Button base "sAction": "text", "sTitle": "", "sToolTip": "" }
Class template 2
var parentFunction = function(options){ var privateValue = 'privateValue'; this.publicValue = 'publicValue'; var vars = { privateVar: ‘original Value’, another: ‘test’ } var root = this; this.construct = function(options){ $.extend(vars , options); } this.childFunction = function(){ alert(vars.privateVar); } this.construct(options); } var exampleObject = new parentFunction({ privateVar : ‘new Value’ }); exampleObject.childFunction();
위에서 root 는 나중에 inner function 등에서 parent class 에 접근하기 위해서 만들어놨다고 한다.[ref. 1]
Class.prototype
위의 방법(class template 2)의 단점은 class 를 new 할 때 마다 함수를 계속 생성해 낸다고 한다.[ref. 2]그래서 ref.2 에서 정의하는 방법을 사용하는 것이 나을 듯 하다. 이것은 아래에서 이야기하는 TypeScript 에 의해 생성되는 부분과도 비슷하다.
function Apple (type) { this.type = type; this.color = "red"; } Apple.prototype.getInfo = function() { return this.color + ' ' + this.type + ' apple'; };
그리고 이 방법을 사용하면 아래 "상속" 부분에서 제안하는 상속 방법을 사용하기 용이 하다.
private class
아래 "참고" 부분에도 적혀있지만, 따로 private 을 만들 수는 없다. 개인적으로 이경우는 python 의 convention 을 따르는 것이 좋을 듯 하다. 그래서 private method 에 해당하는 녀석들도 public 처럼 prototype 으로 만들어 놓고, 이름만 _(underscore) 로 시작하도록 하고, 사용하는 쪽에서 알아서 사용하도록 하는 것이다.prototype
prototype 에 관한 설명은 ref. 4 에 설명이 되어있다. 모든 object 는 __proto__ 라는 property(단순히 변수라고 생각하자.) 를 갖는다고 한다. javascript 에서 contructor 가 호출될 때 이 __proto__ 가 prototype 을 가리키게 설정된다고 한다.class 내부에서 handler 를 작성할 때
만약 java 에 익숙해서 java 처럼 class 내부에 event handler 를 만들고, event handler 안에서 this 를 사용하는 경우 원하는 대로 동작하지 않는다. 아래의 경우에 _defaultKeyDownHandler 는 document.getElementById(this._id) 가 가져가 버린 것이어서 _defaultKeyDownHandler 의 주인(owner)가 document.getElementById(this._id) 가 되어버린다. 그래서 this 는 document.getElementById(this._id) 가 된다.this 가 변경되는 것에 대한 자세한 글은 see also. 1 을 참고하자.
function CandidateInput(id, url){ this._id = id; document.getElementById(this._id).onkeydown = this._defaultKeyDownHandler; } CandidateInput.prototype._defaultKeyDownHandler = function(event){ console.log(cc); console.log(this._id); // this 는 "document.getElementById(this._id)" 가 된다. }이런 경우에 해결책에 대해서는 ref. 3 에 좋은 대답이 있다. 위의 코드로 수정을 하자면, 아래처럼 function call 을 한 번 더 하도록 하는 것이다. 아래처럼 작성하면, function 의 주인(owner)가 여전히 class CandidateInput 이기 때문에 this 가 CandidateInput 이 된다.
function CandidateInput(id, url){ this._id = id; var that = this; document.getElementById(this._id).onkeydown = function(event) {that._defaultKeyDownHandler(event)}; } CandidateInput.prototype._defaultKeyDownHandler = function(event){ console.log(cc); console.log(this._id); // this 는 "CandidateInput" 가 된다. }
Class by TypeScript
그런데, 좀 잘 안되는 듯 하다.차라리 typeScript 를 이용하는 것이 좋을 듯 하다.
http://www.typescriptlang.org/Playground/
아래는 typescript 를 통해 만든 class 를 이용한 예제이다. chrome extension 에 쓰인 코드라서 chrome 관련 API 들이 쓰였다.
// Copyright (c) 2013 Namh
var id = "a";
var pw = "W";
////////////////////////////////////////////////////////
//// TestClass class
////
var TestClass = (function(options){
var vars = {
another: "test",
}
function TestClass(){
this.STATE_INIT = 0;
this.STATE_LOGIN = 1;
this.STATE_CDNPAGE = 2;
this.state = this.STATE_INIT;
this.rapUrl = "http://daum.net/";
this.tabId = 0;
}
TestClass.prototype.changeState= function(newState){
this.state = newState;
}
TestClass.prototype.doLogin = function(tabId){
this.changeState(this.STATE_LOGIN);
}
TestClass.prototype.goToCdnPage = function(tabId){
this.changeState(this.STATE_CDNPAGE);
}
TestClass.prototype.extensionClickListener = function(tab){
testClass.tabId = tab.id;
testClass.state = testClass.STATE_INIT;
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab){
// The reason of using testClass instead of this :
// this is inner function so that the "this" does not work.
if (tabId == testClass.tabId
&& changeInfo.status === 'complete') {
console.log("start updated listener");
switch(testClass.state){
case testClass.STATE_INIT:
testClass.doLogin(tabId);
break;
case testClass.STATE_LOGIN:
testClass.goToCdnPage(tabId);
break;
default:
break;
}
}
});
chrome.tabs.update(tab.id, {url: testClass.rapUrl });
}
return TestClass;
})();
//
// Start here ...
//
var testClass = new TestClass();
chrome.browserAction.onClicked.addListener(testClass.extensionClickListener);
참고사항
- private 이나 static 등을 굳이 만들 수는 있다.(즉, 작성자가 알아서 잘 써야 한다.)
- static 변수는 prototype 을 이용해서 정의할 수 있다.
Static class
위의 link 글이 도움이 된다. javascript 에서 정의하는 모든 것들은 object 가 되는데, 만약 우리가function Test(){ ... }이런식으로 함수를 정의하면, Test class 가 만들어진 것이다. 이 class 는 기본적으로 prototype 을 갖고 있으며 우리는 Test.prototype 에 접근할 수 있게 되는 것이다. 그러므로 OOP 에서 static 함수는 간단하게
Test.myStaticMethod = function(){ ... }이런 식으로 작성하면 된다.
Test.prototype.myMethod = function(){...}
그러면 위에서 작성하는 Test.prototype.myMethod 은 무엇일까? 이 prototype 이란 녀석은 function 을 정의하게 되면 자연적으로 생기는 변수이다.(보통 property 라고 부른다.)
자세한 사항은 아래를 참고하자.
Closure
closure 를 방금 발견(?) 했다. oop 적인 관점에서 programming 을 하기에는 closure 가 훨씬 적합해 보인다. [see also. 5 ] 예전에 TypeScript 도 봤지만, 그것보단 closure 가 좀 더 javascript 안으로 들어와 있어 훨씬 사용하기 좋은 듯 하다. 일단 아직 써보지 않았지만 구조적인 programming 에 훨씬 적합할 듯 보인다. see also. 6 에서 closure 에 대한 비판도 있으니 참고하자.조금 써보고 난 후의 느낌은 Java 등의 oop 개발에 익숙한 사람이라면, closure 가 괜찮을 듯 하다. 자바의 package 나 import 에 해당하는 기능을 구현 해 주어서, 여러 file 로 class 를 만들고 필요한 곳에서 import 를 해서 사용하면 된다. 물론 결국 compiler 에 의해 하나로 묶이는 것이지만, 이렇게 묶을때 필요한 녀석들만 가져다가 묶어준다.
closure 는 compile 을 해야 이점이 있다. 하지만 compile 을 하지 않은 상태에서도 굳이 javascript code 를 커다란 한 파일로 만들어 놓지 않아도 되는 점은 좋다.
불편한 점은 이미 구축된 server side 에서 compile 하지 않은 code 를 이용하기가 조금 용이하지 않다. 하지만 그렇다고 해도 불가능한 것은 아니다. 자세한 부분은 ref. 5 를 참고하도록 하자.
상속 Inheritence
- JavaScript Inheritance Patterns by David Shariff
- Introduction to Object-Oriented JavaScript - JavaScript | MDN
/********************************************************************** * * Constructor * **********************************************************************/ function UserNameChartParameter (subId, startDate, endDate, trafficType){ ChartParameter.call(this, "testId", "pw", subId, startDate, endDate, trafficType); } /********************************************************************** * * Inheritance * **********************************************************************/ UserNameChartParameter.prototype = Object.create(ChartParameter.prototype); UserNameChartParameter.prototype.constructor = UserNameChartParameter;
Namespace
javascript 를 사용하다보니, namespace 를 사용하고 싶어졌다. 그래서 찾아보니 ref.6 에 좋은 방법이 있었다. self-executing anonymous function 을 이용하는 데 코드는 아래와 같다.(IIFE, Immediately-Invoked Function Expression)
(function( myspace, $, undefined ) { //Private Property var isHot = true; //Public Property myspace.ingredient = "Bacon Strips"; //Public Method myspace.fry = function() { var oliveOil; addItem( "\t\n Butter \n\t" ); addItem( oliveOil ); console.log( "Frying " + myspace.ingredient ); }; //Private Method function addItem( item ) { if ( item !== undefined ) { console.log( "Adding " + $.trim(item) ); } } //-- static vars and methods var my2ndSpace = { igredient2 : "2nd Ingredient", fry: function(){ console.log("fry"); }, fryAgain : function(){ console.log("fryAgain"); } } myspace.my2ndSpace = my2ndSpace; //--/ static vars and methods }( window.myspace = window.myspace || {}, jQuery ));
CoffeeScript
지금은 이녀석을 주로 사용한다. coding 이 간결하고, 속도가 나는 느낌이랄까. 개인적으로 애용하고 있다. 위에서 얘기한 class 나 상속(extends) 같은 녀석을 제공한다.See Also
- The this keyword : http://www.quirksmode.org/js/this.html
- javascript this : http://the-earth.tistory.com/147
- http://www.phpied.com/3-ways-to-define-a-javascript-class/, Singleton
- Custom JavaScript classes and packages using goog.provide() and goog.require()
- https://developers.google.com/closure/library/
- http://www.sitepoint.com/google-closure-how-not-to-write-javascript/
- Closure: The world's most misunderstood JavaScript library by gmosx, 09 Aug 2010
- ECMA-262-3 in detail. Chapter 7.2. OOP: ECMAScript implementation.
Reference
- http://www.scorchsoft.com/blog/how-to-write-object-oriented-javascript/
- [번역] 자바스크립트 클래스를 정의하는 3가지 방법 (3 ways to define a JavaScript class)
- Class methods as event handlers in javascript?
- The Javascript Guide to Objects, Functions, Scope, Prototypes and Closures
- [컴][웹] closure compiler 로 compiled javascript code 를 debugging 하는 방법
- How do I declare a namespace in JavaScript? - Stack Overflow
댓글 없음:
댓글 쓰기