자바스크립트를 깨우치다

Table Of Content

Jump to Chapter 2

Chapter 1

객체 만들기

자바스크립트에서는 객체가 왕이다. 거의 모든 것들이 객체이거나 객체처럼 동작하기 때문이다. 그래서 객체를 이해한다는 것은 곧 자바스크립트를 이해한다는 것이 된다.

var cody = new Object(); // Object() 생성자 함수를 호출
cody.living = true;
cody.age = 33;

cody 객체는 정적인 정보이고 메서드가 없으면 JSON과 유사한 데이터베이스일 뿐이다.

var myObject = new Object();
var myString = new String('foo');

객체는 다른 자료형을 가질 수 있다. 자바스크립트는 객체를 사용해 값을 표현한다. String(), Object() 생성자 함수를 사용해 객체를 만들어낸다. 우리도 생성자 함수를 만들 수 있다.

var Person = function(living, age) {
    this.living = living;
    this.age = age;
}

자바스크립트 네이티브 / 내장 객체 생성자

Note : 

* Math는 예외다. Math는 생성자 함수가 아닌 정적 객체인데, 즉 var x = new Math();와 같이 사용할 수 없다는 뜻이다. Math.PI처럼 여러 수학 관련 함수들을 모아두기 위한 그릇으로서 자바스크립트가 정의한 객체 네임스페이스 일 뿐이다.

* 네이티브 객체는 어디서든 사용할 수 있도록 만들어주기 때문에 "전역 네이티브 객체" global objects라고 부르기도 한다. 이 용어는 스코프 체인의 제일 위에 있는, 예컨데 웹브라우저의 winodw와 같은 머리 전역 객체와는 다른 의미이므로 혼동해서는 안된다.

사용자 정의 객체 생성자 함수

var myFunction = function() {
    return {prop : val};
}

new 연산자를 사용한 생성자 인스턴스 생성

자바스크립트 언어에는 Number(), String() Boolean(), Object(), Array(), Date(), RegExp(), Error() 9개의 미리정의된 네이티브 생성자가 포함되어 있다. 이러한 생성자 함수와 new 연산자를 함께 사용하면 객체의 인스턴스를 얻을 수 있다.

// new 키워드를 사용한 각 네이티브 생성자의 인스턴스 생성

var myNumber = new Number(23);
var myString = new String('male');
var myBoolean = new Boolean(false);
var myObject = new Object();
var myArray = new Array('foo','bar');
var myFunction = new Function("x", "y", "return x*y");
var myDate = new Date();
var myRegExp = new RegExp('\bt[a-z]+\b');
var myError = new Error('Crap!');

// 만들어진 객체의 생성자가 무엇인지 기록 및 확인
console.log(myNumber.constructor); // Number()가 기록된다
console.log(myString.constructor); // String()이 기록된다
console.log(myBoolean.constructor); // Boolean()이 기록된다
console.log(myObject.constructor); // Object()가 기록된다
console.log(myArray.constructor); // 최신 브라우저에서는 Array()가 기록된다
console.log(myFunction.constructor); // Function()이 기록된다
console.log(myDate.constructor); // Date()가 기록된다
console.log(myRegExp.constructor); // RegExp()가 기록된다
console.log(myError.constructor); // Error()가 기록된다

리터럴을 사용한 값 생성하기

자바스크립트에는 new Foo() 같은 방법을 사용하지 않아도 대부분의 네이티브 객체 값을 만들 수 있는 “리터럴(literal)”이라는 축약 표현이 있다.

var myNumber = new Number(23); // 객체
var myNumberLiteral = 23; // 원시 숫자값, 객체가 아님

var myString = new String('male'); // 객체
var myStringLiteral = 'male'; // 원시 문자열값, 객체가 아님

var myBoolean = new Boolean(false); // 객체
var myBooleanLiteral = false; // 원시 불리언값, 객체가 아님

var myObject = new Object(); // {}
var myObjectLiteral = {}; // {}

var myArray = new Array('foo', 'bar'); // [ 'foo', 'bar' ]
var myArrayLiteral = ['foo', 'bar'];  // [ 'foo', 'bar' ]

var myFunction = new Function("x", "y", "return x*y"); // [Function: anonymous] 
var myFunctionLiteral = function(x, y) {return x*y}; // [Function: myFunctionLiteral]

var myRegExp = new RegExp('\bt[a-z]+\b');
var myRegExpLiteral = /\bt[a-z]+\b/;

참고로 new를 사용하지 않으면 객체가 아니라 원시값이 반환된다. var primitiveString = String('foo');

