Async



node.js의 장점으로 꼽는 점 중 하나가 non-Blocking으로써 비동기적으로 작동한다는 점이다.


그렇기 때문에 node.js의 대부분의 기능들은 콜백 패턴을 이용한 비동기 처리를 사용는데, 이러한 점 때문에 실제 개발에 콜백 지옥으로 인해 난관을 겪곤 한다.


콜백 지옥이란 콜백 패턴을 이용하는 함수에, 연달아 추가 기능을 사용할 경우 콜백이 계속해서 중첩되는 상태를 말한다.


task1(a, function(err, result1){     task2(b, function(err, result2){         task3(c, function(err, result3){             task4(d, function(err, result4){                 //함수 실행             });         });     }); });


위 예제는 err에 대한 처리와 각 파라미터에 대한 알고리즘을 베재한 코드이다. 

4중첩이지만 실제 코드를 node.js로 코딩할땐 흔히 4중첩이상은 흔히 볼 수 있다.


이러한 콜백 지옥을 해결하기 위해서 다양한 모듈이 있다. 

자바스크립트의 Promise 패턴을 이용한 방법(대부분의 모듈은 Promise를 지원한다.), 혹은 Async나 Step이라는 모듈로 흐름을 제어하는 방법이다.


여기서는 Async모듈을 사용하는 방법을 정리하고자 한다.


async : https://github.com/caolan/async

docs : http://caolan.github.io/async/

설치 : npm install async --save



async 모듈의 메소드는 크게 콜렉션, 흐름제어, 유틸 3종류로 나뉘어진다. 주로 사용하는건 콜렉션과 흐름제어이고, 이 포스팅 자체는 흐름제어가 주제이므로 이를 주로 다루겠다.



흐름 제어 메서드


async의 흐름 제어 메서드는 정말 다양한 종류가 있지만, 내가 주로 사용한 메서드는 다음과 같다.


- 순차 실행 : series, waterfall

- 병렬 실행 : parallels



순차 실행

순차 실행은 이름 그대로 메서드들을 순차적으로 실행하는 메서드이다. 여기엔 인자를 전달하지 않고 순차적으로만 실행하는 메서드인 series와, 

다음 메서드에 인자를 전달할 수 있는 waterfall 메서드가 있다.


1. series


시리즈 메서드는 다음과 같은 형태를 가진다.

async.series([task1,task2,task3], function(err, results){ // series 완료 콜백 });

첫번째 파라미터는 실행할 함수들을 순서대로 배열에 넣어 삽입한다, 두번째 메서드는 이 serise가 완료되었을때 실행되는 콜백 함수를 넣는데,


이 콜백에는 실행 메서드들 중 실패하는 경우 err에 인자를 전달하며 그 뒤 테스크들의 실행을 중단하고 콜백 함수를 호출한다.


모두 성공할 경우 콜백 함수의 results에 배열에 있는 각 테스크들의 실행 결과를 배열로 전달받는다.


배열에 들어가는 메서드들은 callback을 인자로 가지는 함수 형태로 만들어야 한다.




function task1(callback){     //실패시     if(err){         callback(err);     }     //성공시     callback(null, '성공1'); }


성공 시에는 callback인자를 함수로 호출하는데, 첫 인자를 null로 전달하면 되고


실패 시에는 첫 null에 err를 전달하면 된다.


async.series([
    function (callback) {
        if (err) {
            callback('실패1');
        }
        callback(null, '성공1');
    }, 
    function (callback) {
        if (err) {
            callback('실패2');
        }
        callback(null, '성공2');
    },
    function (callback) {
        if (err) {
            callback('실패3');
        }
        callback(null, '성공3');
    }],
    function(err, results) {
        // serise 완료 콜백
        if(err){
            //err시 err난 테스크에서 전달한 err값을 전달받는다
            console.log(err);
        }
        //모두 성공시, 성공 값들을 전달받는다
        //예) ['성공1','성공2','성공3']
        console.log(reuslts);
});



2. waterfall


serise와 waterfall은 둘다 순차 실행이지만

serise는 테스크들이 독립적으로 실행되어 모든 결과를 모아 배열로 콜백에 전달하는 반면에, 

waterfall은 각 테스크들의 값을 다음 인자로 전달한다는 점에서 다르다.


async.waterfall([task1,task2,task3], function(err, result){ // waterfall 완료 콜백 });

메서드의 형태는 searise와 얼핏 비슷하나, task에서 다음 콜백을 호출 할 때 인자를 전달 할 수 있다.



function task1(callback){ //실패 시 if(err){ callback(err); } //성공 시 callback(null, '성공', '1'); } function task2(arg1, arg2, callback){ //실패 시 if(err){ callback(err); } //성공 시 callback(null,'성공2'); }

async.waterfall([ function (callback) { if (err) { callback('실패1'); } callback(null, '성공','1'); }, function (arg1, arg2, callback) { if (err) { callback('실패2'); } //arg1 : '성공', arg2 : 1 console.log(arg1, arg2); callback(null, '성공2'); }, function (arg1, callback) { if (err) { callback('실패3'); } //arg1 : '성공2' console.log(arg1); callback(null, '성공3'); }], function(err, result) { // waterfall 완료 콜백 if(err){ //err시 err난 테스크에서 전달한 err값을 전달받는다 console.log(err); } //모두 성공시, 마지막에 결과 값을 전달 받는다. //result : '성공3' console.log(reuslt); });




병렬 실행


parallel


parallel도 앞서 본 메서드들과 같이 task의 배열과 콜백을 인자로 받는다. 다만 순차 실행이 아닌 전달 받은 task들을 병렬, 즉 동시에 실행한다.


그리고 모든 테스크들이 전부 다 끝난 다음에서야 콜백 함수가 실행된다.

async.parallel([task1,task2,task3], function(err, results){
// parallel 완료 콜백
});


동시 실행이기 때문에 각 task들은 serise와 같이 독립적으로 실행되며, results에는 모든 함수의 결과가 저장된다.


메서드의 형태는 serise와 비슷하다.

async.parallel([
    function (callback) {
        if (err) {
            callback('실패1');
        }
        callback(null, '성공1');
    }, 
    function (callback) {
        if (err) {
            callback('실패2');
        }
        callback(null, '성공2');
    },
    function (callback) {
        if (err) {
            callback('실패3');
        }
        callback(null, '성공3');
    }],
    function(err, results) {
        // parallel 완료 콜백
        if(err){
            //err시 err난 테스크에서 전달한 err값을 전달받는다
            console.log(err);
        }
        //모두 성공시, 성공 값들을 전달받는다
        //예) ['성공1','성공2','성공3']
        console.log(reuslts);
});







node.js를 이용한 프로젝트를 진행하면서 비동기 처리에 따른 콜백 지옥을 벗어나고자 많은 고민이 있었다. 


async 모듈, 그리고 promise 만으로도 대부분의 콜백 지옥을 해소 할 수 있다고 하므로 반복해서 숙지하도록 노력해야겠다.

'Javascript > node.js' 카테고리의 다른 글

비동기 흐름 제어를 위한 모듈 - async  (0) 2016.09.18
node.js 모듈  (0) 2016.03.30
node.js 시작하기  (0) 2016.03.01

모듈은 다른 언어에서 사용하는 라이브러리, API와 같다.

node.js에서는 global 모듈 외에 모든 필요한 기능을 사용하고자 할 때는 모듈(라이브러리)를 로딩하여 사용해야 한다.


모듈은 기본적으로 require함수를 이용하여 로딩한다.

(require은 global 모듈에 있는 함수이기 때문에, 어디서든지 사용 가능한 함수이다.)

만약 http 모듈을 로딩한다면 다음과 같이 입력하면 된다.

var http = require('http');

http 변수 내에 http 모듈이 로딩된다.


node.js에서 설치 시 자동적으로 기본 모듈이 설치된다.

기본적으로 제공되는 모듈은 node.js의 공식 홈페이지에서 제공하는 문서에서 확인 할 수 있다.


node.js api 문서 : https://nodejs.org/dist/latest-v5.x/docs/api/


기본 모듈을 제외한 모듈들은 (ex, nodeman, formidable, multer 등등) npm이라는 패키지 관리자 도구를 이용해 설치해야 한다.


기본적으로 제공되는 모듈은 다음과 같다.


process : 프로세스에 대한 정보를 담고 있는 전역 객체

utility : 타입 검사, 포메팅 등의 유틸리티 함수 제공

events : 이벤트 관련 함수 제공

buffers : 바이너리 데이터의 옥텟 스트림(octet stream)을 다루는 모듈  streams :스트림을 다루기 위한 추상 인터페이스

crypto : 암호화에 대한 함수 제공

TLS/SSL : 공개키, 개인키 기반인 TLS/SSL 에 대한 함수 제공

File System : 파일을 다루는 함수 제공

Path : 파일 경로를 다루는 함수 제공

Net : 비동기 네트워크 통신 기능 제공

UDP : UDP의 데이터그램 소켓 (Datagram Sockets) 통신 기능 제공

DNS : 도메인 네임 서버를 다루는 함수 제공

TTP : HTTP 서버 및 클라이언트 기능 제공

HTTPS : HTTPS 서버 및 클라이언트 기능 제공

URL : URL을 다루는 함수 제공

Query Strings : URL의 쿼리 문자열을 다루는 함수 제공

Readline : 스트림에서 라인 단위로 읽는 기능을 제공

Vm : 자바스크립트 실행 기능 제공

Child Processes : 자식 프로세스 생성과 관련된 함수 제공

Assert : 유닛 테스트를 위한 단언문을 제공

TTY : 터미널이나 콘솔 관련 기능을 제공

Zlib : zlib 압축, 해제 함수 제공

OS : 운영체제에 대한 정보를 구하는 함수 제공

Cluster : 여러 노드 프로세스를 실행하는 클러스터 기능을 제공 

(출처 : T아카데미 node.js 자료)




'Javascript > node.js' 카테고리의 다른 글

비동기 흐름 제어를 위한 모듈 - async  (0) 2016.09.18
node.js 모듈  (0) 2016.03.30
node.js 시작하기  (0) 2016.03.01


기본적으로 프로그래밍 언어에서 스코프란, 어떤 변수에 대한 유효 범위를 뜻한다.

대표적으로 C언어에 함수 안에서 선언한 변수는, 함수 밖에서는 참조할 수 없는 것을 예로 들 수 있다.

보통 C,C++,JAVA에선 이 스코프에 대한 정보 흐름을 스택으로 표현한다. 프로그램이 시작하면 main함수에서 참조하는 정보들이 스택 영역에 push하고, 새로운 함수나 흐름이 시작하면 스택 영역의 가장 위에 push하여 정보를 저장한다. 그리고 변수를 사용할 때 스택의 top에 있는 정보를 사용한다. 

자바스크립트도 이러한 스코프를 구현하기 위해, 다른 언어의 스택 영역과 유사한 개념으로 실행 컨텍스트(Excution context)를 정의하였다.


실행 컨텍스트란, '실행 가능한 코드를 형상화하고 구분하는 추상적인 개념'으로 정의 된다고 한다, 쉽게 말하면 코드 블럭에 대한 정보들을 표현한 것이라고 할 수 있다.


이러한 실행 컨텍스트는 다른 언어의 main영역과 마찬가지로 global 컨텍스트가 존재한다, 이는 실행 컨텍스트가 생성되는 과정을 살펴본 후 알아보도록 하겠다.


function circle(r){
    var number = 3.14;
    function square(){
        return r*r;
    }
    return number * square();
}

circle(4);//50.24


위는 입력받은 반지름에 따른 원의 넓이를 구하는, 그냥 예제를 위한 함수이다. 위 함수를 실행함에 따라 실행컨텍스트가 어떻게 생성이 되는지 하나씩 정리하도록 해자.


1. 함수가 실행되면, 실행 컨텍스트를 생성한다.


 실행 컨텍스트

 



2. 실행 컨텍스트가 생성된 후, 이 컨텍스트에서 실행에 필요한 정보들을 담을 객체인 활성 객체를 생성한다.


 실행 컨텍스트

 

 활성 객체

 


활성 객체에는 이 함수가 가지고 있는 파라미터나 변수, 객체 등에 대한 정보가 저장된다. 

또한 다른 스코프에서 이 실행 컨텍스트를 참조할 땐 저 활성 객체를 통해 참조하게 된다.



3. 활성 객체 내에, 매개변수의 정보를 저장하고 있는 arguments 객체가 생성되고, 함수의 arguments 객체를 참조한다.


 실행 컨텍스트

 

 활성 객체

 arguments ---> [ r ]



활성 객체에는 이 함수가 가지고 있는 파라미터나 변수, 객체 등에 대한 정보가 저장된다. 

또한 다른 스코프에서 이 실행 컨텍스트를 참조할 땐 저 활성 객체를 통해 참조하게 된다.


4. 활성 객체 내에, 이 실행 컨텍스트의 스코프 체인을 생성한다.


 실행 컨텍스트

 

 활성 객체

 arguments ---> [ r ]

 [[scope]] 


스코프 체인은 이 컨텍스트 생성의 대상이 되는 함수가 가지고있는 [[scope]]를 복사해오고, 거기에 이 실행 컨텍스트의 활성 객체를 추가한다.

[[scope]]는 list의 형태로 저장이 되며, 전역객체에서 함수를 선언했으므로, 이 예제에서 저장되어있는 스코프는 이 실행 컨텍스트와, 전역 컨텍스트 일 것이다.



5. 변수 객체가 생성 되고, 함수가 가지고있는 변수 및 객체 정보를 생성한다.


 실행 컨텍스트

 

 활성 객체 = 변수 객체

 arguments ---> [ r ]

 [[scope]] ---> [List]

 r : value

 number : undefined

 square ---> Function Object


변수 객체가 따로 명명 되었지만, 변수 객체가 활성 객체와 같다.

매개변수로 받은 변수들은 바로 그 값으로 초기화 되고, 

함수 내에 선언된 변수는 해당 코드가 실행 되기 전까지는 초기화 되지 않기 때문에,

먼저 undefined로 저장이 된다.



6. this에 대한 정보를 저장하며, 이 객체에 바인딩한다.


 실행 컨텍스트

 

 활성 객체 = 변수 객체

 arguments ---> [ r ]

 [[scope]] ---> [List]

 r : value

 number : undefined

 square() ---> square함수 객체를 참조

 this ---> 전역 객체에 바운딩


this 바운딩에 대해선 this 키워드 포스트를 참조하자. 여기선 전역에 선언되어있는 함수이기 때문에 전역 객체에 바인딩 될 것이다.



7. 코드가 실행된다.

코드가 실행되면서 number의 값이 초기화되고 함수가 실행되어 반환된다.



지금까지 circle 함수의 실행 컨텍스트의 생성 과정을 보았다. 그러면 global 컨텍스트는 어떻게 될까? 

global 객체도 자바스크립트 객체이기 때문에 이와 크게 다르지 않다. global에 선언되어있는 변수와 객체에 대한 정보를 변 수객체에 저장하게 될 것이다.


global 실행 컨텍스트

 

 활성 객체 = 변수 객체

 arguments ---> []

 [[scope]] ---> [List]

 circle() ---> circle 함수 객체를 참조

 this ---> 전역 객체에 바운딩



예제에서의 global 컨텍스트는 위와 같을 것이다. 이 컨텍스트의 [[scope]]는 그 이전의 전역 컨텍스트의 변수 객체만이 존재할 것이며, this도 전역 객체에 바운딩 될 것이다.

  1. 하이 2016.06.09 04:02

    좋은 정보 잘보고 갑니다

요새 자바스크립트 하나만 잘 알아도, 웹사이트 전체를 만들 수 있는 풀스택 개발이 가능하다고 한다. 이러한 풀 스택 개발이 가능하게 된 기반은 node.js라는 자바스크립트 프레임워크의 존재 떄문이다. 


자바스크립트는 초기에 그저 브라우저 내 에서의 동적인 처리를 위한 스크립트 언어였고, 현재도 대부분이 jquery나 angular.js와 같은 프론드엔드 프레임워크를 많이 사용한다. 하지만 수많은 자바스크립트 프레임워크중에 특출나게 서버 사이드를 처리하는 프레임워크가 있으니, 그것이 바로 node.js다. 기존의 자바스크립트 프론트엔드 프레임워크와, node.js가 합쳐져, 그야말로 자바스크립트만 잘 알아도 사이트 하나는 뚝딱 만드는 것이다. 

그 뿐만 아니라 node.js는 기존의 서버와는 다르게 event-driven 기반, 싱글 쓰레드에 non-Blocking I/O 방식으로 작동한다고 한다.(데이터 입출력때마다 blocking되지 않는다.)

그리고 jsp나 php와 같이 웹서버나 WAS가 필요 없고, node.js 그 자체가 웹서버로서 동작한다.


node.js에 대한 관심은 항상 가지고 있었다. node.js는 매 업데이트마다 죄다 뒤집어져서 책이 의미가 없다고 하니, 구글링과 레퍼런스를 통해 독학으로 시작하려 한다.



node.js는 공식 사이트에서 받을 수 있는데, 검색해서 찾아가보니 메인화면만 유일하게 한글화가 되어있다..(..)


공식 사이트의 주소는 다음과 같다 - https://nodejs.org/en/




사이트에 들어가면 위와 같이 LTS 버젼과, Stable버젼이 있다. Stable 버젼이란 안정화된 최신 버전을 뜻하고

LTS란 보통 Long Term Support, 장기간 지원을 하는 버전을 뜻한다. 4.3.1 LTS버젼을 다운 받아보자.




다운 받으면 설치 파일을 실행, node.js에 흥미를 가지실 정도의 분이라면 설치에 대한 두려움을 가진 사람은 없으리라 생각한다.






설치 완료!


C:\nodejs폴더에 example.js 파일을 생성하고, 다음 예제코드를 입력해보자.

const http = require('http');

http.createServer( (request, response) => {
  response.writeHead(200, {'Content-Type': 'text/plain'});
  response.end('Hello World\n');
}).listen(8124);

console.log('Server running at http://127.0.0.1:8124/');


명령 프롬포트를 실행하고, 다음과 같이 입력해보자.




브라우저를 이용해 http://localhost:8124/ 접속해보면 서버가 정상적으로 작동하여 HelloWord가 출력 되었음을 확인 할 수 있다. 




서버를 종료하고 싶을 경우 명령 프롬포트를 종료하거나, 명령 프롬포트에서 Ctrl+C를 누르면 된다.

'Javascript > node.js' 카테고리의 다른 글

비동기 흐름 제어를 위한 모듈 - async  (0) 2016.09.18
node.js 모듈  (0) 2016.03.30
node.js 시작하기  (0) 2016.03.01

자바스크립트는 기본적으로 Java와 C++과 같은 클래스 객체의 상속이란 개념을 사용하지 않고 prototype 모델의 상속을 지원한다. prototype은 객체를 생성하기 위해 사용되는 객체의 원형을 뜻하며 생성자 객체를 통해 객체가 생성 될 때, 생성자의 prototype 프로퍼티에 링크되어있는 프로토타입 객체들이 생성된 객체에 연결된다. prototype 프로퍼티는 함수 함수들이 가지고있는 속성으로서 생성자를 처음 정의할때엔 기본적으로 empty Object를 가르킨다.

(cf: prototype 프로퍼티는 1급 객체인 함수 객체만 가지고 있는 프로퍼티이므로 일반 객체에서 접근하려고하면 syntax error가 발생한다.)


prototype을 정리하기에 앞서 비교를 위해 생성자 함수를 이용한 객체 생성을 먼저 보면,

var Parent = function(){ this.f = function(){ console.log("HI"); } } Parent.f = function(){ console.log("HELLO"); } var A = new Parent; var B = new Parent; A.f(); B.f();

위 코드의 결과는 다음과 같다.
HI
HI

위에서 this.f는 프로토타입이 아닌 생성자 Parent의 프로퍼티이다. 즉 이 생성자를 통해 새로운 객체를 생성하면 this가 A,B객체에 바인딩이 되어 A,B객체의 프로퍼티가 되기 때문에, 아무리 생성자인 Parent의 f를 수정하더라도 생성된 객체인 A와 B의 f 프로퍼티는 변하지 않는다.(객체 생성 부분은 객체 생성 포스트를 참조하자)


다음은 프로토타입을 이용한 객체 생성이다.

var Parent = function(){ Parent.prototype.f = function(){ console.log("HELLO"); }; } var A = new Parent; var B = new Parent; A.f(); B.f(); Parent.prototype.f = function(){ console.log("HI"); }; A.f(); B.f();

위 코드의 결과값은 다음과 같다.

HELLO
HELLO

HI
HI

즉 생성자의 prototype 프로퍼티를 이용해 프로토타입 객체에 생성된 프로퍼티는, 그 생성자 객체에 의해 생성된 모든 객체가 그 프로퍼티를 공유한다. 생성자가 가지고 있는 프로토타입 객체의 프로퍼티가 변하면 그 생성자에 의해 생성된 모든 객체들의 함수도 같이 변한다. 이는 prototype의 값은 생성자가 가지고있는 f의 값을 객체 자신만의 프로퍼티로 가지고 있는 것이 아니라, 프로토타입 객체에 링크되어 직접 찾아가 사용하기 때문이다.

이를 이해하기 위해선, 생성자 함수 객체가 가지고있는 prototype 프로퍼티 말고도, 객체들이 가지고있는 [[Prototype]]프로퍼티의 존재를 알아야한다. 이는 개발자 도구에서 객체를 열어보면 __proto__프로퍼티로 표시되는 프로퍼티이다. 이 __proto__프로퍼티가 자신이 상속 받은 부모 객체의 prototype을 가르키는 프로퍼티이며, 객체는 자기 자신이 가지고있는 프로퍼티 말고도, __proto__의 링크의 연결되어있는 부모 함수객체의 prototype에 정의해놓은 객체나 변수들을 객체 자신의 것인 양 사용하는 것이다.

(객체들은 모두 가지고있는 [[Prototype]]프로퍼티(__proto__)와 함수(생성자) 객체만 가지고있는 prototype 프로퍼티 두개를 헷갈리지 않도록 조심하자, 가르키는 대상은 같지만 가지고 있는 객체는 다르다. ECMAScript에서 [[Prototype]]프로퍼티라고 명세 했지만 대부분의 브라우져에선 __proto__로 표시하고있다.)

여담으로 함수 객체는 상속을 위한 porototype 프로퍼티가 자신의 prototype 객체를 가리키는데, 반대로 prototype에서는 자기 자신을 포함하는 생성자 함수 객체를 가르키는 constructor를 가지고 있다. 밑에 사진에서 보면 그 관계를 자세히 알 수 있다.

크롬의 개발자 도구에서 확인한 객체의 프로퍼티이다.

__proto__프로퍼티(=[[Prototype]] 프로퍼티)가 자기를 생성한 Member 생성자 객체의 prototype을 가르키고 있다. 그리고 그 prototype이 가지고있는 프로퍼티중 constructor 프로퍼티는, 생성자 함수를 가르키는것을 확인할 수 있다.




위 사진처럼 prototype를 통해 상속되어 연속되어 이어진 형태를 프로토타입 체인(prototype chain)이라고 한다.
객체 에서 어떤 프로퍼티를 사용하려고 할때, __proto__를 따라 상속받은 프로토타입을 찾아가며 프로퍼티를 찾아나간다.
원하는 프로퍼티를 찾고자 하는 과정은 위 그림 순서대로 찾아간다.
객체 자신이 해당 프로퍼티를 가지고있지 않으면 __proto__를 따라 올라가 상위 프로토타입 객체에서 찾고, 없으면 또 __proto__를 따라 올라가 프로퍼티를 찾아나간다.
끝까지 탐색을 해나갔음에도 불구하고(Object.prototype까지 탐색) 원하는 프로퍼티를 찾지 못했을 경우에는 undefined를 반환한다.
이와 같이 찾아 체인을 따라 프로퍼티를 찾는 과정이 프로토타입 체이닝이다.


해당 프로퍼티가 객체 자신의 프로퍼티인지, prototype으로 상속받은 프로퍼티인지는 hasOwnProperty 메소드를 이용해 알 수 있다.
hasOwnProperty메소드는 모든 객체들의 조상인 Object.prototype에 정의 되어 있으며, 이 또한 프로토타입 체이닝을 통해 모든 객체가 사용 가능하다.


그외 Object.prototype(네이티브 프로토타입)도 프로퍼티를 추가해서 모든 객체들의 기능을 확장할 수 있는데(Object.prototype은 모든 객체의 조상이니 말이다) 이는 권장되는 방법은 아니다.



정리하자면 함수 객체는 자식에게 상속할 프로퍼티를 포함하는 prototype를 가지고 있다. prototype 프로퍼티는 객체의 원형을 담는 자신의 프로토타입 객체에 링크되고, 또한 객체를 생성할 때 객체의 [[Prototype]] 프로퍼티(__proto__)에 링크된다. (객체 생성 포스트를 참조하자, new 키워드를 이용해 생성될 때 연결된다.). 객체는 이 __proto__에 연결된 프로토타입 객체를 따라가 프로토타입에 정의된 프로퍼티를 객체 자신의 것처럼 사용할 수 있다. 부모 프로토타입에도 없을 경우, 부모가 상속받은 프로토타입을 찾아가며(__proto__프로퍼티를 통해 프로토타입 체인을 따라 올라가며) 해당 프로퍼티를 찾는다.

이렇게 따로 생성한 객체 말고도, Array나 Function 등의 객체들도 프로토타입의 상속을 기반으로 한다. Array나 Function이 내부적으로 가지고있는 메소드들은 바로 이 상속을 기반으로 가능한 것이다.

객체의 생성은 함수 객체(생성자 객체)에 의해 일어나지만, 상속에 관련된 모든 역할은 생성자 객체에 연결되어있는 프로토타입 객체에 의해 일어난다는 것을 기억하자. 

자바스크립트는 기존 C++,Java와 같은 클래스 기반 객체 지향 언어와는 객체 생성 방법이 다르다.


자바스크립트 자체에 클래스라는 개념이 없기 때문이다. 자바스크립트에서 객체를 생성할 수 있는 방법은 크게 3가지로 나뉜다.

1. 기본 객체(Object() 객체)의 생성자 함수를 이용

2. 객체 리터럴을 이용

3. 생성자 함수 이용


위 순서대로 객체 생성 방법에 대해 하나씩 알아보겠다.


1. 기본 객체(Object() 객체)의 생성자 함수를 이용

Object 객체를 생성하고, 여기에 새로운 프로퍼티를 추가하는 방법이다. 객체는 new 연산자를 이용해서 생성이 가능하므로, 기본 객체를 다음과 같이 생성하자.

var member = new Object()

이 같이 기본 객체를 생성한 뒤에, 필요한 프로퍼티를 추가한다.

member.Id = 'phs1116';
member.pass = '1234';
member.gender = 'male'

이 변수의 타입과 변수를 출력해보자.

console.log(typeof member);
console.log(member);

출력값은 다음과 같다.

object
Object {Id: "phs1116", pass: "1234", gender: "male"}

이로서 member변수가 여러 프로퍼티를 포함한 객체로 생성됬음을 알 수 있다.


2. 객체 리터럴을 이용

그 다음은 1번과 같이 기본 객체를 생성하여 프로퍼티를 추가하는 방법이 아닌, 자체로 바로 객체를 생성하는 방법이다. 방법은 간단한데, 변수를 생성할 때 중괄호({})안에 생성할 객체의 프로퍼티를 정의하면 된다. 안에 아무것도 넣지 않고, 중괄호 만으로 생성을 하면 빈 객체가 생성된다. 프로퍼티를 선언할 땐 다음과 같은 방식으로 선언한다.

프로퍼티 이름 : 프로퍼티 값

1번과 같은 객체를 객체 리터럴 방식으로 생성해보자.

var member = {
  Id : 'phs1116',
  pass : '1234',
  gender : 'male'
};

이렇게 생성한 변수를 확인해보면 이 또한 여러 프로퍼티를 포함한 객체임을 알 수 있다.


3. 생성자 함수 이용

자바스크립트의 함수는 생성자 함수로도 정의할 수 있다. 생성자 함수라는 것은 일정의 형식이 정해진 것이 아니라, 정의한 함수에 new 키워드를 붙여 변수에 호출하면 그 자체로 생성자의 역할을 한다. 생성자 함수와 일반 함수의 구분이 명확치 않기 때문에, 이의 구분을 위해 생성자 함수의 첫 글자는 대문자로 쓰는 것을 권장하고 있다.

생성자 함수를 new 키워드로 이용해서 생성하는 객체는 다음과 같이 작동한다.

1. 먼저 빈 객체를 생성하고, this를 이 객체에 바인딩한다.(new 키워드를 사용하지 않을경우엔 this는 전역객체 window에 바인딩된다.) 또한 생성한 객체의 프로토타입 객체를(__proto__) 자신의 생성자의 프로토타입 프로퍼티로 설정한다.

2. 생성자 내부에 this 키워드로 정의된 프로퍼티들을 객체 내에 동적으로 생성한다.

3. 생성자 내부에 return문이 따로 명시되어있지 않으면, 생성한 객체를 리턴한다. 뿐만 아니라 this 키워드를 리턴해도 생성한 객체를 반환한다. (new 키워드를 사용하지 않았고(즉 함수로 사용하였고), return문을 명시하지 않았을 경우에는 undefined가 반환된다.)



생성자는 다음과 같이 정의한다.

var Member = function(Id,pass,gender) {
    this.Id  = Id;
    this.pass = pass;
    this.gender = gender;
}
var Member = function(Id,pass,gender) {
    this.Id  = Id;
    this.pass = pass;
    this.gender = gender;
    return this;
}

위 생성자를 다음과 같이 new 키워드로 호출하고, console.log로 출력해보면

var member = new Member('phs1116','1234','male');

console.log(member);
console.dir(member);

Member {Id: "phs1116", pass: "1234", gender: "male"}

가 출력되어 member 변수가 Member 생성자의 객체로 생성됬음을 알 수 있다.


부모 객체의 프로토타입을 가르키는 [[Prototype]]프로퍼티(__proto__)도 Member 생성자의 프로토타입 객체로 설정됬음을 확인할 수 있다.


기본 객체를 이용하는 방법이나, 객체 리터럴을 이용하는 방법은 같은 형태의 객체를 다시 생성할 수 없다. 같은 형태의 여러 객체를 생성하고자 한다면 생성자 함수를 정의하여 사용해야 할 것이다.

또한 생성자 함수를 이용해서 객체를 생성하면 프로토타입 객체(__proto__)는 생성자의 프로토타입 프로퍼티로 설정하지만, 객체 리터럴은 Object 객체의 프로토타입으로 설정된다. 이는 자바스크립트에서 객체를 생성할 때, 자동적으로 프로토타입 객체가 생성자의 프로토타입 프로퍼티로 설정되는데, 객체 리터럴을 사용할경우 Object()생성자를 사용하여 생성하는 것과 같기 때문이다.


this 키워드는 자바스크립트와 다른 클래스 기반 언어와는 약간 다른개념이다.
C++에서 this라함은 보통 이를 사용하는 멤버함수가 속한 클래스를 가르키는 포인터이고, java또한 비슷한 기능을 가진다.
이렇게 클래스 기반언어 C++/JAVA의 this는 기능이 정해져있다.

자바스크립트 또한 클래스기반 언어 처럼 같은 기능을 제공하지만, this 키워드를 사용하는 방법에 따라 가르키는 대상이 모두 다르다.


자바에서의 this

class Memeber{
    private String id;
    private String password;
    
    Memeber{String id, String password){
        this.id = id;
        this.password = password;
    }
}

C++에서의 this

class Brick{
private:
    int shape;
    int rot;
public:
    Brick(){
    srand(GetTickCount());
    this->shape = rand() % 6; 
    this->rot = rand() % 3; 
    }
};  

 

'This'

자바스크립트에서 this가 가르키는 경우는 크게 5가지의 경우로 나타낼 수 있다.

1. Global Scope에서 사용될 때 this는 전역 객체를 가르킨다(window 객체)

2. 함수에서 사용될 때에도 this는 전역 객체를 가르킨다.(window 객체)

3. 객체에 속한 메서드에서 사용될 때 this는 메서드가 속한 객체를 가르킨다.

4. 객체에 속한 메서드의 내부함수에서 사용될 때 this는 전역 객체를 가르킨다.(window 객체)

5. 생성자에서 사용될 때, this는 이 생성자로 인해 생성된 새로운 객체를 가르킨다.


이렇게 정리해놓고 보니, 4번의 경우를 제외하고 this도 일반적으로 자기가 속한 객체를 가르키는것으로 알 수 있다.


1. Global Scope에서 사용될 때 this는 전역 객체를 가르킨다(window 객체)

var func = {}; //객체 생성

this === window;//true

this === func;//false

다음과 같이 global scope에서 this와 전역객체를 비교해보면 true를 반환한다. global scope 자체가 전역객체(window)이므로  전역변수들과 함수들은 모두 전역객체에 속한다.


2. 함수에서 사용될 때에도 this는 전역 객체를 가르킨다.(window 객체)

function func(){
    if(this===window)
        console.log("this===window");
}

func();

1번과 같은 맥락으로 global scope가 전역객체이므로 함수는 전역객체에 속하는 메서드와 같다. this는 자신이 속한 메서드를 가르키므로 함수의 this는 window를 가르킨다. 따라서 이 코드의 함수 내 if문은 올바르게 작동하여 "this===window"를 출력한다.


3. 객체에 속한 메서드에서 사용될 때 this는 메서드를 호출한 객체를 가르킨다.

var obj = { func : function(){ if(this===window){ console.log("this===window"); } else if(this===obj){ console.log("this===obj"); } } } object.func();

메서드를 하나 가지고있는 객체를 생성했다. 이 객체의 메서드 func는, this가 window일때와 obj일때에 따라 다른 결과를 출력한다.

코드를 실행해보면 this===obj가 출력된다. 따라서 객체에 메서드가 사용하는 this는 자신이 속한 객체를 가리킴을 알 수 있다.


4. 객체에 속한 메서드의 내부함수에서 사용될 때 this는 전역 객체를 가르킨다.(window 객체)

var obj = { func : function(){ function inner(){ if(this===window){ console.log("this===window"); } else if(this===obj){ console.log("this===obj"); } } inner(); } } obj.func();

 

3번의 코드에서 메서드에 내부함수를 추가하고 그 안에 this가 어떤 객체에 속하는지 확인하는 코드를 넣었다. 이코드의 결과로는 놀랍게도 "this===window"가 출력된다. 따라서 내부함수에서 this를 사용할때에는 주의를 요한다.
내부함수에서 자신이 속한 객체를 가르키는 this 키워드가 필요할 시 보통은 다음과 같이 메서드 내에서 this를 변수에 따로 저장하여 그 변수를 사용하는 방법을 취한다.


var obj = { func : function(){ var that = this; function inner(){ if(that===window){ console.log("that===window"); } else if(that===obj){ console.log("that===obj"); } } inner(); } } obj.func();

위 코드는 "that===obj"가 출력된다.위 방법 외에 아래와 같이 bind 메서드를 사용해서 같은 결과를 출력할 수 있다. 밑의 코드는 "this===obj"를 출력한다

var obj = {
    func : function(){
        var inner = function(){
            if(this===window){
                console.log("this===window");
            }
            else if(this===obj){
                console.log("this===obj");
            }
        }.bind(this);
        inner();
    }
}

obj.func()

 

5. 생성자에서 사용될 때, this는 이 생성자로 인해 생성된 새로운 객체를 가르킨다.

function obj(){ this.thisValue = this; this.func = function(){ if(this===window){ console.log("this===window"); } else if(this===obj){ console.log("this===obj"); } else{ console.log("this===other Object"); } } } var A = new obj(); A.func();

이는 일반적인 this 사용과 같다. 위 생성자의 프로퍼티로 들어있는 func메서드는 this가 어떤 값인지를 출력한다. 새로운 객체를 생성하고 위 메서드를 실행해보면,"this===other Object"가 출력된다. 즉 여기서 this는 window도, obj아닌 새로운 값을 가진다는 것을 알 수 있다.
이 this는 새롭게 생성된 객체 A를 가르키는지 확인을 위해 다음 코드를 입력해보자.

A.thisValue === A

여기서 thisValue 프로퍼티는 this의 값을 저장하고 있다. 위 코드를 입력하면 true를 반환하는데, 이를 통해 생성자의 this는 새로운 객체를 생성할 시 그 객체(여기서는 객체 A)를 가르킴을 확인할 수 있다.

 

apply 메서드와 call 메서드로 this 제어

this는 이렇게 어디서 사용되냐에 따라 참조하는 값이 다르다. 근데 function.prototype의 apply 메서드와 call 메서드를 이용해서 이 this 키워드를 제어할수 있다. 즉 위에 정해진 5가지 경우와 다르게 다른 값을 참조하도록 만들어줄 수 있다.

apply와 call은 인자값만 조금 다르고, 결과는 모두 같으므로 apply로만 예제를 보도록 하겠다.

var obj = {};
function func(){
    if(this===window)   
        console.log("this===window");
    else if(this===obj)
        console.log("this===obj");
    }
    
func();
func.apply(obj);

기본적으로 함수 func는 grobal scope에 존재하므로 this는 전역객체(window)를 가르킨다. 따라서 func를 실행하면 "this===window"가 출력된다.

하지만 apply로 인자값에 obj를 전달하여 func.apply(obj)를 실행하면 "this===obj"가 출력된다. 즉 apply를 통해 저 함수의 this값을 obj로 변경하여 함수가 실행되었다.

apply는 인자로 바인딩할 객체와, 해당 함수에 전달할 인자들을 '배열'로 입력받는다. 반면 call은 배열이 아닌 인자들도 입력받는다는 점이 다르니 이는 입맛이나 때에 따라 사용하자.

ex) func.apply(obj,[a,b,c]);    func.apply(obj,a,b,c);



접근제어자 public으로써의 this 

자바스크립트에서는 C++과 JAVA와 같이 public이나 private같은 접근제어자가 따로 존재하지 않는다. 하지만 var,this로 객체를 캡슐화 할 수 있다.

function Member(id,password){
    this.id = id;
    var pass = password;
    this.getPassword = function(){
        return pass;
    }
}

var newUser = new Member("hsp1116","12345");

console.log(newUser.id);
console.log(newUser.pass);
console.log(newUser.getPassword());

위 코드를 실행해보면 console.log(newUser.id)는 올바르게 id를 출력하지만 console.log(newUser.pass)는 undefiend가 출력된다. this.id는 노출되지만 var.pass는 은닉화 된것이다.
대신 getter메서드인 console.log(newUser.getPassword())는 올바르게 출력되는것을 확인할 수 있다.

 

+ Recent posts