전역을 피하는 자바스크립트 코딩 기법

객체 소유권과 밀접히 관련된 이론으로, 전역 변수나 함수 사용을 가능한 한 피해야 한다. 이는 스크립트가 실행되는 환경을 항상 일정하게, 관리하기 쉽게 만드는 일이다. 전역 변수는 단 하나만 생성하고 그 안에 다른 객체와 함수가 존재하도록 해야 한다. 전역 변수는 단 하나만 생성하고 그 안에 다른 객체와 함수가 존재하도록 해야 한다. 다음의 코드를 보자.


// 전역에 두 가지를 추가한다. 다음은 나쁜 코딩 사례이다.

var name = "Woonohyo";

function sayName() {

alert(name);

}


위 코드는 전역에 변수 name과 함수 sayName()을 추가한다. 하지만 위의 방법보다는 다음과 같이 이들을 모두 포함하는 객체를 만드는 편이 낫다.


// 전역에는 단 하나만 추가하자.

var MyApplication = {

name = "Woonohyo";

sayName: function() {

alert(this.name);

}

};


고쳐 쓴 코드에서는 전역 객체를 MyApplication 단 하나만 정의하며 name 변수와 sayName() 함수는 모두 그 안에 존재한다. 이렇게 하면 이전 코드의 문제 두 가지를 해결할 수 있다. 


1. 변수 name이 다른 기능과 관련 있을 수도 있는 window.name 프로퍼티를 덮어쓰지 않는다.

2. 기능을 어디서 호출하는지 혼란스럽지 않다. 

MyApplication.sayName()을 호출하는 코드만 보아도, 문제가 있을 때는 MyApplication을 정의한 코드를 살펴봐야 함을 알 수 있다.


이렇게 전역에 단 하나의 객체만을 추가하는 방법이 '네임스페이스'라는 개념이며 야후! 사용자 인터페이스(YUI) 라이브러리에 도입되어 유명해졌다. YUI 버전 2에서는 다음과 같이 여러 가지 네임스페이스를 통해 기능을 추가했다.


  • YAHOO.util.Dom - DOM을 조작하는 메서드이다.

  • YAHOO.util.Event - 이벤트를 다루는 메서드이다.

  •  YAHOO.lang - 저수준 언어 기능과 관련된 메서드이다.

YUI에서는 전역 객체 YAHOO가 컨테이너이며 이 안에 다른 객체가 정의된다. 이런 식으로 단순히 기능을 한데 묶기 위해 사용하는 객체를 '네임스페이스'라고 한다. YUI 라이브러리는 전체가 이 개념 위에서 만들어졌으므로 같은 페이지에서 다른 자바스크립트 라이브러리와 문제 없이 공존할 수 있다.

네임스페이스에 쓸 전역 객체 이름을 정할 때는 모든 사람이 동의하고 다른 라이브러리에서 쓰지 않을만큼 충분히 고유하게 정해야 한다. 대개는 YAHOO나 Wrox처럼 회사 이름을 쓰면 된다. 일단 네임스페이스를 만들면 다음과 같이 기능을 한데 묶는다.


// 전역 객체 생성

var Woonohyo = {};


// Professional Javascript의 네임스페이스 생성

Woonohyo.ProJS = {};


// 기존에 사용한 다른 객체 등록

Woonohyo.ProJS.EventUtil = { ... };

Woonohyo.ProJS.CookieUtil = { ... };


위 예제에서는 전역인 Woonohyo에 네임스페이스를 생성한다. 이 포스트에 있는 코드를 모두 Woonohyo.ProJS 네임스페이스에 두면 다른 저자들도 자신의 코드를 Woonohyo 객체에 추가할 수 있다. 모두가 이 패턴을 따른다면 다른 개발자가 EventUtil이나 CookieUtil이란 객체를 사용하더라도 다른 네임스페이스에 존재하므로 걱정할 필요가 사라진다.

다음 예제를 살펴보자.


// Professional Ajax의 네임스페이스 생성

Woonohyo.ProAjax = {};


// 기존에 사용한 다른 객체 등록

Woonohyo.ProAjax.EventUtil = { ... };

Woonohyo.ProAjax.CookieUtil = { ... };


// ProJS의 객체는 그대로 사용 가능

Woonohyo.ProJS.EventUtil.addHandler( ... );


