일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 싸피 합격
- 세마포어란?
- 싸피
- 프록시
- SSAFY
- 세마포어와 뮤텍스의 차이
- 다익스트라 알고리즘
- 웹 호스팅
- 최단 경로
- 세마포어
- 호스팅
- 프록시서버
- floyd-warshall
- 서버 호스팅
- 싸피 면접 후기
- 뮤텍스란?
- Proxy
- 뮤텍스
- 클라우드 서버
- 플로이드 와샬
- 삼성 청년 SW 아카데미
- Proxy Server
- 플로이드 워셜
- Dijkstra Algorithm
- Synchronization
- 호스팅이란?
- 세마포어와 뮤텍스
- 다익스트라
- 동기화
- Today
- Total
어제의 나보다 성장한 오늘의 나
[자바스크립트] 호출패턴과 this 바인딩 본문
1. 객체의 메서드 호출할 때 this 바인딩
- 객체의 프로퍼티가 함수일 경우, 이 함수를 메서드라고 부른다.
- 이러한 메서드를 호출할 때 메서드 내부 코드에서 사용된 this는 해당 메서드를 호출한 객체로 바인딩된다.
// 객체의 메서드에서의 this는 자신을 호출한 객체에 바인딩된다
var myObject = {
name:'foo',
sayName:function(){
console.log(this.name);
},
};
var otherObject = {
name:'bar'
};
otherObject.sayName = myObject.sayName;
myObject.sayName(); // foo
otherObject.sayName(); // bar
- myObject 객체와 ohterObject 객체는 name 프로퍼티와 sayName() 메서드가 있다.
- sayName() 메서드는 this.name 값을 출력하는 간단한 함수로서, myObject와 otherObject객체로부터 각각 호출된다.
- 이때 앞서 설명했던 대로 sayName() 메서드에서 사용된 this는 자신을 호출한 객체에 바인딩된다.
2. 함수를 호출할 때 this 바인딩
자바스크립트에서는 함수를 호출하면, 해당 함수 내부 코드에서 사용된 this는 전역 객체에 바인딩된다.
브라우저에서 자바스크립트를 실행하는 경우 전역 객체는 window 객체가 된다.
var foo = "오늘보다 성장한 오늘의 나"; // 전역변수 선언
console.log(foo); // 출력 : 오늘보다 성장한 오늘의 나
console.log(window.foo); // 출력 : 오늘보다 성장한 오늘의 나
이제 함수를 호출할 때 this 바인딩이 어떻게 되는지 살펴보자.
var test = 'This is test';
console.log(window.test); // This is test
// sayFoo() 함수 호출시 this는 전역 객체에 바인딩된다.
var sayFoo = function(){
console.log(this.test); // This is test
};
sayFoo();
- test라는 전역 변수는 전역 객체 window의 프로퍼티로 접근 가능하다.
- sayFoo() 함수를 보면 단순히 this.test를 출력하는 함수이다. 함수를 호출할 때 this는 전역 객체에 바인딩된다고 했는데, sayFoo() 함수가 호출된 시점에서 this는 전역 객체인 window에 바인딩된다.
- 때문에 this.test는 window.test를 의미한다. 결국 "this is test" 출력
하지만!!! 이러한 함수 호출에서의 this 바인딩 특성은 내부 함수를 호출했을 경우에도 그대로 적용된다.
- 내부 함수에서 this를 이용할 때는 주의해야 한다.
// 전역 변수 value 정의
var value = 100;
// myObject 객체 생성
var myObject = {
value : 1,
func1 : function () {
this.value +=1 ;
console.log('func1() called, this.value : '+this.value);
// 내부 함수의this는 전역 객체 (window)에 바인딩 된다.
// func2() 내부함수
func2 = function () {
this.value +=1;
console.log('func2() called. this.value : ' + this.value);
// func3 내부 함수
func3 = function() {
this.value += 1;
console.log('func3() called. this.value : '+this.value);
}
func3(); // func3 내부 함수 호출
}
func2(); // func2 내부 함수 호출
}
};
myObject.func1(); // func1 내부 함수 호출
- 이렇게 실행결과가 출력된 이유는 자바스크립트에서는 내부 함수 호출 패턴을 정의해 놓지 않기 때문이다.
- 내부 함수도 결국 함수 이므로 이를 호출할 때는 함수 호출로 취급된다.
- 따라서 함수 호출 패턴 규칙에 따라 내부 함수의 this는 전역 객체에 바인딩된다.
그렇다면? 내부 함수가 this를 참조하는 자바스크립트의 한계를 극복하려면?
- 부모 함수의 this를 내부 함수가 접근 가능한 다른 변수에 저장하는 방법이 사용된다.
- 보통 관례상 this 값을 저장하는 변수의 이름을 that이라고 짓는다.
- 이렇게 되면 내부 함수에서는 that 변수로 부모 함수의 this가 가리키는 객체에 접근할 수 있다.
// func2()와 func3()내부 함수는 자신을 둘러싼 부모 함수인 func1()의 변수에 접근 가능하므로,
// func2()와 func3()도 that변수로 func1()의 this바인딩 된 객체인 myObject에 접근 가능하게 된다.
var value = 100;
var myObject = {
value:1,
func1:function(){
var that = this;
this.value += 1;
console.log('func1() called. this.value: '+this.value);
func2 = function(){
that.value += 1;
console.log('func2() called this.value: '+this.value);
func3 = function(){
that.value += 1;
console.log('func3() called this.value: '+that.value);
}
func3();
}
func2();
}
}
myObject.func1();
- 부모 함수인 func1()의 this 값을 that 변수에 저장했다.
- 앞서 내부 함수의 특징에서 설명했듯이 func2()와 func3() 내부 함수는 자신을 둘러싼 부모 함수인 func1()의 변수에 접근 가능하다
- func2()와 func3()도 that 변수로 func1()의 this가 바인딩된 객체인 myObject에 접근 가능하게 된다
3. 생성자 함수를 호출할 때 this 바인딩
객체를 생성하는 방법은 크게 객체 리터럴 방식이나 생성자 함수를 이용하는 두 가지 방법이다.
이번에는 생성자 함수를 이용한 객체 생성 방법을 알아보자!
- 자바스크립트의 생성자 함수는 말 그대로 자바스크립트의 객체를 생성하는 역할을 한다.
- 자바와 같은 객체지향 언어에서의 생성자 함수의 형식과는 다르게 그 형식이 정해져 잇는 것이 아니라, 기존 함수에 new 연산자를 붙여서 호출하면 해당 함수는 생성자 함수로 동작한다.
- 이는 반대로 생각하면 일반 함수에 new를 붙여 호출하면 원치 않는 생성자 함수처럼 동작할 수 있다.
- 따라서 대부분의 자바스크립트 스타일 가이드에서는 특정 함수가 생성자 함수로 정의되어 있음을 알리려고 함수 이름의 첫 문자를 대문자로 쓰기를 권하고 있다.
이러한 생성자 함수를 호출할 때, 생성자 함수 코드 내부에서 this는 메서드와 함수 호출 방식에서의 this 바인딩과 다르게 동작한다. 이를 정확히 이해하려면 생성자 함수가 호출됐을 때 동작하는 방식을 살펴보자!
생성자 함수가 동작하는 방식
- 빈 객체 생성 및 this 바인딩
- 생성자 함수 코드가 실행되기 전빈 객체가 생성된다. 바로 이 객체가 생성자 함수가 새로 생성하는 객체이며, 이 객체는 this로 바인딩된다.
- 따라서 이후 생성자 함수의 코드 내부에서 사용된 this는 이 빈 객체를 가리킨다.
- 하지만 여기서 생성된 객체는 엄밀히 말하면 빈 객체가 아니다. 자바스크립트의 모든 객체는 자신의 부모인 프로토타입 객체와 연결되어 있으며, 이를 통해 부모 객체의 프로퍼티나 메서드를 마치 자신의 것처럼 사용할 수 있기 때문이다.
- 중요! 이렇게 생성자 함수가 생성한 객체는 자신을 생성한 생성자 함수의 prototype 프로퍼티가 가리키는 객체를 자신의 프로토타입 객체로 설정한다. (이는 자바스크립트의 규칙이니 잘 기억하자!)
- this를 통한 프로퍼티 생성
- 함수 코드 내부에서 this를 사용해서, 앞에서 생성된 빈 객체에 동적으로 프로퍼티나 메서드를 생성할 수 있다.
- 생성된 객체 리턴
- 리턴 문이 동작하는 방식은 경우에 따라 다르므로 주의해야 한다.
- 우선 가장 일반적인 경우로 특별하게 리턴 문이 없을 경우! this로 바인딩된 새로 생성한 객체가 리턴된다.
- 이것은 명시적으로 this를 리턴해도 결과는 같다. (주의! - 생성자 함수가 아닌 일반 함수를 호출할 때 리턴 값이 명시되어 있지 않으면 undefined가 리턴된다.)
위의 내용을 코드를 통해 알아보자!
// Person() 생성자 함수
var Person = function(name){
// 함수 코드 실행 전
this.name=name;
// 함수 리턴
};
// foo 객체 생성
var foo = new Person('foo');
console.log(foo.name); // 출력값 : foo
위에 처럼 new로 호출하면, Person()은 생성자 함수로 동작한다.
- Person() 함수가 생성자로 호출되면, 함수 코드가 실행되기 전에 빈 객체가 생성된다.
- 여기서 생성된 빈 객체는 Person() 생성자 함수의 prototype 프로퍼티가 가리키는 객체(Person.prototype 객체)를 __proto__ 링크로 연결해서 자신의 프로토타입으로 설정한다.
- 그리고 이렇게 생성된 객체는 생성자 함수 코드에서 사용되는 this로 바인딩된다.
- this가 가리키는 빈 객체에 name이라는 동적 프로퍼티를 생성했다.
- 리턴 값이 특별히 없으므로 this로 바인딩한 객체가 생성자 함수의 리턴 값으로 반환돼서, foo 변수에 저장된다.
객체 리터럴 방식과 생성자 함수를 이용해서 객체를 생성하는 방식을 모두 알아봤다!
그럼 두 가지 방법의 차이점은 무엇일까?
var foo = {
name:'foo',
age:35,
gender:'man'
};
console.dir(foo);
// 생성자 함수
function Person(name,age,gender){
this.name=name;
this.age=age;
this.gender=gender;
};
// Person 생성자 함수를 이용하여 객체 생성
var bar = new Person('bar',33,'woman');
console.dir(bar);
// Person 생성자 함수를 이용하여 객체 생성
var baz = new Person('baz',25,'woman');
console.log(baz);
위는 두 가지 객체 생성 방식으로 객체를 만들고 출력하는 예제이다.
- foo 객체와 같이 객체 리터럴 방식으로 생성된 객체는 같은 형태의 객체를 재생성할 수 없다.
- Person() 생성자 함수를 사용해서 객체를 생성한다면, 생성자 함수를 호출할 때 다른 인자를 넘김으로써 같은 형태의 서로 다른 객체 bar와 baz를 생성할 수 있다.
그렇다면! console.dir()를 이용한 객체의 출력 결과는 어떨까?
- 객체 리터럴과 생성자 함수 방식의 차이는 프로토타입 객체(__proto__프로퍼티)에 있다.
- 객체 리터럴 방식은 Object, 생성자 함수 방식은 Person(실제로는 person.prototype)으로 서로 다르다
이렇게 차이가 발생하는 이유는 자바 스크립트 객체 생성 규칙 때문이다!
- 객체는 자신을 생성한 생성자 함수의 prototype 프로퍼티가 가리키는 객체를 자신의 프로토타입 객체로 설정한다.
이제 생성자 함수를 new를 붙이지 않고 호출할 경우!
- 일반 함수와 생성자 함수가 별도의 차이가 없다.
- new를 붙여서 함수를 호출하면 생성자 함수로 동작하는 것이다.
- 객체 생성을 목적으로 작성한 생성자 함수에 new 없이 호출하거나, 일반 함수를 new를 붙여서 호출할 경우 코드에서 오류가 발생할 수 있다.
- 그 이유는 일반 함수 호출과 생성자 함수를 호출할 때 this바인딩 방식이 다르기 때문이다.
- 일반 함수 호출 : this가 window 전역 객체 바인딩
- 생성자 함수 호출 : this는 새로 생성되는 빈 객체에 바인딩
그렇다면 Person() 생성자 함수를 new 없이 호출을 한다면?
// new를 붙히지않고 생성자 함수 호출시의 에러
var qux = Person('qux',20,'man');
console.log(qux); // undefined
// 생성자 함수
function Person(name,age,gender){
this.name=name;
this.age=age;
this.gender=gender;
};
console.log(window.name); // qux
console.log(window.age); // 20
console.log(window.gender); // man
- new 없이 일반 함수 형태로 호출할 경우, this는 함수 호출이므로 전역 객체인 window 객체에 바인딩된다.
- window 객체에 동적으로 name, age, gender 프로퍼티가 생성된다.
- 생성자 함수는 별도의 리턴 값이 정해져 있지 않은 경우에 새로 생성된 객체가 리턴된다.
- 일반 함수를 호출할 때는 undefined가 리턴된다.
일반과 생성자 함수를 구분하기 위해 첫 글자를 대문자로 표기를 해도 사람은 실수를 한다!
그래서 다음과 같은 별도의 코드 패턴을 사용하기도 한다!
function A(arg){
if(!(this instanceof A)) return new A(arg);
this.value = arg ? arg : 0;
}
var a = new A(100);
var v = A(10);
console.log(a.value) // 출력값 100
console.log(b.value) // 출력값 100
console.log(global.value) // 출력값 undefined
- this가 A의 인스턴스인지 확인하는 분기문 추가
- this가 A의 인스턴스가 아니라면, new로 호출된 것이 아님을 의미
- 어떤 코드에서는 앞과 같이 함수의 이름을 그대로 쓰지 않고 다음과 같이 표현식을 쓰곤 한다.
if(!(this instanceof arguments.callee))
- arguments.callee가 곧 호출될 함수를 가리킨다.
- 이와 같이 하면, 특정 함수 이름과 상관없이 이 패턴을 공통으로 사용하는 모듈을 작성할 수 있는 장점이 있다.
- 대부분의 자바스크립트 라이브러리에 이 패턴이 들어가 있다.
출처
'공부 > JavaScript && jquery' 카테고리의 다른 글
[자바스크립트] 함수 리턴 (1) | 2021.04.16 |
---|---|
[자바스크립트] call과 apply (0) | 2021.04.16 |
[자바스크립트] arguments객체 (0) | 2021.04.14 |
[자바스크립트] 콜백함수, 즉시 실행 함수, 내부함수 (0) | 2021.04.13 |
함수 객체의 기본 프로퍼티 (0) | 2021.04.10 |