인사이드 자바스크립트를 공부하며 정리하는 포스팅입니다.



This 바인딩


자바스크립트에서 함수를 호출할 때 기존 매개변수로 전달되는 인자값에 더해, arguments 객체 및 this 인자가 함수 내부로 암묵적으로 전달됩니다. 여기서 this는 함수가 호출되는 방식(호출 패턴)에 따라 다른 객체를 참조하게(this 바인딩) 됩니다.

지금부터 여러가지 호출 패턴에 따른 this 바인딩에 대하여 나열해 보겠습니다.



1.객체의 메서드 호출할 때 This 바인딩


객체의 메서드를 호출할 때에는 해당 메서드를 호출한 객체로 바인딩 됩니다. 


var person ={
    name : 'song',
    sayName : function(){
        console.log(this.name);
    }
}


위와 같은예제에서 person.sayName();을 호출하면 결과는 'song'이 로그에 찍히게 됩니다. 즉, 자신을 호출한 객체가 this에 바인딩 되었습니다.




2.함수를 호출할 때 this 바인딩


자바스크립트에서 함수를 호출하면, 해당 함수 내부 코드에서 사용된 this는 전역 객체에 바인딩됩니다.(window)

*브라우저에서 전역객체란 window 객체, node.js에서는 global 객체.


var name = 'sooooong';

var sayName =function(){
  console.log(this.name);
}


위와 같은예제에서 sayName();을 호출하면 결과는 'sooooong'이 로그에 찍히게 됩니다. 즉 window.name이 this.name이 되는겁니다.


이러한 함수 호출에서의 this 바인딩 특성은 내부 함수를 호출했을 때에도 동일합니다. 때문에 내부 함수에서 this를 사용할 때 주의해야합니다.

var age = 40;
var person ={
    name : 'song',
    age : 29,
    sayName : function(){
        console.log(this.name);
    },
    sayAge : function(){
     console.log(this.age);

     sayAge2:fucntion(){
      console.log(this.age + 1);
     }
      sayAge2();
    }
}

위와 같은예제에서 person.sayAge();을 호출하면 결과는

29

30

이 아닌

29

41

이 나오게 됩니다. 이유는 sayAge의 내부함수인 sayAge2에서의 this는 window객체이기 때문입니다. 때문에 window.age인 40이 참조되게 되는 것입니다.


이렇게 되는 이유는 자바스크립트에서는 내부 함수 호출 패턴을 정의해 놓지 않기 때문입니다. 내부 함수도 결국 함수이므로 함수 호출 패턴 규칙에 따라서 window에 this가 binding됩니다.


때문에 이러한 자바스크립트의 한계를 극복하려면 부모함수의 this를 내부 함수가 접근 가능한 다른 변수에 저장하는 방법을 사용해야합니다.



var age = 40;
var person ={
    var that = this;
    name : 'song',
    age : 29,
    sayName : function(){
        console.log(this.name);
    },
    sayAge : function(){
     console.log(that.age);

     sayAge2:fucntion(){
      console.log(this.age + 1);
     }
      sayAge2();
    }
}

위와 같이 this를 that에 저장하면 원래 의도했던 29, 30이라는 결과를 얻을수있습니다.

자바스크립트에서는 이러한 this 바인딩의 한계를 극복하기위해 this 바인딩을 명시적으로 할 수 있는 call과 apply라는 메서드를 제공합니다.



3. 생성자 함수를 호출할 때 this 바인딩


자바스크립트의 생성자 함수는 말 그대로 자바스크립트의 객체를 생성합니다. 하지만 c++과 같이 그 형식이 정해지지는 않았고, 기존 함수에 new 연산자를 붙여서 호출하면 해당 함수는 생성자 함수로 동작합니다. 다만 특정 함수가 생성자 함수로 정의되어 있음을 알리려고 첫 글자를 대문자로 하는 것을 권하고 있습니다.


new 연산자로 자바스크립트 함수를 생성자로 호출하면, 다음과 같은 순서로 동작합니다. 


1) 빈 객체 생성 및 this 바인딩

