생활코딩/

[JS] 객체 지향 - 생성자와 new, 전역객체, this, 상속, prototype

hayjo 2020. 9. 5. 00:25

[강의 출처] opentutorials.org/course/743/6584

 

객체지향 - 생활코딩

객체지향 프로그래밍은 크고 견고한 프로그램을 만들기 위한 노력의 산물이다. 객체지향이라는 큰 흐름은 현대적 프로그래밍 언어들을 지배하고 있는 가장 중요한 맥락이라고 할 수 있다. 하지

opentutorials.org


객체 지향 프로그래밍(Object Oriented Programming)

-프로젝트가 확장되면서 기존의 절차적인 방법으로 정리하기 어려워질 때, 기능별로 로직을 구분해서 정리하는 방식 중 하나

-해당 문법과 설계방식(문제의 복잡성을 적절히 단순화하여 설계하는 추상화(abstract)와 그룹핑, 캡슐화 등)을 아우르는 개념

-JS는 Prototype-based Programming에 속함

 

생성자(constructor)와 new

-일반적으로 객체를 만드는 방법은 아래와 같다.

var person = {
    'name' = 'a';
    'introduce' = function(){
        return 'My name is ' + this.name;
    }
}
document.write(person.introduce());

-만약 person이라는 형식으로 객체를 여러 개 만들고 싶다면 생성자를 이용한다.

-생성자(constructor)는 객체를 만드는 함수다. 자바스크립트에는 클래스가 없기 때문에 생성자는 객체와 독립적이다.

function Person();       // 함수를 정의
var p = new Person();    // 새로운 객체가 생성된다
p.name = 'a';
p.introduce = function(){
    return 'My name is ' + this.name;
}

-아래처럼 생성자로 쓰일 함수를 정의할 때, 필요한 로직(메소드와 프로퍼티 등)을 정의해두면 편리하게 객체를 생성할 수 있다.

 이런 작업을 초기화(initialize)라고 부른다.

function Person(name){                      // 생성자로 쓰일 함수 정의
    this.name = name;
    this.introduce = function(){            // 여기서 메소드를 정의해두면
        return 'My name is ' + this.name;   // 이후 선언되는 객체들에서 반복적으로 사용가능
    }                                       // 이런 초기 세팅을 '초기화'라고 부름
};
var p1 = new Person('a');   // 생성자 Person에서 프로퍼티를 'a'로 정의
var p2 = new Person('b');

document.write(p1.introduce() + "<br />");
document.write(p2.introduce() + "<br />");

 

전역객체(Global Object)

-자바스크립트의 모든 객체(전역변수 및 함수를 포함해서)는 window 객체의 프로퍼티다. [참고페이지]

-node.js에서는 전역객체를 global라고 부른다.

function func(){
    console.log('Hello?');
};
func();           // 'Hello?'
window.func();    // 'Hello?'

 

this

-함수 내에서 함수를 호출한 맥락(context)의 객체를 가리킨다. this를 통해 함수와 객체를 연결시킬 수 있다.

/* 별다른 맥락이 없는 경우, 전역객체인 window를 가리킨다 */

function func(){
    if(window === this){
        console.log("window === this")
    }
}
func();        // window === this
/* 객체에 소속된 메소드의 this는 그 객체를 가리킨다 */
var o = {
    func: function(){
        if(o === this){
            console.log("o === this");
        }
    }
}
o.func();       // o === this

-this는 객체 생성이 끝나서 어떤 식별자에 담기기 이전에도(객체 생성 진행 중에도) 해당 객체에 접근이 가능하다.

/* 생성자 안에서 this는 생성될 객체를 가리킨다*/

var funcThis = null;  // 다른 함수에서도 불러와야 하니까 일단 전역변수로 세팅

function Func(){
    funcThis = this;  // 여기에서 this의 값으로 funcThis 재할당
}
var o1 = Func();              // new 없이 생성한 경우
if(funcThis === window){      // 함수 Func() 맥락에서 그 함수의 객체는 window가 됨
    console.log('window');    // 'window'
}                   
var o2 = new Func();          // 생성자를 이용해 생성한 경우
if(funcThis === o2){          // 호출한 시점에서 비어있는 {} 객체가 만들어지고, 그 객체가 생성자 내의 this
   console.log('o2');         // 'o2'
                
                             
function Func(){
    console.log(o);
}
var o = new Func();          // undefined
                             // 이 경우 호출이 모두 끝나야 var o가 할당완료되고, this가 o가 되는데,
                             // 생성 중간에 o를 호출한 것이므로 아직 정의되지 않았다고 출력된다.

-함수의 메소드 apply, call를 통해, 타 객체의 메소드를 적용 가능함

 *리터럴(literal)의 개념

더보기

 new로 객체를 정의하지 않고 일반적인 방법으로 객체를 생성하는 경우 ~ 리터럴이라고 부른다

// 함수 리터럴(function literal)
function sum(x,y){
    return x+y;
}

// 객체 리터럴
var o = { }
var p = { }

// 배열 리터럴
var a = [1, 2, 3];
var o = {}
var p = {}
function func(){
    switch(this){
        case o:
            console.log('o');
            break;
        case p:
            console.log('p');
            break;
        case window:
            console.log('window');
            break;
    }
}

func();           // 'window'
func.apply(o);    // 'o'
func.apply(p);    // 'p'
/* apply가 사용된 맥락에 따라 window.func(), o.func(), p.func()처럼 해석됨 */

 

상속(inheritance)

-어떤 객체의 로직(메소드, 프로퍼티 등)을 prototype 객체를 통해 다른 객체에게 물려줄 수 있는 기능

-상속을 통해 기존 객체로부터 새로운 파생 객체를 만들 수 있음

function Person(name){
    this.name = name;
}
Person.prototype.name = null;               // 빌트인 메소드 prototype으로 프로퍼티 추가도 가능
Person.prototype.introduce = function(){
    return 'My name is ' +this.name;
}
function Programmer(name){
    this.name = name;
}
Programmer.prototype = new Person();        // 이렇게 해서 Person의 로직을 상속 받음
Programmer.prototype.coding = function(){   // 상속받은 부모 객체의 prototype을 복사
    return "hello world!";                  // coding()은 Programmer만 실행 가능한 메소드
}

function Designer(name){
    this.name = name;
}
Designer.prototype = new Person();
Designer.prototype.design = function(){     // Designer의 고유 메소드를 구현
    return "beautiful!";
}

var p1 = new Programmer('a');
console.log(p1.introduce());                // My name is a. Person의 메소드를 상속 받음
console.log(p1.coding());                   // hello world!


var p2 = new Designer('b');
console.log(p2.introduce());                // My name is b. Person의 메소드를 상속 받음
console.log(p2.design());                   // beautiful!
console.log(p2.coding());                   // Designer 객체는 coding 프로퍼티가 없으므로 에러
                                   /* Uncaught TypeError: p2.coding is not a function */

 

prototype

-프로토타입 객체의 프로퍼티들은 생성자를 통해서 객체가 생성될 때 그 객체로 전달된다.

-이렇게 프로토타입을 통해 프로퍼티들이 연결되는 형태를 'prototype chain'이라고 부른다.

function Ultra(){}
Ultra.prototype.ultraProp = true;

function Super(){}
Super.prototype = new Ultra();

function Sub(){}
Sub.prototype = new Super(); // 상속받고자하는 객체를 새로 생성해서 new() 형태로 전달해야함
                             // Super.prototype()형태로 전달할 경우, Sub 객체의 변경사항이
var o = new Sub();           // 부모인 Super 객체에도 영향을 줄 수 있기 때문
console.log(o.ultraProp); // true