Inside Javascript 정리 - 2 (함수, 체이닝)

함수를 리턴하는 함수

: 함수도 일급객체라고 했자나. 그래서 일반 값처럼 함수 자체를 리턴할 수도 있어.

여기에서 오는 특징이 함수를 호출함과 동시에 다른 함수로 바꾸거나, 자기 자신을 재정의 하는 함수를 구현할수도 있게되는거야


arguments 객체

: 자바스크립트에는 arguments라는 객체가 있어. 자바스크립트 함수에는 인자의 갯수를 함수의 정의에서 정해준 것 보다 더 많이 주던 더 적게 주던 에러가 발생하지 않아!

그게 바로 arguments 객체 덕분이야. arguments 객체는 함수를 호출할 때 넘긴 인자들이 배열 형태로 저장된 객체를 의미해. 특이한 것은 정확히는 유사 배열 객체 라는 점이지!

그렇기에 배열 메서드를 이용할 수 없음을 주의해야해. 이 arguments 객체를 이용하면 매개변수가 정확하게 정해지지 않은 함수를 구현하거나, 전달된 인자의 개수에 따라 서로 다른 처리를 해줘야 하는 함수를 개발하는 데 유용하게 사용할 수 있어.

1
2
3
4
5
6
7
8
9
function sum(){
var result = 0;

for(var i=0 ; i<arguments.length ; i++){
result += arguments[i];
}

return result;
}

이런식으로 말야!!


호출패턴과 this 바인딩

: 함수를 호출할 때 기존 매개변수로 전달되는 인자값에 더해서 앞에서 설명한 arguments 객체와 this 인자가 함수내부로 암묵적으로 전달되어져!

이 this 인자를 이해하는 것이 중요하데. 얘는 함수가 호출되는 방식(호출패턴)에 따라 this가 다른 객체를 참조하기 때문이야.

  • 객체의 메서드를 호출할 때 this 바인딩 : 객체의 프로퍼티가 함수인 경우 이를 메소드라고 한다 했자나. 이 메소드 내부 코드에서 사용된 this는 해당 메소드를 호출한 객체로 바인딩 되!
  • 함수를 호출할 때 this 바인딩 : 자바스크립트에서는 함수를 호출하면, 해당 함수 내부 코드에서 사용된 this는 전역 객체에 바인딩 되! 브라우저에서 자바스크립트를 이용할 경우 window가 전역객체가 되겠지. 여기서 알고 넘어가야 할 것은 내부함수를 호출하였을 때야. 특정 객체의 메소드 내부에 내부함수가 정의되어있다 생각해보자. 그 내부 함수에서 this를 사용할 경우 부모함수를 호출한 객체를 가르키지 않을까 생각할 수 있지만 그렇지 않아! 내부함수도 함수로 처리되기에 this로 접근할 경우 전역객체에 접근하게 되는거야. 이를 방지하고 싶다면 다른 참조변수를 이용하면 되는데 보통은 that 으로 이름을 많이 짓는데!! 그래서 부모 함수에서 that = this; 를 통해 that이 this를 참조하게 한후 그 내부 함수에서는 that을 통해 접근한다면 전역객체가 아닌 부모 함수를 호출한 객체에 접근할 수 있게 되는거지!!
  • 생성자 함수를 호출할 때 this 바인딩 : 기존 함수에 new 연산자를 붙여서 호출하면 해당 함수는 생성자 함수로 동작해. 이는 어떻게보면 일반 함수에 new를 붙여 호출하면 원치 않는 생성자 함수처럼 동작할 수 있다는거지. 그래서 자바스크립트 스타일 가이드에서는 생성자 함수로 특정함수가 정의되어 있음을 알리기 위해 함수 이름의 첫 문자를 대문자로 쓰기를 권해. 생성되는 과정은 다음과 같아. new 연산자로 함수를 생성자로 호출 -> 빈 객체가 생성되고 이 객체는 this로 바인딩 -> 함수 코드 내부에서 this 를 이용해 동적으로 프로퍼티나 메서드 생성 -> 생성된 객체 리턴