먼저 빈 객체가 생성되고, 이 객체가 생성자 함수가 새로 생성하는 객체입니다. 이 객체는 this로 바인딩됩니다. 따라서 이후 생성자 함수의 코드 내부에서 사용된 this는 이 빈 객체를 가리킵니다.


엄밀히 말해서 빈 객체는 아니고 자신을 생성한 생성자 함수의 prototoype 프로퍼티가 가리키는 객체를 자신의 프로토타입 객체로 설정합니다.


2) this를 통한 프로퍼티 설정

이후에는 함수 코드 내부에서 this를 사용해서, 앞에서 생성된 빈 객체에 동적으로 프로퍼티나 메서드를 생성할 수 있습니다.


3) 생성된 객체 리턴

특별하게 정의된 리턴문이 없는 경우 this로 바인딩된 새로 생성한 객체가 리턴됩니다. 만약 명시적으로 다른 객체를 리턴하도록 했다면 다른 객체가 리턴됩니다.


var Person = function(name){
     this.name = name;
};
var song = new Person('song');
console.log(song.name);


이렇게 생성자를 이용해 객체를 생성하는 방식과 리터럴을 이용해 생성하는 방식의 차이는 프로토타입 객체(_proto_)에 있습니다.

위에 예제에서의 song객체의 프로토타입 객체는 Person이고 리터럴로 동일한 song 객체를 만들면 그 song객체의 프로토타입 객체는 Object입니다.


이는 자바스크립트 객체 생성 규칙 때문입니다. 자바스크립트 객체는 자신을 생성한 생성자 함수의 prototype 프로퍼티가 가리키는 객체를 자신의 프로토타입 객체로 설정합니다.


객체 리터럴 방식에서는 객체 생성자 함수가 Object이고, 생성자 함수 방식의 경우는 생성자 함수 자체가 프로토타입 객체입니다.



3. call과 apply 메서드를 이용한 명시적인 this 바인딩


자바스크립트에서는 this를 특정 객체에 명시적으로 바인딩시키는 방법도 제공합니다. 이를 가능하게 하는 것이 함수 객체의 기본 프로퍼티인 apply와 call메서드입니다. 


이 메서드들은 모든 함수의 부모 객체인 Function.prototype 객체의 메서드이므로, 모든 함수는 다음과 같이 apply를 호출할 수 있습니다.


function.apply(thisArg, argArray);


call과 apply는 기능은 같고 단지 넘겨받는 인자의 형식만 다릅니다.


apply() 메서드를 호출하는 주체는 함수고, 특정 this를 바인딩할 뿐 본질적인 기능은 함수 호출입니다. sayName(); 은 

sayName.apply(); 와 같습니다.


apply의 첫 번째 인자인 thisArg는 apply() 메서드를 호출한 함수 내부에서 사용한 this에 바인딩할 객체입니다.

즉, 첫 번째 인자로 넘긴 객체가 this에 명시적으로 바인딩됩니다.


두 번째 인자인 argArray는 함수를 호출할 때 넘길 인자들의 배열입니다.


call()은 apply()와 기능은 같지만, apply에서 두 번째 인자로 배열 형태로 넘기는 것을 각각 하나의 인자로 넘기는 차이가 있습니다. 


Person.apply(song, ['song', 29]);

Person.call(song, 'song', 29);


call()과 apply() 함수의 대표적인 용도는 유사 배열 객체에서 배열 표준 메서드를 사용할 때 입니다.




인사이드 자바스크립트를 공부하며 정리하는 포스팅입니다.



1.함수 생성


자바스크립트에서 함수를 생성하는 방법은 3가지가 있습니다

1)함수 리터럴

아래 코드에서 add에 해당하는 함수 이름은 생략 가능합니다.


function add(x, y){

return x + y;

}


2)함수 선언문

함수명이 반드시 정의되어 있어야 합니다.


function add(x, y){

return x + y;

}


3)함수 표현식(추천!! 이유는 2번 함수 호이스팅에서 설명하겠습니다.)

아래와 같이 함수에 이름을 만들지 않고 변수에 할당하는 방식을 익명 함수 표현식이라 합니다.

함수 내부에서 재귀적으로 호출할 필요가 있을 때에는 함수에 이름을 붙여주면 됩니다.

