객체와 값

 - 자바스크립트의 Object는 Undefined, Null, Boolean, Symbol 등등과 같이 자료형 중 하나이다.

 - 자바스크립트의 모든 "값"은 이런 자료형의 값이다.

자료형(타입)  
undefined => undefined(단일)
null => null(단일)
Boolean => true, false

이런식이다.

 

객체란

1. 자바 : 필드 (속성) + 메소드 (동작)

2. 자바스크립트 : 프로퍼티의 집합 (프로퍼티 = String type의 이름과 속성리스트로 이루어진 것)

 

속성이란?

프로퍼티의 상태를 정의하고 설명하는 것

 

프로퍼티란?

 - 종류 : 데이터 프로퍼티, 접근 프로퍼티

     

1. 데이터 프로퍼티

  • value : 자바스크립트의 모든 자료형 (값)
  • writable : 데이터 프로퍼티 변경 가능 여부
  • enumerable : for - in 구분을 이용한 열거 가능 여부
  • configurable : 프로퍼티 제거가능여부(delete), 접근프로퍼티 변경 가능여부(getter,setter), 쓰기 불가여부, enumerable 속성의 수정 가능여부

=> value는 값이고 나머지 세가지 속성은 value으로의 접근 권한자를 설정하기 위한 속성

 

2. 접근(자) 프로퍼티

  • get 접근자 (get accessor): Function 객체 혹은 undefined 지정
  • set 접근자 (set accessor) : Function 객체 혹은 undefined 지정
  • enumerable : for - in 구문을 이용한 열거 가능여부
  • configurable : 제거 가능여부, 데이터프로퍼티 변경 가능 여부, 다른 속성들의 수정 가능여부

=>

 - value와 writable 대신 getter, setter를 사용한다고 보면 됨.

 - ES6 부터 나온 문법

 - 객체 속성 값에 대한 접근 권한자 역할을 함

 - get & set 과 writable 속성을 함께 설정할 수 없음 (get & set 속성 자체로 writable 속성의 역할을 가지기 때문)

 

 

프로퍼티에 접근하는 방법

- 점표기법 : ex) user.name;

- 대괄호표기법 : String type 의 Key로 불러와야함. 객체를 사용한다면 toString() 을 호출해서 해당하는 문자열 값을 받아온 뒤, 그것을 key으로 해서 가져옴

var obj = {};
obj['100'] = 'one hundred';

//숫자 100은 '100'으로 변환됨
console.log(obj[100]); //one hundred

var foo = {prop: 'f'}, boo = {prop: 'b'};
obj[foo] = 'Foo';
console.log(obj[bar]); //'Foo'
console.log(obj['[object Object]']); //'Foo' obj[bar]의 bar가 toString으로 인해 '[object Object]'으로 변환됨

 

그림으로 표현하자면 아래와 같음

객체는 각각의 프로퍼티를 가지고, 프로퍼티를 설정하는 종류와 방법은 위 그림과 같이 2개이다.

 

* 프로퍼티는 Object.defineProperty 또는 Object.defineProperties를 사용해서 수정이 가능하다!

function User(name, department) {
    var _department = department;
    var _name = name;
    Object.defineProperty(this, 'name', {
        value: _name, // name이라는 이름으로 접근 했을 때 실제 접근하는 프로퍼티는 _name, 이는 자바의 캡슐화 개념과 유사
        writable: true,
        enumberable: true,
        configurable: false
    });
    
    Object.defineProperty(this, 'department', { //this 키워드로 실행 컨텍스트 전달 (User)
        get: function () {
            return _department;
        },
        set: function(value) {
            _department = value;
        },
        enumberable: true,
        configurable: true
    });
    Object.defineProperty(this, 'greeting', { 
        value: function () {
            console.log('Hi, I\'m ' + this.name + '. ');
        },
        enumberable: false,
        configurable: false
    });
}

위 예제코드를 아래로 확인하면

var user = new User('Sunny', 'Engineer');
console.log(User.department); //'Engineer' => 프로퍼티에 접근
user.department = 'Marketing';
user.greeting();
Object.defineProperty(user, 'name', {
    enumerable: true;
}); // configurable이 false 였기 때문에 속성 변경시 에러가 남
delete user.name; //에러
delete user.department; //에러 X
for (var prop in user) { //에러
    console.log(prop);
}

위 결과가 나온다.

 

아래는 리터럴 get, set 을 설정하는 예제 코드이다.

var user = {
	_name : 'kim'
};
Object.defineProperty(user, 'name', {
	get: function () {
		return this._name;
	},
	set: function (value) {
		this._name = value;
	}
});
console.log(user.name); // kim

아래와 같이 _name을 name으로 변경할 경우 에러가 난다.

var user = {
	name : 'kim'
};
Object.defineProperty(user, 'name', {
	get: function () {
		return this.name;
	},
	set: function (value) {
		this.name = value;
	}
});
console.log(user.name); // Uncaught RangeError: Maximum call stack size exceeded

원인은

1. console.log(user.name); 의 user.name이 user의 프로퍼티인 name의 getter 를 호출