객체 리터럴 방식과 생성자 함수를 통한 객체 생성 방식의 차이

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//객체 리터럴 방식으로 foo 객체 생성
var foo = {
name : 'foo',
age : 35,
gender : 'man'
};

//생성자 함수
function Person(name,age,gender,position){
this.name=name;
this.age=age;
this.gender=gender;
}

var bar = new Person('bar',33,'woman');
var baz = new Person('baz',32,'man');

위의 차이에서 보이듯이 객체 리터럴 방식은 같은 형태의 객체를 재생성 할 수 없으나 생성자 함수를 이용한 경우에는 가능하다.

그리고 가장 큰 차이점은 프로토타입 객체에 존재해! 객체 리터럴 방식의 경우 자신의 프로토타입 객체는 Object이나 생성자 함수 방식의 경우는 Person이야!! 그 차이는 자바스크립트 객체 생성 규칙에서 와. 자바스크립트 객체는 자신을 생성한 생성자 함수의 prototype 프로퍼티가 가리키는 객체를 자신의 프로토타입 객체로 설정해. 그렇기에 객체 리터럴 방식에서는 객체 생성자 함수가 Object(), 생성자 함수 방식의 경우에는 생성자 함수 자체이기에 위와 같은 차이를 보이게 되는거지!


강제로 인스턴스 생성하기

: 위와 같은 문제가 발생하기에 널리 사용되는 한가지 패턴이 있어.

1
2
3
4
5
function A(arg){
if(!(this instanceof A))
return new A(arg);
this.value = arg ? arg : 0;
}

이런식으로 정의하게 될 경우 new로 호출된 것이 아니더라도 new로 A를 호출하여 반환하게 되어있어서 문제가 발생히자 않게 되는거야.

if (!(this instanceof arguments.calle)) 이렇게 많이 쓰이기도 해. arguents.callee가 곧 호출된 함수를 가르키거든!


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

: call 과 apply는 Function.prototype 객체의 메서드로 모든 함수는 function.apply(thisArg, argArray) 의 방식으로 메서드를 호출하는 것이 가능해! (call도 마찬가지!!)

두 함수 모두 기능은 같으나 매개변수가 차이가 있는거야.

apply 함수에 대해 먼저 보자면 thisArg는 apply() 메서드를 호출한 함수 내부에서 사용한 this에 바인딩 할 객체를 가리켜!! 즉 한마디로 첫번째 인자로 넘긴 객체가 this로 명시적으로 바인딩 되는거지. argArray 인자는 함수를 호출할 때 넘길 인자들의 배열을 가리켜. 즉 정리하자면! apply() 메서드는 두번째 인자인 argArray 배열을 자신을 호출한 함수의 인자로 사용하되, 임 하수 내부에서 사용된 this 는 첫 번째 인자인 thisArg 객체로 바인딩해서 함수를 호출하는 기능을 하는 것이야.

call 메서드가 다른 점이 있다면 apply 함수에서는 argArray 즉 두번째 인자를 배열로 넘겨주었다면 call 함수에서는 배열이 아닌 각각의 값을 넘겨주는게 차이야!

그럼 어디에 쓰냐?? 대표적으로 사용되는 곳이 arguments 객체와 같은 유사 배열 객체에서 배열 메서드를 사용하는 경우야!!

1
2
3
4
function myFunction(){
var args = Array.prototype.slice.apply(arguments);
console.log(args);
}

위와 같은 function을 보자. arguments는 앞서 말했듯이 유사배열객체야. 하지만 Array.prototype.slice.apply 메서드를 통해 arguments를 slice할 수 있어. 한마디로 Array.prototype.slice() 메서드를 호출하라. 이때 this는 arguments 객체로 바인딩해라! 라고 생각할 수 있어. 그렇기에 결과는 배열로 출력이 되게되는거지. slice(start,end) 메서드는 참고로 start부터 end-1 지점까지 자르는 메서드이며 아무 인자도 넣지 않을경우 그대로 복사한 배열을 반환해.


함수리턴