// ProAjax도 별도로 존재

Woonohyo.ProAjax.EventUtil.addHandler( ... );


네임스페이스를 쓰면 코드가 좀 늘어나긴 하지만 관리하기 쉬워지므로 그만한 가치는 충분하다. 또한 페이지에 다른 개발자의 코드가 존재하더라도 서로 침범하지 않게 된다.






NameSpace 타입


E4X에서는 네임스페이스를 Namespace 객체로 표현한다. Namespace 객체는 일반적으로 네임스페이스 접두사를 네임스페이스 URI와 연결하는데 사용하지만 접두사가 항상 필요하진 않다. 다음과 같이 Namespace 생성자를 사용해서 Namespace 객체를 생성한다.


var ns = new Namespace();


다음과 같이 URI나 접두사와 URI를 넘겨 Namespace 객체를 초기화할 수 있다.


var ns = new Namespace("http://www.woonohyo.net/");    // 접두사 없는 네임스페이스

var woonohyo = new Namespace("woonohyo", "http://www.woonohyo.net/");    // woonohyo 네임스페이스


Namespace 객체의 정보는 다음과 같이 prefix와 uri 프로퍼티로 접근한다.


alert(ns.uri);             // "http://www.woonohyo.net/"

alert(ns.prefix);        // undefined

alert(wrox.uri);        // "http://www.woonohyo.net/"

alert(wrox.prefix);    // "woonohyo" 


Namespace 객체에 접두사를 할당하지 않으면 prefix 프로퍼티는 undefined이다. 기본 네임스페이스를 생성하려면 접두사에 빈 문자열을 지정해야 한다.

XML 리터럴에 네임스페이스가 들어있거나 네임스페이스 정보가 들어 있는 XML 문자열을 XML 생성자로 파싱하면 Namespace 객체가 자동으로 생성된다. Namespace 객체가 생성되면 namespace() 메서드에 접두사를 넘겨 Namespace 객체의 참조를 가져올 수 있다. 다음 예제를 보자.


var xml = <woonohyo:root xmlns:woonohyo="http://www.woonohyo.net/">

<woonohyo:message> Hello, Woonohyo! </woonohyo:message>

</woonohyo:root>;



var woonohyo = xml.namespace("woonohyo");

alert(woonohyo.uri);

alert(woonohyo.prefix);


위 예제에서는 네임스페이스 정보를 담은 XML 리터럴을 생성했다. uri와 prefix 프로퍼티를 사용할 수 있는 시점부터 woonohyo 네임스페이스의 Namespace 객체에 namespace("woonohyo")로 접근이 가능하다.










저작자 표시 비영리 동일 조건 변경 허락
신고
Posted by 우너효

Transaction Isolation Level이란 사용자에게 트랜잭션의 안정성을 보장해 주는 수준을 뜻한다.


4단계로 되어있으며, 각 단계 별로 안정성과 성능이 달라진다.



  • Read Uncommitted (가장 낮은 수준)
  • Read Committed
  • Repeatable Read
  • Serializable (가장 높은 수준)

실습용 테이블을 만들어서 살펴보자

CREATE TABLE TX_TEST(A INT, B CHAR(8), C TIMESTAMP);
언제 레코드가 바뀌는 지 정확히 알기 위해 TIMESTAMP 컬럼을 넣었다.

INSERT INTO TX_TEST (A, B) VALUES (1, 'A'), (2, 'B'), (3, 'C'), (4, 'D');
여러 레코드를 한 번에 넣을 수 있는 방법.

SELECT * FROM TX_TEST;





Isolation Level 변경하는 법
SET GLOBAL TRANSACTION ISOLATION LEVEL [LEVEL_NAME];


Isolation Level 확인하는 법

SELECT @@TX_ISOLATION;



GLOBAL 값을 변경하더라도, 세션의 Isolation Level이 변경되지 않을 수 있다.


Session에 대한 Level 변경하는 법

SET SESSION TRANSACTION ISOLATION LEVEL [LEVEL_NAME];




1. READ-UNCOMMITTED 인 경우


[세션 A에서]

START TRANSACTION;

UPDATE TX_TEST SET A = A + 1;


[세션 B에서]

SELECT * FROM TX_TEST;


세션 A에서 commit을 하지 않았음에도, transaction 중간 변경사항이 세션 B에서도 확인할 수 있게 되었다.