2. getter 내부의 return this.name 이 기본 다시 name의 getter를 찾는다.

3. 그러면 다시 name에 접근해서 getter를 찾는다

=> 위 과정을 반복하면 무한 반복이 된다. (재귀호출)

 

 

 

 

 

자바스크립트의 상속 : 프로토타입을 활용해서 상속한다.

 

프로토타입이란?

  •  다른 객체에 공유 프로퍼티(share properties)를 제공하는 개체를 의미함
  •  오직 함수 객체만 프로토타입이라는 것을 가짐 

     - 유일하게 호출이 가능한 객체이기 때문 => 상속이 가능한 이유 (기존의 코드를 물려주는 일)

     - 다른 객체 생성이 가능하기 때문

     - ES6에서 화살표 함수는 프로토타입을 가지지 않음 => 이 부분은 나중에 이해하도록 한다.

  • 함수는 공장으로, 프로토타입은 공장에서 생산된 제품의 명세로 간주하면, new 키워드로 함수 호출시에 제품 주문이 들어가고 공장은 프로토타입에 지정된 방식으로 생산함
//생성자 함수 생성. Function 생성자로 function 객체를 생성함
function User(name, interests) {
    this.name = name;
    this.interests = interests;
}
User.prototype.greeting = function () {
    console.log('Hi I\'m ' + this.name + '. '_);
}

=> constructor 프로퍼티는 생성자 객체를 역참조 하는데, 이를 이용해서 누가 프로토타입 객체를 생성했는지 확인이 가능

console.log(User.constructor == Function); //true

User 생성자 함수가 생성되면 프로토타입 객체를 가짐 == 함수가 생기면 해당 이름을 가진 프로토타입도 자동 생성된다

 

그리고 해당 생성자 함수를 통해서 객체를 생성하면 그 객체는 생성자 함수가 생성한 프로토타입을 __proto__ 라는 프로퍼티에 참조를 합니다.

var user = new User();
console.log(User.__proto__ == User.prototype); // true 

위 코드를 보면 __proto__ 프로퍼티에 User 의 프로토 타입을 참조하고 있는 것을 알 수 있음.

이런 원리를 통해서 생성자 함수를 통해서 생성한 객체는 같은 prototype 객체를 공유할 수 있음

이 __proto__ 참조는 프로토타입체인에서 링크역할을 함

 

즉, 같은 프로토타입만 바라보게 하면 자바의 상속이라는 기능(?)을 자바스크립트에서도 구현할 수 있게 되는 것

 

 

function User(name,interests) {
    this.name = name;
    this.interests = interests;
} 
User.prototype.greeting = function() {
    console.log('Hi I\'m ' + this.name + '. ');
} // 상속 가능한 프로퍼티인 greeting을 생성함

function TeamMember(name,interests,task) {
    User.call(this, name, interests); // User 생성자 함수를 호출하고, (name, interests) 초기화 == User의 내부프로퍼티를 사용하겠다는 의미
    this.task = task;
}
TeamMember.prototype = Object.create(User.prototype); // TeamMember의 prototype이라는 객체가 __proto__ 프로퍼티에 User.prototype을 참조하게 함
TeamMember.prototype.greeting = function () {
    console.log('I \'m ' + this.name + ' welcome to the team!');
}

TeamMember.prototype.greeting = function () {
    console.log('I \'m working on ' + this.task.length + ' task');
}

call 메소드는 User 생성자 함수의 call 메소드(기본생성)를 호출함.

자바클래스의 생성자에서 super()를 호출하는 방식과 유사함

한가지 차이점은 call 메소드 첫번째 인자는 객체여야함! => 여기서 전달하는 객체의 의미는 실행컨텍스트로 역할을 하게됨

 

TeamMember.prototype.greeting 부분은 TeamMember의 프로퍼티를 재정의하고 있지만

User의 프로토타입의 greeting은 전혀 영향을 받지 않고 있음

 

console.log(User.prototype == Teammember.prototype); // false
console.log(User.prototype.constructor == TeamMember.prototype.constructor); // true ==> 프로토타입의 생성자는 같음
console.log(user.__proto__ == member.__proto__); // false

즉, 체인만 연결해두고 프로토타입이 같은 것은 아님.

현재 프로토타입 객체를 생성하게한 생성자 함수의 생성자는 같음!

 

 

var member = new TeamMember('Sunny', ['Traveling'], ['Buy Three tickets', 'Book a hotel']);
member.greeting(); // I'm Sunny, welcome to the team!
member.work(); // I'm working on 2 tasks

console.log(member instanceof TeamMember); // true
console.log(member instanceof User);  // true
console.log(member instanceof Object);  // true
// User를 상속 한 것이므로 member는 User의 객체이다. 동시에 Object의 객체이다. 동시에 TeamMember의 객체이다.

User.prototype.eat = function () {
    console.log("What will I have for lunch?");
};
member.eat(); // what will I have for lunch?

//최상위 객체에 메소드를 추가해본다.
Object.prototype.move = function () {
    console.log('Every Object can move now');
};