: 자바스크립트 함수는 항상 리턴값을 반환해!! return 문을 사용하지 않더라도 아래와 같은 규칙으로 전달하게 되어있어!!

  1. 일반 함수나 메서드는 리턴 값을 지정하지 않을 경우, undefined 값이 리턴되는거야
  2. 생성자 함수에서 리턴값을 지정하지 않을 경우 생성된 객체가 리턴되는거야 ( 생성자 함수의 리턴값이 객체가 아닌 불린,숫자,문자열일 경우 이를 무시하고 this로 바인딩된 객체 반환해)

프로토타입 체이닝

프로토타입의 두가지 의미

: 자바스크립트의 모든 객체는 자신의 부모인 프로토타입 객체를 가리키는 참조 링크 형태의 숨겨진 프로퍼티가 있고 이를 암묵적 프로토타입 링크라고 부르며 이러한 링크는 모든 객체의 Prototype 프로퍼티에 저장된다고 했어. 여기서 주의해야 할 것은! 바로 함수 객체의 prototype 프로퍼티와 객체의 숨은 Prototype 링크를 구분할 줄 알아야해.

자바스크립트에서 모든 객체는 자신을 생성한 생성자 함수의 prototype 프로퍼티가 가리키는 프로토타입 객체를 자신의 부모 객체로 설정하는 Prototype 링크로 연결해

prototype 프로퍼티는 함수의 입장에서 자신과 링크된 프로토타입 객체를 가리키고 있으며, 이에 반해 Prototype 링크는 객체의 입장에서 자신의 부모 객체인 프로토타입 객체를 내부의 숨겨진 링크로 가리키고 있는거야. 결국은 자바스크립트에서 객체를 생성하는 건 생성자 함수의 역할이나, 생성된 객체의 실제 부모 역할을 하는 건 생성자 자신이 아닌 생성자의 prototype 프로퍼티가 가리키는 프로토타입 객체인거지!!


객체 리터럴 방식으로 생성된 객체의 프로토타입 체이닝

: 자바스크립트에서 객체는 자기 자신의 프로퍼티뿐만 아니라, 자신의 부모 역할을 하는 프로토타입 객체의 프로퍼티 또한 마치 자신의 것처럼 접근하는 것이 가능해! 그리고 이를 가능케 하는 것이 바로 프로토타입 체이닝인거쥐!!

그럼 프로토타입 체이닝이 뭐냐?? -> 자바스크립트에서 특정 개체의 프로퍼티나 메서드에 접근하려고 할 때, 해당 객체에 접근하려는 프로퍼티 또는 메서드가 없다면 Prototype 링크를 따라 자신의 부모 역할을 하는 프로토타입 객체의 프로퍼티를 차례대로 검색하는 것을 말해.


생성자 함수로 생성된 객체의 프로토타입 체이닝

: 그 문장 기억하쥐? “자바스크립트에서 모든 객체는 자신을 생성한 생성자 함수의 prototype 프로퍼티가 가리키는 객체를 자신의 프로토타입 객체(부모객체)로 취급한다”

그럼 다 한거야!! 물론 함수 리터럴 방식으로 만들경우 Prototype링크로 연결되어 있는것은 Object.prototype이고 생성자 함수를 이용해서 만들 경우 function.prototype을 가리키고 있겠지. 하지만 이 function.prototype은 프로퍼티로 constructor 프로퍼티만 있어. 하지만 이 녀석도 객체이기에 Prototype링크로 Object.prototype이 있고 그렇기에 한번 더 타고 가서 Object 객체에 존재하는 메소드를 사용할 수 있게 되는거야!! ( Object.prototype이 프로토타입 체이닝의 종점이야 )


기본 데이터 타입 확장

: 자바스크립트의 숫자,, 문자열, 배열 등에서 사용되는 표준 메서드들의 경우, 이들의 프로토타입인 Number.prototype, String.prototype, Array.prototype 등에 정의되어 있어. 물론 이러한 기본 내장 프로토타입 객체 또한 Object.prototype을 자신의 프로토타입으로 가지고 있어서 프로토타입 체이닝으로 연결되지! 만약 String.prototype 객체에 사용자가 정의한 메서드를 추가하면 이 메서드는 일반 문자열 표준 메서드처럼 모든 문자열에서 접근 가능해지는거야. 마치 문자열 API처럼 사용할 수 있게 되는거지!


