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
댓글 없음:
댓글 쓰기