여기서 add는 함수 이름이 아닌 익명함수를 참조하는 변수입니다. //함수변수


var add = function (x, y){

return x + y;

};


함수 표현식으로 함수를 생성하는 경우에는 세미콜론을 필수로 사용해야합니다.

이유는 다음과 같은 오류가 발생할 수 있기 때문입니다.


var func = function() {
return 42;
}
(function(){
return 24;
})();


위와 같은 코드가 있을 때 func 함수 표현문 끝에 세미콜론이 없기 때문에 자바스크립트 엔진은 중괄호에서 끝났다고 인식하지 못하고 ()로 감싸진 익명함수 생성 리터럴을 무시하고 ()로 func를 실행하게됩니다.

때문에 그 뒤에 있는 ()는 func의 리턴 값인 42()와 같이 인식하게 되어 오류가 발생합니다.




2.함수 호이스팅


앞에서 함수를 생성하는 여러가지 방법에 대해서 나왔는데 이들 사이에는 동작 방식이 약간 차이가 있습니다.

그중의 하나가 바로 함수 호이스팅(Function Hoisting)입니다. 


add(3, 5);

function add(x, y){
return x + y;
};

add(3, 4);


맨 위에 add(3, 5); 같은 경우에는 아직 함수가 선언되지 않았는대도 불구하고 호출이 가능합니다. 

함수가 위치한 위치에 상관없이 함수 선언문 형태로 정의한 함수의 유효 범위는 코드의 맨 처음부터 시작한다. 

이것을 함수 호이스팅이라고 합니다.


함수 표현식으로 함수를 생성하면 이러한 함수 호이스팅이 일어나지 않는다. 


3.함수도 객체다.


자바스크립트에서는 함수를 일급객체라고 부른다.


1) 프로퍼티를 가질 수 있다.


2) 자바스크립트에서 함수는 값으로 취급된다.

- 리터럴에 의해 생성

- 변수나 배열의 요소, 객체의 프로퍼티 등에 할당 가능

- 함수의 인자로 전달 가능

- 함수의 리턴값으로 리턴 가능

- 동적으로 프로퍼티를 생성 및 할당 가능

*위와 같은 자바스크립트 함수의 특성들을 가지고 있는 객체를 일급객체라고 한다.


3) 함수 객체의 기본 프로퍼티

함수는 객체이므로 함수 객체만의 표준 프로퍼티가 정의되어 있습니다.

대표적으로 arguments, caller, length, name, apply, call, bind이 있습니다.

이들 프로퍼티들은 Function.prototype 객체로 부터 받은 것입니다.


- arguemnts

함수로 전달된 인자값들

- length

인자의 수

- caller

자신을 호출한 함수

- name

함수의 이름

- prototype

prototype 프로퍼티는 _proto_와 다릅니다.

* _proto_ : 자신의 부모 역할을 하는 프로토타입 객체를 가리킨다.


prototype 프로퍼티는 함수가 생성될 때 만들어지며 단지 constructor 프로퍼티 하나만 있는 객체를 가리킵니다.

그리고 prototype 프로퍼티가 가리키는 프로토타입 객체의 constructor 프로퍼티는 자신과 연결된 함수를 가리킵니다.


즉, 자바스크립트에서는 함수를 생성할 때, 함수 자신과 연결된 프로토타입 객체를 동시에 생성하며

이 둘은 prototype 과 constructor라는 프로퍼티로 서로를 가리킵니다.




그러니까 예를들어 func 함수는 생성될 때 프로토타입 객체가 동시에 생성되고 이 객체를 prototype 프로퍼티로 참조합니다. 이 프로토타입 객체는 constructor라는 프로퍼티를 가지고 있고, 이 또한 객체이므로 _proto_ 프로퍼티도 가지고 있습니다.




prototype 프로퍼티가 가리키고 있는 func의 프로토타입객체는 constructor 프로퍼티로 func 함수를 가리킵니다.




이해하기 힘든 개념일 수 있습니다. 이후 프로토타입과 프로토타입 체이닝에 대해서 자세히 포스팅할 예정이니 이정도 책 내용 정도로만 이해하시고 넘어가시면 될 것 같습니다.



4.함수의 다양한 형태