프로토타입 메서드와 this바인딩

: 프로토타입 객체는 메서드를 가질 수 있어. 근데 이때 그럼 메서드 내에 this가 있을 경우 어디에 바인딩 될까?? 그건 앞서 공부했던 규칙과 똑같이 객체에서 매서드를 호출하면 메서드 내의 this는 그 메서드를 호출한 객체에 바인딩 되는거야!!


디폴트 프로토타입은 다른 객체로 변경이 가능해

: 디폴트 프로토타입 객체는 함수가 생성될 때 같이 생성되며, 함수의 prototype 프로퍼티에 연결되!! 자바스크립트에서는 이렇게 함수를 생성할 때 해당 함수와 연결되는 디폴트 프로토타입 객체를 다른 일반 객체로 변경하는 것이 가능해!! -> 이러한 특징을 이용해서 객체지향의 상속을 구현하는거쥐!!

주의할 것은 생성자 함수의 프로토타입 객체가 변경되면, 변경된 시점 이후에 생성된 객체들은 변경된 프로토타입 객체로 Prototype 링크를 연결한다는 걸 명심해야해!! 물론 변경 전에 생성된 객체들은 기존 프로토타입 객체로의 Prototype 링크를 유지하겠지


객체의 프로퍼티 읽기나 메서드를 실행할 때만 프로토타입 체이닝 동작

: 이는 너무 당연한 설명이나 그래!! 왜냐?? 객체에 없는 프로퍼티에 쓰기를 진행할 경우 해당 프로퍼티를 추가하고 쓰기 작업을 하기 때문이지.


Chapter05 실행 컨텍스트와 클로저


실행 컨텍스트 개념

: call stack 알자나. 함수 호출할 때 해당 함수의 호출정보가 쌓여있는 스택! 실행 컨텍스트는 이 콜 스택에 들어가는 실행 정보 하나와 비슷하다고 생각하면 되. 실행 가능한 자바스크립트 코드 블록이 실행되는 환경이라고 생각할 수 있고 이 컨텍스트 안에 실행에 필요한 여러가지 정보가 담겨있는거지.


실행 컨텍스트 생성과정

  1. 활성 객체 생성 : 실행 컨텍스트가 생성되면 자바스크립트 엔진은 해당 컨텍스트에서 실행에 필요한 여러가지 정보를 담을 객체를 생성하는데 이를 활성 객체 라고해. 이 객체에 매개변수, 사용자 정의 변수, 객체 등을 저장하고 새로 만들어진 컨텍스트로 접근 가능하게 되어있어.
  2. arguments 객체 생성 : arguments 객체를 생성해. 앞서 만들어진 활성 객체는 arguments 프로퍼티로 이 arguments 객체를 참조하는거야.
  3. 스코프 정보 생성 : 현재 컨텍스트의 유효 범위를 나타내는 스코프 정보를 생성해. 이 스코프 정보는 현재 실행 중인 실행 컨텍스트 안에서 연결 리스트와 유사한 형식으로 만들어지지. 이 연결리스트를 활용해서 현재 컨텍스트에서 특정 변수에 접근하는 것이 가능하고 만약 찾지 못하면 에러 검출하고 하는거지. 이 리스트를 바로 스코프 체인이라고 해.
  4. 변수 생성 : 현재 실행 컨텍스트 내부에서 사용되는 지역 변수의 생성이 이루어져. (참고로 변수객체랑 활성객체랑 같은거야)
  5. this 바인딩 : 앞서 공부했자나. 만약 참조하는 객체 없으면 전역 객체 참조해.
  6. 코드 실행 : 이렇게 하나의 실행 컨텍스트가 생성되고, 변수 객체가 만들어진 후에 코드에 있는 여러가지 표현식의 실행이 이루어져. 아까 4에서 진행했던 변수들에 있어 초기에는 undefined로 되어 있으나 코드 실행 단계에서 이 undefined 대신에 값이 할당되어져. (참고로 전역 실행 컨텍스트는 arguments 객체가 없고 전역 객체 하나만을 포함하는 스코프체인이 있어. 그리고 여기서 변수 객체가 곧 전역 객체이고 전역으로 선언된 함수와 변수가 전역 객체의 프로퍼티가 되는거지)