Note : 문자열, 숫자, 불리언 자료형에 대해 리터럴 값을 사용하면 이 값을 객체처럼 다루기 전까지는 리터럴에 해당하는 복합 객체가 만들어지지 않는다. 리터럴의 메소드/속성에 접근하면 자바스크립트는 먼저 리터럴 값에 해당하는 래퍼 객체를 만들고 이를 통해 메소드나 속성에 접근한후 래퍼객체를 제거해 다시 값을 리터럴 형으로 되돌린다. 이 덕분에 문자열, 숫자 불리언 값은 원시(혹은 단순) 자료형으로 취급된다. 이런 특성이 있다는 것을 이해하여 "자바스크립트에서 모든 것은 객체처럼 동작한다"는 사실을 "자바스크립트에서 모든 것은 객체다"라는 말과 혼동하지 않았으면 좋겠다.

원시값 (= 단순값)

원시값은 어떻게 저장 복사되는가

원시값은 액면가 그대로 저장되고 관리한다. ‘foo’ 자체가 메모리에 저장된다. 원시값은 복사된다.

객체는 저장된 참조가 같은가 이다.

복합객체 (=합성객체)

네이티브 객체 생성자들은 한 개 이상의 원시값이나 복합객체를 저장할 수 있기 때문에 복합적이라 볼 수 있다. 복합객체는 어떤 값이든 포함할 수 있기 때문에 복합객체가 메모리에서 차지하는 크기는 명확하지 않다고 말할 수있다.

var object = { // 복합객체, 합성객체
    myString : 'string'
}

var myString = 'string' // 원시값

복합객체는 어떻게 저장 복사 되는 가

메모리 주소만 복사한다.

복합 객체는 참조를 비교한다.

복합 객체를 비교할 때는 두 복합 객체가 같은 객체를 참조하고 있을 때만(즉, 같은 주소를 가지고 있는 경우에만) 같다고 판단한다.

복합 객체는 동적 속성을 포함한다.

복합객체를 가리켜 참조 객체라고 부르기도 한다. 복합 객체는 원한다면 얼마든지 참조를 가질 수 있다.

객체를 정의하고 참조를 만든후 객체를 갱신하면 해당 객체를 가르키는 모든 참조도 같이 갱신되므로 동적인 객체 속성도 가능해진다.

typeof 연산자

typeof 연산자는 다루는 값의 자료형을 문자열로 반환할 때 사용한다. 하지만 반환되는 문자열은 정확하거나 논리적이라고 보기는 어렵다.

이 연산자를 사용할 때는 다루고 있는 값(원시값이든 복합 객체이든)이 반환할 문자열이 무엇일지 주의를 기울여야 한다.

동적 속성 덕분에 객체 수정이 가능하다.

네이티브 객체를 수정하지 말아야 한다고 생각한다. 그러나 수정 가능 여부와 개인적인 의견은 별개의 문제다.

String.prototype.trimIT = function() {

}

var myString = 'trim me';
myString.trimIT();

생성자 인스턴스에는 자신의 생성자 함수를 가리키는 속성이 있다.

객체를 인스턴스로 만들면 이 객체/인스턴스에는 constructor라는 속성이 자동으로 추가되는데, 이 속성은 해당 객체를 만든 생성자 함수를 가르킨다.

var foo = {};
console.log(foo.constructor) // Object() 생성자 함수를 가르킨다.

어떤 인스턴스를 다루고 있는데 인스턴스를 만든 생성자 함수를 알 수 없는 경우(특히 다른 사람이 작성한 코드일 때), 이 속성을 사용하면 간편하게 생성자 함수를 찾을 수 있다.

리터럴/원시값의 생성자도 올바르게 찾아낸다.

var myNumber = new Number('23');
var myNumberL = 23;

console.log(myNumber.constructor); // Number
console.log(myNumberL.constructor); // Number

사용자 정의 생성자 함수와도 잘 동작하지만, constructor를 통해 생성자 함수의 실제 이름을 알고 싶다면 생성자 함수를 만들 때 함수표현식에 실제 이름을 표현 시켜야 한다.

객체가 특정 생성자 함수의 인스턴스인지 확인하기

instanceof

Note
* 자바스크립트의 모든 객체는 Object()생성자를 상속하므로 Object()의 인스턴스 인지를 물으면 항상 true를 반환한다
* 원시값이 래퍼 객체의 인스턴스를 물으면 false를 반환한다. 예) 'foo' instanceof String 은 false. 만약 new 연산자를 사용해 'foo' 문자열을 만들었으면 true이다.