일반적인 데이터베이스의 transaction에서 이러한 일이 생겨서는 안된다.


만약 이 때 세션 B에서 UPDATE 문을 날리면 어떻게 될까?


UPDATE TX_TEST SET A = A + 5;

...


세션 A에서 TRANSACTION이 이루어지고 있기 때문에, 해당 TRANSACTION이 끝나기를 기다리게 된다.

(장시간 기다릴 경우 LOCK WAIT TIMEOUT EXCEEDED 오류가 난다)


[세션 A]

SELECT NOW(); ROLLBACK;


[세션 B]

SELECT * FROM TX_TEST;


현재 컬럼 C에 찍혀있는 TIMESTAMP 값은 해당 UPDATE 문을 날렸을 때의 TIMESTAMP이다.




2. READ-COMMITTED 인 경우


각 세션에 다음을 통해 Transaction Isolation Level을 read-committed로 변경하자.

SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;


[세션 A]

START TRANSACTION;

UPDATE TX_TEST SET A = A * 10;


SELECT * FROM TX_TEST;


[세션 B]

START TRANSACTION;

SELECT * FROM TX_TEST;


세션 A에서 날린 UPDATE 문의 결과를 세션 B에서는 아직 확인할 수 없다.



[세션 A]

COMMIT;


[세션 B]

SELECT * FROM TX_TEST;


세션 A의 TRANSACTION이 끝난 이후 세션 B에서도 업데이트 된 레코드를 읽을 수 있게 되었다.

하지만, 세션 B의 TRANSACTION이 진행 중인데 레코드의 값이 변경이 되는 것은 문제가 될 수 있다.




3. REPEATABLE-READ


각 세션에 다음을 통해 Transaction Isolation Level을 REPEATABLE READ로 변경하자.

SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;

SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;


[세션 A, B]

START TRANSACTION;


[세션 A]

UPDATE TX_TEST SET B = CONCAT(B, 'A');

SELECT * FROM TX_TEST;



[세션 B]

SELECT * FROM TX_TEST;



심지어 세션 A에서 COMMIT을 하고 난 이후에도,

SELECT * FROM TX_TEST;

가 된다.


결국 세션 B에서 COMMIT; 을 하고 난 뒤에, 세션 A에서 UPDATE를 한 결과 레코드들을 확인할 수 있게 된다.


만약 세션 B에서 TRANSACTION 중 UPDATE 문을 사용하면 어떻게 될까?

당연히 세션 A에서 TRANSACTION-UPDATE가 이루어지고 있기 때문에 LOCK이 걸려서 응답을 기다리게 된다.


만약 세션 A에서 COMMIT;을 하게 되면 A의 UPDATE 문과 B의 UPDATE 문의 결과를 모두 반영한 레코드를 각 세션들이 확인할 수 있게 된다.



4. SERIALIZABLE


각 세션에 다음을 통해 Transaction Isolation Level을 SERIALIZABLE로 변경하자.

SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;

SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;


[세션 A, B]

START TRANSACTION;


[세션 B]

SELECT * FROM TX_TEST;    [정상 작동]

UPDATE TX_TEST SET A = A + 1;    [LOCK이 걸림]


[세션 A]

UPDATE TX_TEST SET A = A + 1;    [DEADLOCK FOUND ERROR 발생]

심지어 세션 B에서 TRANSACTION-UPDATE를 하고 있는 경우에는, SELECT조차 실행이 되지 않는다.







저작자 표시 비영리 동일 조건 변경 허락
신고
Posted by 우너효

모든 함수는 prototype 프로퍼티를 갖는다. 이 프로퍼티는 해당 참조 타입의 인스턴스가 가져야 할 프로퍼티와 메서드를 담고 있는 객체이다. 이 객체는 생성자를 호출할 때 생성되는 객체의 문자 그대로 '프로토타입(원형)'이다. 프로토타입의 프로퍼티와 메서드는 객체 인스턴스 전체에서 공유된다는 점이 프로토타입의 장점이다. 객체 정보를 생성자에 할당하는 대신 다음과 같이 직접적으로 프로토타입에 할당할 수 있다.


function Person() {

}


Person.prototype.name = "Woonohyo";

Person.prototype.age = 27;

Person.prototype.job = "Software Engineer";