1) 콜백함수

함수를 명시적으로 호출하는 것이 아니라 개발자는 함수를 등록하고, 특정 이벤트가 발생하거나 특정 시점에 도달하였을 때 시스템에서 호출하는 함수를 말합니다.


대표적인 콜백 함수의 사용 예가 자바스크립트에서의 이벤트 핸들러 처리입니다. 웹페이지가 로드 되거나, 키보드 입력되는 등 DOM이벤트가 발생할 경우 브라우저는 정의된 DOM 이벤트에 해당하는 이벤트 핸들러를 실행시킵니다. 개발자가 만약 해당하는 이벤트 핸들러에 콜백 함수를 등록했다면 이벤트가 발생할 때마다 브라우저에 의해 실행되게 됩니다.



2) 즉시 실행 함수

함수를 정의함과 동시에 바로 실행하는 함수를 즉시 실행 함수(immediate functions)라 합니다.

함수가 선언되자마자 실행되게 만든 즉시 실행 함수의 경우 같은 함수를 다시 호출할 수 없습니다. 따라서 최초 한 번의 실행만을 필요로 하는 초기화 코드에 사용할 수 있습니다.

예를 들어 아래와 같은 형태로 사용하실 수 있습니다.

(function(name){
//initialize 함수
console.log("Init");
InitFrame();
InitModel();
InitMain();
user.name = name;
})("song");

그리고 jQuery와 같은 자바스크립트 라이브러리나 프레임워크 소스들에서 사용되니 알아두면 좋습니다.

jQuery에서 즉시 실행 함수를 사용하는 이유는 함수 유효 범위 때문입니다.

*자바스크립트에서 변수는 전역 유효 범위를 가진다. 그러나 함수 내부의 변수(var)는 함수 유효 범위를 가진다.



따라서 라이브러리 코드를 즉시 실행 함수 내부에 정의해두면, 라이브러리 내의 변수들에 외부에서 접근할 수 없습니다. 따라서 즉시 실행 함수 내에 라이브러리 코드를 추가하면 전역 네임스페이스를 더럽히지 않고, 다른 라이브러리들이 동시에 로드 되더라도 라이브러리간 변수 이름 충돌을 방지할 수 있습니다.

3) 내부 함수

함수 내부에 정의된 함수를 내부 함수라고 부릅니다. 내부 함수는 클로저를 생성하거나 부모 함수 코드에서 외부에서의 접근을 막고 독립적인 헬퍼 함수를 구현하는 용도로 사용합니다.


내부 함수의 특징은 다음과 같습니다.

- 내부 함수에서는 자신을 둘러싼 부모 함수의 변수에 접근이 가능하다.

C++과 같은 언어를 사용하셨던 분들은 낯설게 느끼실 수 있습니다. 이러한 것이 가능한 이유는 자바스크립트의 스코프 체이닝 때문입니다.  


*클로저, 스코프 체이닝은 따로 포스팅하겠습니다. ^^



- 내부 함수는 일반적으로 자신이 정의된 부모 함수 내부에서만 호출이 가능하다.


5. 함수 리턴


1) 일반 함수나 메서드는 리턴값을 지정하지 않을 경우, undefined 값이 리턴된다.

2) 생성자 함수에서 리턴값을 지정하지 않을 경우 생성된 객체가 리턴된다.

3) 생성자 함수에서 리턴값을 명시적으로 특정 객체로 지정한 경우 지정된 객체가 리턴된다.

그러나 만약 객체가 아닌 value값을 리턴 하려한다면 무시하고 this로 바인딩된 객체가 리턴됩니다.


다음번 포스팅은 함수와 this에 대한 내용입니다.



인사이드 자바스크립트를 공부하며 정리하는 포스팅입니다.



배열(Array)

배열은 자바스크립트 객체의 특별한 형태다. C의 배열과 유사하지만 크기를 지정하지 않아도 되며, 어떠한 타입의 데이터를 저장하더라도 상관없다.




1. 배열 생성

배열의 생성은 두 가지 방법으로 할 수 있습니다.

일반적으로 배열 리터럴 방식으로 생성합니다.


1) 배열 리터럴

[] 대괄호 문법을 이용한 리터럴 방식으로 배열 생성