생성자를 통해 만든 인스턴스에 인스턴스 속성 추가하기

var myArray = new Array();
myArray.prop = "test";

인스턴스는 자신만의 고유한 속성은 물론 프로토타입 체인에서 상속받은 속성도 포함할 수 있으며, 인스턴스로 만든후에 속성을 추가할 수도 있다.

“자바스크립트 객체”와 “Object() 객체”의 의미

자바스크립트 객체라는 용어는 자바스크립트의 모든 객체를 가르킬 때 사용됐다. -Array() 객체, Object() 객체 포함- Object() 객체는 Object()생성자로 만든 객체 인스턴스에만 해당하는 용어이다.

Chapter 2

복합 객체는 자바스크립트 자료형 대부분을 속성으로 포함할 수 있다.

복합 객체에 다른 객체 포함하기

// 객체 체인을 만들었다.
var object1 = {
    object1_1 : {

    }
    object1_2 : {

    }
}

// 빈배열안에 빈배열안에 빈 배열이 있다.
var myArray = [[[]]]; 

// 함수 안에 함수 안에 함수가 있다.
var myFunction = function() {
    var myFunction = function() {
        var myFunction = function() {

        }
    }
}

// 위를 모두 섞을 수도 있다.
var foo = [{
    foo : [function(){

    }]
}]

점 표기법과 각괄호 표기법을 사용한 객체 속성 접근

각괄호 표기법은 필요한 경우를 제외하곤 그리 많이 사용되지 않는다.

각괄호 표기법은 속성 이름 문자열을 저장하고 있는 변수를 사용해 속성 키에 접근해야 할 때 매우 편리하다.

var object = {
    foo : 'foo';
    bar : 'bar';
}
var string1 = 'foo';
var string2 = 'bar';

console.log(object[string1], object[string2])
Note
* object.object.object 이를 가르켜 객체 체이닝이라고 한다.
* 자바스크립트에서 객체는 가변적이다. 대부분의 객체들은 언제든 값을 가져오거나 설정하거나 갱신할 수 있다. 

객체 속성 삭제하기

delete 연산자를 사용하면 객체에서 특정 속성을 완전히 제거할 수 있따.

Note
* delete 연산자는 프로토타입 체인에 있는 속성을 제거하지 않는다.
* delete 연산자는 객체에서 속성을 제거할 수 있는 유일한 방법이다. 속성을 undefined 또는 null로 설정하면 속성의 값이 변경될 뿐 속성이 삭제되지 않는다.

객체 속성의 참조를 찾는법

접근한 속성이 객체에 포함되어 있지 않으면 바로 undefined를 반환하는 것이 아니라 프로토타입 체인을 이용해 속성과 메서드를 찾은 후에 값을 반환한다.

예를들어 myArray.foo를 탐색하면 바로 undefined를 반환하는 것이 아니라 Array.prototype과 Object.prototype을 탐색한 후에 반환한다.

객체의 속성에 접근하면 객체 인스턴스에 해당 속성이 있는지 확인한다. 객체에 속성이 있으면 속성의 값을 반환하고 이때에는 프로토타입체인을 끌어들이지 않았으므로 상속이 발생하지 않는다. 하지만 객체에 찾는 속성이 없으면 자바스크립트는 해당 객체 생성자 함수의 prototype 객체를 뒤적인다.

**모든 객체 인스턴스에는 인스턴스를 만든 생성자 함수를 가리키는 비밀 링크 (__proto__ 라 부른다.)를 속성으로 가지고 있다. 이 비밀 링크를 사용하면 생성자 함수를 바로 알 수 있어 인스턴스 생성자 함수의 prototype 속성에 접근할 때 유용하다.

자바스크립트 객체를 다룰 때 가장 햇갈리는 부분 중 하나이다. 객체는 다른 객체로부터 속성을 상속받을 수 있다. 속성을 공유하는 것이다. 자바스크립트는 프로토 타입 체인을 사용해 네이티브 객체를 모두 이런 식으로 연결해 놓았다.

hasOwnProperty를 사용해 프로토타입 체인에서 상속받은 속성인지 확인하기

in 연산자를 사용하면 객체의 속성을 확인할 때 프로토타입 체인에서 상속받은 속성까지 포함하지만, hasOwnProperty 메소드를 사용하면 객체의 속성이 프로토타입 체인에서 상속받지 않은 해당 객체의 고유한 것인지 확인 할 수 있다.

var myObject = {foo : 'value'};
console.log(myObject.hasOwnProperty('foo')); // true
console.log(myObject.hasOwnProperty('toString')); // false