Person.prototype.sayName = function() {

alert(this.name);

};


var person1 = new Person();

person1.sayName();    // "Woonohyo"


var person2 = new Person();

person2.sayName();    // "Woonohyo"


alert(person1.sayName == person2.sayName);    // true


여기에서 프로퍼티들과 sayName() 메서드는 Person의 prototype 프로퍼티에 직접 추가되었고 생성자 함수는 비워 두었다. 비록 생성자 함수는 비어있지만, 생성자를 호출헤 만든 객체에도 프로퍼티와 메서드는 존재한다. 생성자 패턴과는 달리 프로퍼티와 메서드를 모든 인스턴스에서 공유하므로 person1과 person2는 같은 프로퍼티 집합에 접근하며 같은 sayName() 함수를 공유한다. 세부 동작에 대한 이해를 위해서는 ECMAScript의 프로토타입에 대해 알아야 한다.



- 프로토타입은 어떻게 동작하는가

함수가 생성될 때마다 prototype 프로퍼티 역시 특정 규칙에 따라 생성된다. 기본적으로 모든 프로토타입은 자동으로 contructor 프로퍼티를 갖는데 이 프로퍼티는 해당 프로토타입이 프로퍼티로서 소속된 함수를 가리킨다. 예를 들어 위의 코드에서 Person.prototype.constructor는 Person을 가리킨다. 다음에는 생성자에 따라 각종 프로퍼티와 메서드가 프로토타입에 추가된다.

커스텀 생성자를 정의하면해당 프로토타입은 단지 constructor 프로퍼티만 가지며 다른 메서드는 Object에서 상속한다. 생성자를 호출해서 인스턴스를 생성할 때마다 해당 인스턴스 내부에는 생성자의 프로토타입을 가리키는 포인터가 생성된다. ECMA-262 5판에서는 이 포인터를 [[Prototype]]이라고 부른다. 스크립트에서 [[Prototype]]에 접근하는 표준은 없지만 파이어폭스, 사파리, 크롬은 모두 __proto__ 라는 프로퍼티를 지원한다. 인스턴스와 직접 연결되는 것은 생성자의 프로토타입이지 생성자 자체가 아님을 이해해야 한다.


프로토타입은 contructor 프로퍼티를 갖고 기타 추가된 프로퍼티들도 가지게 된다. Person의 인스턴스인 person1과 person2는 Person.prototype을 가리키는 내부 프로퍼티를 가질 뿐 생성자와 직접 연결되지는 않는다. 이 인스턴스들에는 아무 프로퍼티나 메서드도 없는데 person1.sayName()이 동작하는 것도 객체 프로퍼티를 검색하는 메커니즘 때문이다. 

[[prototype]]은 구현 환경에 따라 접근 불가능할 수도 있지만 객체 사이에 프로토타입 연결이 존재하는지는 isPrototypeOf() 메서드를 통해 알 수 있다. 요약하면, isPrototypeOf()는 다음과 같이 [[Prototype]]이 자신을 호출하는 프로토타입을 가리킬 때 true를 반환한다.


alert(Person.prototype.isPrototypeOf(person1));    // true

alert(Person.prototype.isPrototypeOf(person2));    // true


이 코드에서 프로토타입의 isPrototypeOf() 메서드를 person1과 person2 모두에 대해 호출했다. 두 인스턴스는 모두 Person.prototype에 연결되므로 isPrototypeOf() 메서드는 true를 반환한다.

ECMAScript 5판에는 [[Prototype]]의 값을 반환하는 Object.getPrototypeOf() 라는 메서드가 추가 되었다. 다음의 코드를 보자.


alert(Object.getPrototypeOf(person1) == Person.prototype);    // true

alert(Object.getPrototypeOf(Person1).name);    // "Woonohyo"


첫번째 줄은 단순히 Object.getPrototypeOf()에서 반환 된 객체가 해당 객체의 프로토타입이 맞는지만 확인한다.

두번째 줄은 프로토타입의 name 프로퍼티 값인 "Woonohyo"를 가져온다.

이 메서드를 통해 프로토타입을 이용한 상속 구현을 할 수 있다.

(메서드 지원 브라우저: IE9+, Firefox 3.5+, Safari 5+, Opera 12+, Chrome)