member.move(); // Every Object can move now
var alien = {};
alien.move(); // Every Object can move now
User.move(); // Every Object can move now ==> User 또한 Function으로 만든 객체이기 때문에 Object를 상속 받고 있다.
//프로토타입은 객체끼리 공유하는 공간이므로 object 객체를 상속한 TeamMember의 객체 member도 move를 공유한다.

 

 

이제! Object | User | TeamMember로 연결되는 상속체인이 생성됨

그림으로 보자면 이런 느낌..

그림이 너무 성의 없어 보이는데.. 지금은 시간이 없어서 이렇게 적었습니다.. (9시 출근인데 지금 새벽 두시..)

보면, 왼쪽은 생성자 함수이고 우측은 그에 대응하는 프로토타입입니다.

그리고 해석하자면

1. member 객체의 name 프로퍼티에 접근시 자바스크립트는 해당 객체에서 name을 찾을 것이고, name(User의 것)이 있기 때문에 더 이상 체인을 타고 올라가진 않을 것임.

2. 허나 move() 메소드를 찾는다면, 체인을 타고 쭉 올라가 TeamMember 프로토타입에 존재하는지 확인 후 없다면 Object 프로토타입까지 올라가 찾을 것임

 

** 상속체인을 타고 올라가 찾은 프로퍼티가 아닌(상속받지 않은), 오직 해당 객체가 소유한 프토퍼티인지 확인하는 메소드는 hasownProperty();

member.hasownProperty('task') // false
member.hasownProperty('move') // true

 

 

 

console.log(member.__proto__ === TeamMember.__proto__); // true
console.log(TeamMember.__proto__ === User.__proto__); // true
console.log(User.prototype.__proto__ === Object.prototype); // true

보면 알겠지만, 각각 뭐를 참조하고 있는지 유심하게 보길 바랍니다.
그리고 __proto__ 프로퍼티는 상속을 유지해줄수 있는 프로퍼티로 잘못 사용하면 상속이 깨지기 때문에 정말 조심해서 사용해야함!

User.prototype.__proto__ = null;
member.move(); // uncaught Type Error member move is not a function
console.log(member instanceof Object) // false => 상속이 깨짐

대신 프로토타입을 확인할 때는 isPrototypeof를 사용한다!

TeamMember.prototype.isPrototypeof(member) // true

member 객체의 프로토타입은 TeamMember에 상응하는 프로토타입인 TeamMember.prototype 이다.

 

 

 

 

 

스코프와 클로저

 

scope란?

 - 변수의 접근성에 관한 것이다

 - 자바에서 {} (중괄호쌍)을 이용해 class-level scope, method-level scope, block-level scope 범위를 구분지음

예를 들어

public class User {
    private String name; // class 스코프 (클래스 내부 어디서든 접근)
    private List<String> interests;
    
    public User(String name, List<String> interests) {
        this.name = name;
        this.interests = interests;
    }
    //User 의 interests 에 something이 있는지 check
    public boolean isInterestedIn (String something) {
        boolean interested = false; // 메소드 내부에서 유효
        for (int i=0; i<interests.size(); i++) { // {} 중괄호 블럭 내부에서만 유효한 변수 i 
            if (interests.get(i).equals(something)) {
                interested = true;
                break;
            }
        }
        return interested;
    }
}

변수 스코프는 상황에 따라 변하지 않고 정적이고 컴파일러에 의해 결정이 됨

 

반면 자바스크립트는 스코프가 유연함

1. 전역 scope (global)

2. 함수스코프 (function scope)

3. ES6 에서 도입된 블록 스코프 (block). => 이는 let이나 const 키워드를 사용해야만 만들 수 있음

 

//스코프와 클로저 자바스크립트 예제
function bookHotel(city) {
    var availableHotel = 'None';
    for (var i=0; i<hotels.length; i++) { //뒤에 나올 전역변수 hotels 
        var hotel = hotels[i];
        if (hotel.city == city && hotel.hasRoom) {
            availableHotel = hotel.name;
            break;
        }
    }
    //여기서 i와 hotel은 여전히 접근가능
    console.log('checked ' + (i+1) + ' record(s)'); // checked 2 record(s)
    console.log('last checked ' + hotel.name); // last checked Hotel B
    // 이는 i와 hotel 변수가 로컬 변수이긴 하나, 같은 함수의 함수scope이기 때문에 여전히 접근 가능
    // ES6는 let으로 선언을 해서 블록 스코프로 만들 수 있음
    
    {
        function placeOrder() {
            var totalAmount = 200;
            console.log('order placed to ' + availableHotel); // 중첩함수는 몇번을 중첩해도 global 변수에 접근 가능
        }
    }
    placeOrder(); // order placed to Hotel B
    
    // 접근불가 => 중첩 함수라서 그런가.. 함수스코프 내부의 것이고
    // console.log(totalAmount);
    return availableHotel;
}
var hotels = [{name:'Hotel A', hasRoom:false,city:'Sanya'},{name:'Hotel B', hasRoom:true, city:'Sanya'}];
console.log(bookHotel('Sanya')); // Hotel B 출력
//접근불가
// console.log(availableHotel); =>함수스코프라서 접근 불가 X