in 연산자를 사용해 객체가 주어진 속성을 포함하는지 확인하기

in 연산자는 참조한 객체에 포함된 속성은 물론 프로토타입 체인을 통해 상속받은 속성도 확인한다.

var myObject = {foo : 'value'};
console.log('foo' in myObject); // true
console.log('toString' in myObject); // true

for in 루프를 사용해 객체의 속성 탐색하기

var cody = {
    age : 23,
    gender : 'male'
}
for(var key in cody) {
    if(cody.hasOwnProperty(key)) console.log(key); // 프로토타입 체인에서 상속받은 속성은 표시하지 않는다.
}

호스트 객체 vs 네이티브 객체

일반적으로 자바스크립트가 실행되는 환경(예 : 웹브라우저)에는 호스트객체라는 것이 있다. 호스트 객체는 ECMAScript 명세에서 정의되지 않았으나 코드를 실행할 때는 사용될 수 있는 객체를 뜻한다.

예를들어 웹브라우저 환경에서는 window객체와 window객체가 포함한 모든 객체를 호스트 객체로 볼 수 있다

for(x in window) {
    console.log(x);
}

위 코드에서 네이티브 자바스크립트 객체는 보이지 않는다.

웹 브라우저에서 가장 유명한 호스트 객체는 HTML 문서와 동작하는 인터페이스로서 보통은 DOM으로 많이 알려져 있는 것이다.

for(x in window.document) {
    console.log(x);
}

자바스크립트 명세는 호스트 객체에 관여하지 않고, 호스트 객체 역시 자바스크립트 명세에 관여하지 않는다. 자바스크립트에서 제공하는 것(자바스크립트 1.5, ECMAScript3 vs 모질라의 자바스크립트 1.6, 1.7)과 호스트 환경이 제공하는 것 사이에는 구분선이 있으며 이 두가지를 서로 헷갈려선 안 된다.

자바스크립트 코드를 실행하는 호스트 환경에는 두가지 객체가 있다.

underscore.js

자바스크립트 1.5에는 객체를 조작하고 관리하는 기능이 부족하다. underscore.js에는 객체를 다루는 기능이 많이 포함되어 있다.

Chapter 3

Object() 객체 사용

Object() 생성자를 사용해 빈 객체를 만드는 방법은 물론 Object() 생성자 함수와 같은 객체의 클래스 - Person() - 을 만드는 방법도 숙지하고 있어야 한다.

Object() 매개변수

한개의 인수를 전달한다. 매개변수가 없으면 null이나 undefined를 전달했다고 가정한다.


// Object() 생성자를 사용해 문자열, 숫자, 배열, 함수, 불리언, 정규 표현식 객체를 만든다.

console.log(new Object('foo')); // String {'foo'}

// 실제로는 사용되지 않는 방법이다. 가능하다는 것만 참조

Object 속성과 메서드

객체 리터럴을 사용한 Object() 객체 생성

var cody = {
    name : 'cody'
    age : 23,
}

모든 객체는 Object.prototype을 상속받는다.

프로토타입 체인의 가장 끝에는 Object() 생성자 함수의 prototype 속성이다.

Object.prototype.foo = 'foo';

var myString = 'bar'
console.log(myString.foo); // 프로토타입 체인을 통해 Object.prototype.foo에서 가져왔다.

객체

최상위 객체

// browser
Object.prototype

// browser result
constructor
hasOwnProperty

노드에서는 아래와 같다.

Object.prototype

// result
{}

// 보이지는 않지만 모두 정의 되어 있다.

Object를 객체 인스턴스로 만들면

const object = new Object(); // new 연산자
const objectLiteral = {}; // 리터럴

prototype에 선언된 method를 사용할 수 있다.

const object = new Object(); // new 연산자
const objectLiteral = {}; // 리터럴

prototype에 선언된 속성도 가져올 수 있다.

Object.prototype.foo = 'hello foo';
const object = {};
console.log(object.foo); // 'hello foo';

좀 더 자세히 말하자면 객체 인스턴스가 생성될때 prototype에 선언된 것을 상속받아 __proto__ property를 만들고 객체를 만들어 낸다.

Object.prototype.toCustomString = function() {
    console.log('hi');
}

const object = {};
object.toCustomString(); // 'hi'

상속을 적용해보면 어떨까. new 생성하면 this는 생성자의 인스턴스를 가르킨다. - 뒤에 this 에서 다룬다. -

Object.prototype.toCustomString = function() {
    console.log(this.name);
}