객체에서 프로퍼티를 읽으려 할 때마다 해당 프로퍼티 이름으로 찾으려는 검색은 객체 인스턴스 자체에서 시작한다. 인스턴스에서 프로퍼티 이름을 찾으면 그 값을 반환하고, 그렇지 않으면 포인터를 프로토타입으로 올려서 검색을 계속한다. 프로퍼티를 프로토타입에서 찾으면 그 값을 반환한다. 따라서 person1.sayName()을 호출하면 두 단계가 발생한다. 첫 단계에서 자바스크립트 엔진은 person1 인스턴스에 sayName이라는 프로퍼티가 있는지 확인한다. 찾을 수 없기 때문에 person1의 프로토타입에서 sayName이 있는지 확인하고, 찾았으므로 프로토타입에 저장된 함수가 실행된다. person2.sayName()을 호출하면 똑같은 방식으로 검색하여 실행하고 같은 결과를 반환한다. 프로토타입은 이런 식으로 여러 객체 인스턴스 사이에서 프로퍼티와 메서드를 공유한다.


객체 인스턴스에서 프로토타입에 있는 값을 읽을 수는 있지만, 수정은 불가능하다. 프로토타입 프로퍼티와 같은 이름의 프로퍼티를 인스턴스에 추가하면 해당 프로퍼티는 인스턴스에 추가되며 프로토타입까지 올라가지 않는다.


function Person() {

}


Person.prototype.name = "Woonohyo";

Person.prototype.age = 27;

Person.prototype.job = "Software Engineer";

Person.prototype.sayName = function() {

alert(this.name);

};


var person1 = new Person();

var person2 = new Person();


person1.name = "Wonbin";

alert(person1.name);    // "Wonbin" - 인스턴스에서

alert(person2.name);    // "Woonohyo" - 프로토타입에서


위 예제에서 person1의 name 프로퍼티에 새 값이 반영되었다. person1의 경우 person1.name에 접근하면 인스턴스에서 name 프로퍼티를 반환하고, person2의 경우 person2.name에 접근하면 인스턴스에서 찾지 못하므로 프로토타입을 검색해 name 프로퍼티를 찾는다.

즉, 객체 인스턴스에 프로퍼티를 추가하면 해당 프로퍼티는 프로토타입에 존재하는 프로퍼티를 '가리게' 된다. (프로토타입에 존재하는 동명의 프로퍼티에 대한 접근 차단)

심지어 프로퍼티를 null로 설정해도 인스턴스의 프로퍼티만 변경할 뿐 프로토타입 프로퍼티에 다시 연결되지 않는다. 다시 프로토타입 프로퍼티에 접근하기 위해서는 delete 연산자를 사용해야 한다.


function Person() {

}


Person.prototype.name = "Woonohyo";

Person.prototype.age = 27;

Person.prototype.job = "Software Engineer";

Person.prototype.sayName = function() {

alert(this.name);

};


var person1 = new Person();

var person2 = new Person();


person1.name = "Wonbin";

alert(person1.name);    // "Wonbin" - 인스턴스에서

alert(person2.name);    // "Woonohyo" - 프로토타입에서


delete person1.name;

alert(person1.name);    // "Woonohyo" - 프로토타입에서


hasOwnProperty("propertyName") - 프로퍼티가 인스턴스에 존재하는지 프로토타입에 존재하는지 확인할 수 있는 메서드. (true = 인스턴스, false = 프로토타입)

Object로부터 상속되는 메서드로, 다음과 같이 해당 프로퍼티가 객체 인스턴스에 존재할 때만 true를 반환한다.


function Person() {

}


Person.prototype.name = "Woonohyo";

Person.prototype.age = 27;

Person.prototype.job = "Software Engineer";

Person.prototype.sayName = function() {

alert(this.name);

};


var person1 = new Person();

var person2 = new Person();


alert(person1.hasOwnProperty("name"));    // false


person1.name = "Wonbin";

alert(person1.name);    // "Wonbin" - 인스턴스에서

alert(person1.hasOwnProperty("name"));    // true


alert(person2.name);    // "Woonohyo" - 프로퍼티에서

alert(person2.hasOwnProperty("name"));    // false


delete person1.name;

alert(person1.name);    // "Woonohyo" - 프로퍼티에서

alert(person1.hasOwnProperty("name"));    // false















저작자 표시 비영리 동일 조건 변경 허락
신고
Posted by 우너효