var str = 'test';
var colorArr = ['orange', 'yellow', blue];

2) 생성자 함수를 이용한 생성

호출할 때 인자가 1개이고, 숫자인 경우: 호출된 인자를 length로 갖는 빈 배열을 생성한다.

그 외의 경우: 호출된 인자를 요소로 갖는 배열 생성.

var colorArr = new Array(3);
colorArr.push('orange');
colorArr.push('yellow');
colorArr.push('blue');
//var colorArr = new Array('orange', 'yellow', 'blue');


2. length 프로퍼티

배열의 원소 개수를 나타낸다. 그러나 배열에 존재하는 실제 원소 개수와 일치하는 것은 아니다. 

배열 내에 가장 큰 인덱스에 1을 더한 값이다.


배열의 가장 큰 인덱스값이 변하면, length 값 또한 자동으로 그에 맞춰 변경된다.

var arr = []; arr[0] = 1; arr[100] = 2; console.log(arr.length); // 101


배열의 length 프로퍼티는 명시적으로 값을 지정할 수도 있습니다.

만약 현재 배열의 length보다 큰 값을 지정한다면 현재 length Index 이후 값들은 undefined로 채워지게 됩니다.


length는 배열 표준 메서드에 영향을 미친다.


배열 표준 메서드인 push 메서드는 length값을 기준으로 수행됩니다. 

만약 length가 5라면 length는 마지막 Index + 1이므로 push 메서드의 수행결과로 새롭게 추가될 원소의 자리는 배열[length]가 되게됩니다.

length 프로퍼티는 이렇게 배열 표준 메서드에 영향을 미칠 수 있는 프로퍼티이므로 중요합니다.


이렇게 중요한 length 프로퍼티가 객체에 프로퍼티로 존재하면 어떻게될까요?

자바스크립트에서는 이렇게 length 프로퍼티를 가진 객체를 유사 배열 객체라고 부릅니다. 


* 배열에 동적으로 프로퍼티가 추가되어도 length는 증가하지 않는다. 배열의 length 프로퍼티는 오직 배열 원소의 가장 큰 인덱스가 변했을 경우만 변경된다.




3. 배열과 객체

자바스크립트에서는 배열 역시 객체입니다. 


객체와 배열의 typeof 값은 둘 다 Object로 동일합니다. 


그치만 분명히 배열과 객체는 다릅니다.

- length 프로퍼티의 존재 여부

- 배열 표준 메서드 존재 여부


이러한 차이점이 생기는 이유는 

객체의 경우 프로토타입으로 Object.prototype 객체를 갖고,

배열의 경우 프로토타입으로 Array.prototype 객체를 갖게되기 때문입니다.


length, 배열 표준 메서드는 Array.prototype 객체가 가지고 있는 프로퍼티입니다.


3. 배열의 프로퍼티 열거


배열을 객체와 같이 for in 문으로 프로퍼티를 열거하면 의도치 않은 동작이 수행될 수 있습니다.


var arr = ['a', 'b' , 'c']; arr['name'] = 'song'; for(var prop in arr) { console.log(prop, arr[prop]); } //0 a //1 b //2 c //name song //....여러가지 배열 프로퍼티 for(var i = 0; i< arr.length; i++){ console.log(i, arr[i]) } //0 "a" //1 "b" //2 "c"

위와 같이 for in문을 사용하면 배열의 원소 뿐아니라 여러가지 프로퍼티들도 같이 출력되게 됩니다. 의도치 않은 동작이 수행될 수 있겠죠?

때문에 배열의 원소를 탐색할 때에는 for문을 사용하는 것이 좋습니다.


4. 배열의 요소 삭제


1) delete

delete 연산자는 length 값은 그대로 유지한채 삭제합니다. 즉, 배열의 원소값을 undefined로 바꿔줍니다.


2) splice

만약 배열의 요소를 완전히 삭제하고 싶다면 splice()메서드를 이용해서 배열의 요소를 삭제할 수 있습니다.


splice(start, deleteCount, item...)

- start: 배열에서 시작 인덱스

- deleteCount: 삭제할 요소의 수

- item: 삭제할 위치에 추가할 요소