const object = [];

function Parent() {
    this.name = 'parent';
}

const parent = new Parent();
parent.toCustomString(); // 'parent'

여기서 call()을 적용해보자.

Object.prototype.toCustomString = function() {
    console.log(this.name);
}

const object = [];

function Parent() {
    this.name = 'parent';
}

const parent = new Parent();


object.toCustomString(); // undefined
parent.toCustomString(); // parent
object.toCustomString.call(parent); // parent
parent.toCustomString.call(parent); // parent

call()을 적용하지 말고 상속을 한번 더 해보자.

Object.prototype.toCustomString = function() {
    console.log(this.name);
}

const object = {};

function Parent() {
    this.name = 'parent';
}


function Child() {
    Parent.call(this);
}

Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

Child.prototype.toStringInChild = function() {
    console.log(this.name);
}

const parent = new Parent();
const child = new Child();
child.toStringInChild(); // Parent.call(this); 없으면 undefined, 있으면 'parent'

in을 사용하면 상속받은 속성까지 - 부모의 prototype에 정의 되어 있던 속성까지 모두 상속 받는다. -

Object.prototype.toStringCustom = function() {
    console.log(this.name);
}

Object.prototype.foo = 'foo';

const object = {};

function Parent() {
    this.name = 'parent';
}

Parent.prototype.toStringParent = function() {
    console.log(this.name);
}

function Child() {
    Parent.call(this);
}

Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

Child.prototype.toStringInChild = function() {
    console.log(this.name);
}

const parent = new Parent();

for(let prop in parent) {
    console.log('parent :: ', prop, ' : hasOwnProperty', parent.hasOwnProperty(prop));
}

const child = new Child();
for(let prop in child) {
    console.log('child :: ', prop, ' : hasOwnProperty', child.hasOwnProperty(prop));
}


// result
// parent ::  foo  : hasOwnProperty false
// parent ::  toStringCustom  : hasOwnProperty false  // Object.prototype에 선언되어 있었다. in에 걸렸지만 객체 원래의 속성은 아니다.

// parent ::  name  : hasOwnProperty true // 주의해야할 것은 function 안에 this로 선언한 것은 true에 걸리고
// parent ::  toStringParent  : hasOwnProperty false // prototype으로 선언한 것은 false에 걸린다.

// child ::  name  : hasOwnProperty true // 오직 child 생성자 함수 안에서 this로 선언된것만.
// child ::  constructor  : hasOwnProperty false
// child ::  toStringInChild  : hasOwnProperty false
// child ::  toStringParent  : hasOwnProperty false
// child ::  toStringCustom  : hasOwnProperty false
// child ::  foo  : hasOwnProperty false

주의해야할 것은 function 안에 this로 선언한 것은 true에 걸리고, prototype으로 선언한 것은 false에 걸린다.

function Hello() {
    this.propInFunction = 'hi there';
}

Hello.prototype.propInPrototype = 'hi there';

const hello = new Hello();
for(let prop in hello) {
    console.log(`${prop} is my own property ${hello.hasOwnProperty(prop)}`);
}

call()연습

const targetA = {
    a : 3,
    b : 4,
}

const targetB = {
    a : 10,
    b : 20,
}

function addWith() {
    return this.a + this.b;
}

function addArgumentsA(number) {
    return this.a + this.b + number;
}

function addArgumentsB() {
    return this.a + this.b + arguments[0];
}

addWith(); // NaN
addWith.call(targetA); // 7
addWith.call(targetB); // 30
const resultA = addArgumentsA.call(targetB, 4); // 34
const resultB = addArgumentsB.call(targetB, 4); // 34

this에 대해

함수를 만들면 이 함수를 속성 또는 메서드로 포함하고 있는 객체를 this로 참조한다.

var cody = {
    getGender : function(){
        console.log(this);
    }
}

여기서 getGender 함수는 cody 객체 안에서 실행되므로 thiscody를 가르킨다.

new를 사용해서 function을 생성자 함수로 사용하면 this는 이전처럼 상위객체를 this로 참조하는 것이 아니라, 생성자함수로 만들어져서 반환되는 객체를 this로 참조한다.

이것이 this가 조금 어렵게 느껴지는 게, 함수가 호출될때의 컨텍스트에 따라 달라지기 때문이다. 여기서 말하는 컨텍스트란 함수를 싸고 있는 객체의 상황을 말한다.

Extras

Tricky part

이름이 비슷하지만 다른용어들

클로저가 생기지 않는 경우

스코프가 생기지 않는 경우

Extras

연습