(이 부분은 후에 책을 한번 더 보거나 추가적인 자료 찾아봐!)


스코프 체인

: 자바스크립트도 다른 언어처럼 유효범위가 있어. C의 경우 { }가 유효범위의 기준이지. 그러나 자바스크립트에서는 { }와 같은 구문은 유효범위가 없어. 오직 함수만이 유효범위의 한 단위가 되는거야! 그리고 이 유효범위를 나타내는 스코프가 scope 프로퍼티로 각 함수 객체 내에서 연결리스트 형식으로 관리되는데 이를 스코프체인이라고 하는거지!

그래서 각각의 함수는 scope 프로퍼티로 자신이 생성된 실행 컨텍스트의 스코프 체인을 참조해. 그리고 함수가 실행되는 순간 실행 컨텍스트가 만들어지고, 이 실행 컨텍스트는 실행된 함수의 scope프로퍼티를 기반으로 새로운 스코프체인을 만드는거야.


전역 실행 컨텍스트의 스코프 체인

: 전역 실행 컨텍스트 단 하나만 실행되고 있어 참조할 상위 컨텍스트가 없자나?? 그래서 결국은 자신이 최상위에 위치하는 변수 객체니까 스코프체인은 자기 자신만을 가리키게 되는거지.


함수를 호출한 경우 생성되는 실행 컨텍스트의 스코프체인

: 함수 객체가 생성될 때, 그 함수 객체의 scope 프로퍼티는 현재 실행되는 컨텍스트의 변수 객체에 있는 scope를 그대로 가져.

1
2
3
4
5
6
function func(){
var var1 = 1;
console.log(var1);
}
func();
...

위 예제를 실행하면 우선 전역 실행 컨텍스트가 생성되겠지. 그리고 func() 함수 객체가 만들어지지. 이 func 함수객체의 scope가 결국 전역 변수 객체가 되는거라 이말이지.

그런다음 밑에 계속 실행하면 func() 함수를 실행하네? 그럼 새로운 컨텍스트가 만들어지겠지. 이를 func 컨텍스트라고 해보자. 그럼 func 컨텍스트의 스코프 체인은 어떻게 될까?

실행된 함수의 scope 프로퍼티를 우선 그대로 복사해. 그리고 현재 생성된 변수 객체를 복사한 스코프 체인의 맨 앞에 추가하는거야!! 그래서 결국은 이렇게 되는거지

-> 스코프 체인 = 현재 실행 컨텍스트의 변수 객체 + 상위 컨텍스트의 스코프 체인

그래서 이렇게 스코프 체인이 만들어지면 이를 바탕으로 식별자 인식을 하는거야. 스코프 체인의 첫번째 변수 객체부터 찾아서 없으면 다음 객체로 이동하면서 찾는거지!!
(참고로 this는 식별자가 아닌 키워드로 분류되기에 스코프체인의 참조 없이 접근할 수 있어!)


클로저

클로저의 개념

: 간단하게 얘기하자면 이미 생명 주기가 끝난 외부 함수의 변수를 참조하는 함수라고 생각하면 돼. 그리고 클로저로 참조되는 외부 변수를 자유 변수(free variable)이라고 해.

1
2
3
4
5
6
7
8
function outerFunc(arg1, arg2){
var local = 8;
function innerFunc(innerArg){
console.log((arg1+arg2)/(innerArg+local));
}
}
var exam1 = outerFunc(2,4);
exam1(2);

위 코드를 보자. 여기서 outerFunc()가 실행되면서 생성되는 변수 객체가 스코프 체인에 들어가게 되고, 이 스코프 체인은 innerFunc의 스코프 체인으로 참조되겠지. 그렇기에 outerFunc()함수가 종료되었찌만, 여전히 내부함수 innerFunc의 scope로 참조되므로 garbage collection의 대상이 되지 않고 접근가능하게 살아있을 수 있는거야. 그렇기에 exam1(n)을 호출하여 innerFunc()에서 변수 local에 참조가 가능한거지. 클로저는 이렇게