스코프 체인이 가능토록 하는 것 (외부 환경의 참조정보)라고 할 수 있다.
outer는 현재 호출된 함수가 선언될 당시의 LexicalEnvironment를 참조한다.
A함수 내부에 B함수 선언 -> B함수 내부에 C함수 선언(Linked List)
한 경우 어떻게 될까?
결국 타고, 타고 올라가다보면 전역 컨텍스트의 LexicalEnvironment
를 참조 하게 된다.
항상 outer는 오직 자신이 선언된 시점의 LexicalEnvironment를 참조하고 있으므로, 가장 가까운 요소부터 차례대로 접근 가능
결론 : 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에게만 접근이 가능.
// 아래 코드를 여러분이 직접 call stack을 그려가며 scope 관점에서 변수에 접근해보세요!
// 어려우신 분들은 강의를 한번 더 돌려보시기를 권장드려요 :)
var a = 1;
var outer = function() {
var inner = function() {
console.log(a); // undefined
var a = 3;
};
inner();
console.log(a); // 1 inner은 사라짐 따라서 outer나 -> 전역 순서로 참조함
};
outer();
console.log(a); // 1 outer없음, 전역에서 참조해야함
다른 객체지향 언어해서의 this는 곧 클래스로 생성한 인스턴스를 말한다.
그러나 자바스크립트에서는 this가 어디에서나 사용될 수 있다.
실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체이다.
그 객체 안에는 3가지가 존재한다.
✓ VariableEnvironment
✓ LexicalEnvironment
✅ ThisBindings
this는 함수를 호출할 때 결정된다.
라고 할 수 있다.a. 전역 공간에서의 this
1. 전역 공간에서 this는 전역 객체를 가리킨다.
2. 런타임 환경에 따라 this는 window(브라우저 환경) 또는 global(node 환경)를 각각 가르킨다.
런타임 환경?
javascript를 구동중인 환경을 말한다.
a. 함수 vs 메서드
함수와 메서드, 상당히 비슷해 보이지만 엄연한 차이가 존재한다. 기준은 독립성이다. 함수는 그 자체로 독립적인 기능을 수행한다.
함수명();
그러나 메서드는 자신을 호출한 대상 객체에 대한 동작을 수행한다
객체.메서드명();
b. this의 할당
// CASE1 : 함수
// 호출 주체를 명시할 수 없기 때문에 this는 전역 객체를 의미해요.
var func = function (x) {
console.log(this, x);
};
func(1); // Window { ... } 1
// CASE2 : 메서드
// 호출 주체를 명시할 수 있기 때문에 this는 해당 객체(obj)를 의미해요.
// obj는 곧 { method: f }를 의미하죠?
var obj = {
method: func,
};
obj.method(2); // { method: f } 2
c. 함수로서의 호출과 메서드로서의 호출 구분 기준: .``[]
var obj = {
method: function (x) { console.log(this, x) }
};
obj.method(1); // { method: f } 1
obj['method'](2); // { method: f } 2
d. 메서드 내부에서의 this
this에는 호출을 누가 했는지에 대한 정보
가 담긴다
var obj = {
methodA: function () { console.log(this) },
inner: {
methodB: function() { console.log(this) },
}
};
obj.methodA(); // this === obj
obj['methodA'](); // this === obj
obj.inner.methodB(); // this === obj.inner
obj.inner['methodB'](); // this === obj.inner
obj['inner'].methodB(); // this === obj.inner
obj['inner']['methodB'](); // this === obj.inner
a. 함수 내부에서의 this
어떤 함수를 함수로서 호출할 경우, this는 지정되지 않는다(호출 주제가 알 수 없다)
실행컨텍스트를 활성화 할 당시 this가 지정되지 않은 경우, this는 전역 객체를 의미한다.
따라서 함수로서 '독립적으로'호출할 때는
this는 항상 전역객체를 가리킨다
는 것을 주의
b. 메서드 내부함수에서의 this
- 메서드 내부라고 해도, 함수로서 호출한다면 this는 전역 객체를 의미한다
var obj1 = {
outer: function() {
console.log(this); // (obj1)
var innerFunc = function() {
console.log(this); // (전역객체), (obj2)
}
innerFunc();
var obj2 = {
innerMethod: innerFunc
};
obj2.innerMethod();
}
};
obj1.outer();
❗ this 바인딩에 관해서는 함수를 실행하는 당시의 주변 환경(메서드 내부인지, 함수 내부인지)는 중요하지 않고 오직 해당 함수를 호출하는 구문앞에 점 또는 대괄호 표기가 있는지가 관건이라는 것을 알 수 있다.
c. 메서드 내부 함수에서의 this우회
내부 스코프에 이미 존재하는 this를 별도의 변수(ex: self)에 할당하는 방법.
var obj1 = {
outer: function() {
console.log(this); // (1) outer
// AS-IS
var innerFunc1 = function() {
console.log(this); // (2) 전역객체
}
innerFunc1();
// TO-BE
var self = this;
var innerFunc2 = function() {
console.log(self); // (3) outer
};
innerFunc2();
}
};
// 메서드 호출 부분
obj1.outer();
ES6에서는 함수 내부에서 this가 전역객체를 바라보는 문제 때문에 화살표 함수를 도입했다.
일반 함수와 화살표 함수의 가장 큰 차이점은?
=> this binding 여부
var obj = {
outer: function() {
console.log(this); // (1) obj
var innerFunc = () => {
console.log(this); // (2) obj
};
innerFunc();
}
}
obj.outer();
우리는 앞선 과정에서 콜백 함수를 다음과 같이 정의한 적이 있다.
"어떠한 함수, 메서드의 인자(매개변수)로 넘겨주는 함수"
이 때, 콜백함수 내부의 this는 해당 콜백함수를 넘겨받은 함수(메서드)가 정한 규칙에 따라 값이 결정된다. 콜백 함수도 함수
기 때문에 this는 전역 객체를 참조하지만(호출 주체가 없다), 콜백함수를 넘겨받은 함수에서 콜백 함수에 별도로 this를 지정한 경우는 예외적으로 그 대상을 참조하게 되어있다.
❗ 로직을 이해하는 것 보다는 this의 상태를 이해하는 것이 중요
// 별도 지정 없음 : 전역객체
setTimeout(function () { console.log(this) }, 300);
// 별도 지정 없음 : 전역객체
[1, 2, 3, 4, 5].forEach(function(x) {
console.log(this, x);
});
// addListener 안에서의 this는 항상 호출한 주체의 element를 return하도록 설계되었음
// 따라서 this는 button을 의미함
document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a').addEventListener('click', function(e) {
console.log(this, e);
});
setTimeout 함수, foeEach 메서드는 콜백 함수를 호출할 때 대상이 될 this를 지정하지 않으므로 this는 곧 window객체
addEventListner 메서드는 콜백 함수 호출 시, 자신의 this를 상속하므로, this는 addEventListner의 앞부분 (button태그)
생성자 : 구체적인 인스턴스(어려운면 객체로 이해)를 만들기 위한 일종의 틀
var Cat = function (name, age) {
this.bark = '야옹';
this.name = name;
this.age = age;
};
var choco = new Cat('초코', 7); //this : choco
var nabi = new Cat('나비', 5); //this : nabi
자동으로 부여되는 상활별 this의 규칙을 깨고 this에 별도 값을 저장하는 방법
call
apply
bind
에 대해 알아보자.
a. 호출 주체인 함수를 즉시 실행하는 명령어이다.
b. call명령어를 사용하여, 첫 번째 매개변수에 this로 binding할 객체를 넣어주면 명시적으로 binding할 수 있다.
var func = function (a, b, c) {
console.log(this, a, b, c);
};
// no binding
func(1, 2, 3); // Window{ ... } 1 2 3
// 명시적 binding
// func 안에 this에는 {x: 1}이 binding돼요
func.call({ x: 1 }, 4, 5, 6}; // { x: 1 } 4 5 6
아래 예시는, 예상되는 this가 있어도 일부러 바꾸는 연습
var obj = {
a: 1,
method: function (x, y) {
console.log(this.a, x, y);
}
};
obj.method(2, 3); // 1 2 3
obj.method.call({ a: 4 }, 5, 6); // 4 5 6
a. call메서드와 완전히 동일하다 다만, this에 binding할 객체는 똑같이 넣어주고 나머지 부분만 배열 형태
로 넘겨준다
var func = function (a, b, c) {
console.log(this, a, b, c);
};
func.apply({ x: 1 }, [4, 5, 6]); // { x: 1 } 4 5 6
var obj = {
a: 1,
method: function (x, y) {
console.log(this.a, x, y);
}
};
obj.method.apply({ a: 4 }, [5, 6]); // 4 5 6
물론 this binding을 위해 call, apply, method를 사용하기도 하지만 더 유용한 측면도 있다.
//객체에는 배열 메서드를 직접 적용할 수 없어요.
//유사배열객체에는 call 또는 apply 메서드를 이용해 배열 메서드를 차용할 수 있어요.
var obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
Array.prototype.push.call(obj, 'd');
console.log(obj); // { 0: 'a', 1: 'b', 2: 'c', 3: 'd', length: 4 }
var arr = Array.prototype.slice.call(obj);
console.log(arr); // [ 'a', 'b', 'c', 'd' ]
사실, call / apply를 통해 this binding을 하는 것이 아니라 객체 -> 배열
로의 형 변환 만을 위해서도 쓸 수 있지만 원래 의도와는 거리가 먼 방법이라 할 수 있다.
따라서 ES6에서는 Array.from
이라는 방법을 제시했는데 아주 편리하다
// 유사배열
var obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
// 객체 -> 배열
var arr = Array.from(obj);
// 찍어보면 배열이 출력됩니다.
console.log(arr);
Student
,Employee
모두Person
이다. name과 gender 속성 모두 필요하다. 그러니Student
와Employee
인스턴스를 만들 때 마다 세 가지 속성을 모두 각 생성자 함수에 넣기 보다는Person
이라는 생성자 함수를 별도로 빼는게 ‘구조화’에 도움이 더 도움이 된다.
function Person(name, gender) {
this.name = name;
this.gender = gender;
}
function Student(name, gender, school) {
Person.call(this, name, gender); // 여기서 this는 student 인스턴스!
this.school = school;
}
function Employee(name, gender, company) {
Person.apply(this, [name, gender]); // 여기서 this는 employee 인스턴스!
this.company = company;
}
var kd = new Student('길동', 'male', '서울대');
var ks = new Employee('길순', 'female', '삼성');
a. apply를 통해 비효율적인 예시를 효율적인 예시로 바꿔보자
//비효율
var numbers = [10, 20, 3, 16, 45];
var max = min = numbers[0];
numbers.forEach(function(number) {
// 현재 돌아가는 숫자가 max값 보다 큰 경우
if (number > max) {
// max 값을 교체
max = number;
}
// 현재 돌아가는 숫자가 min값 보다 작은 경우
if (number < min) {
// min 값을 교체
min = number;
}
});
console.log(max, min);
가독성이 너무 떨어진다
//효율
var numbers = [10, 20, 3, 16, 45];
var max = Math.max.apply(null, numbers);
var min = Math.min.apply(null, numbers);
console.log(max, min);
// 펼치기 연산자(Spread Operation)를 통하면 더 간편하게 해결도 가능해요
const numbers = [10, 20, 3, 16, 45];
const max = Math.max(...numbers);
const min = Math.min(...numbers);
console.log(max min);
a. call과 비슷해 보인다. 하지만 즉시 call과는 다르게 즉시 호출하지는 않고 넘겨받은 this 및 인수들을 바탕으로 새로운 함수를 반환하는 메서드라고 볼 수 있다.
b. 목적
1. 함수에 this를 미리 적용한다
2. 부분 적용 함수 구현할 때 용이하다.
c. 예시
var func = function (a, b, c, d) {
console.log(this, a, b, c, d);
};
func(1, 2, 3, 4); // window객체
// 함수에 this 미리 적용
var bindFunc1 = func.bind({ x: 1 }); // 바로 호출되지는 않아요! 그 외에는 같아요.
bindFunc1(5, 6, 7, 8); // { x: 1 } 5 6 7 8
// 부분 적용 함수 구현
var bindFunc2 = func.bind({ x: 1 }, 4, 5); // 4와 5를 미리 적용
bindFunc2(6, 7); // { x: 1 } 4 5 6 7
bindFunc2(8, 9); // { x: 1 } 4 5 8 9
d. name 프로퍼티
var func = function (a, b, c, d) {
console.log(this, a, b, c, d);
};
var bindFunc = func.bind({ x:1 }, 4, 5);
// func와 bindFunc의 name 프로퍼티의 차이를 살펴보세요!
console.log(func.name); // func
console.log(bindFunc.name); // bound func
e. 상위 컨텍스트의 this를 내부함수나 콜백 함수에 전달하기
1) 내부함수
메서드의 내부함수에서 메서드의 this를 그대로 사용하기 위한 방법이다.
self등 변수를 활용한 우회법보다 call, apply, bind를 사용하면 깔끔하게 처리 가능하기 때문에 이렇게 이용하는게 더 낫다.
var obj = {
outer: function() {
console.log(this); // obj
var innerFunc = function () {
console.log(this);
};
// call을 이용해서 즉시실행하면서 this를 넘겨주었습니다
innerFunc.call(this); // obj
}
};
obj.outer();
이번엔 call이 아니라 bind를 이용한다
var obj = {
outer: function() {
console.log(this); // obj
var innerFunc = function () {
console.log(this);
}.bind(this); // innerFunc에 this를 결합한 새로운 함수를 할당
innerFunc();
}
};
obj.outer();
2) 콜백함수
콜백함수도 함수이기 때문에, 함수가 인자로 전달될 때는 함수 자체로 전달한다. (this유실)
bind메서드를 이용해 this를 입맛에 맞게 변경 가능하다
var obj = {
logThis: function () {
console.log(this);
},
logThisLater1: function () {
// 0.5초를 기다렸다가 출력해요. 정상동작하지 않아요.
// 콜백함수도 함수이기 때문에 this를 bind해주지 않아서 잃어버렸어요!(유실)
setTimeout(this.logThis, 500);
},
logThisLater2: function () {
// 1초를 기다렸다가 출력해요. 정상동작해요.
// 콜백함수에 this를 bind 해주었기 때문이죠.
setTimeout(this.logThis.bind(this), 1000);
}
};
obj.logThisLater1();
obj.logThisLater2();
화살표 함수는 실행 컨텍스트 생성 시, this를 바인딩하는 과정이 제외된다고 했다.
이 함수 내부에는 this의 할당과정(바인딩 과정)이 아에 없으며, 접근코자 하면 스코프체인상 가장 가까운 this에 접근하게 됨
this우회, call, apply, bind보다 편리한 방법
var obj = {
outer: function () {
console.log(this); // obj
var innerFunc = () => {
console.log(this); // obj
};
innerFunc();
};
};
obj.outer();