This pt.1

심현인·2021년 6월 17일
0

JS에서 가장 거지같은 개념을 고르라고 하면 this일 것이다.

상황에 따라 달라지는 this

JS에서 this는 함수를 호출할 때 결정된다.
즉 어떤 방식으로 함수를 호출하냐에 따라 값이 달라진다는 뜻.

1. 전역공간에서의 this

전역 공간에서 this는 전역 객체를 가르킨다. 전역 객체는 JS런타임 환경에 따라 다른 정보를 갖고 있다.
브라우저 환경 -> window
Node.js -> global

/*브라우저 환경*/

console.log(this)
//Window {0: global, window: Window, self: Window, document: document, name: "", location: Location, …}
console.log(window)
//Window {0: global, window: Window, self: Window, document: document, name: "", location: Location, …}
console.log(this === window) //true

/*Node JS*/

console.log(this)
/*Object [global] {
  global: [Circular],
  clearInterval: [Function: clearInterval],
  clearTimeout: [Function: clearTimeout],
  setInterval: [Function: setInterval],
  setTimeout: [Function: setTimeout] { [Symbol(util.promisify.custom)]: [Function] },
  queueMicrotask: [Function: queueMicrotask],
  clearImmediate: [Function: clearImmediate],
  setImmediate: [Function: setImmediate] {
    [Symbol(util.promisify.custom)]: [Function]
  }
}*/
console.log(global)
/*Object [global] {
  global: [Circular],
  clearInterval: [Function: clearInterval],
  clearTimeout: [Function: clearTimeout],
  setInterval: [Function: setInterval],
  setTimeout: [Function: setTimeout] { [Symbol(util.promisify.custom)]: [Function] },
  queueMicrotask: [Function: queueMicrotask],
  clearImmediate: [Function: clearImmediate],
  setImmediate: [Function: setImmediate] {
    [Symbol(util.promisify.custom)]: [Function]
  }
}*/
console.log(this === global) //true

전역 공간에서만 발생하는 특이한 성질

JS에서 전역변수를 선언하면 JS엔진은 이를 전역객체의 프로퍼티로도 할당한다

var a = 1;
console.log(a)
console.log(window.a)
console.log(this.a)

전역공간에서 변수 a에 1을 할당했는데 어째서 window.a, this.a가 모두 1이 출력이 될까?
그 이유는 JS의 모든 변수가 실은 어떤 객체의 프로퍼티로서 동작하기 때문이다.
어떤 객체란 바로 실행 컨텍스트의 LexicalEnvironment(이하 LE)다.
실행 컨텍스트는 변수를 수집해서 LE의 프로퍼티로 저장을 하고, 이후 어떤 변수를 호출하면 LE를 조회해서 일치하는 프로퍼티가 있을 경우 그 값을 반환한다. 물론 전역 컨텍스트 같은 경우 LE는 전역 객체를 그대로 참조한다.
그래서 window.a, this.a가 1이 나온다는게 설명이 되었다면, 왜 a를 그냥 호출해도 1이 나올까?
그 이유는 a에 접근하려고 ㅎㅏ면 스코프 체인에서 a를 검색하다가 가장 마지막에 도달하는 전역 스코프의 LE, 즉 전역객체에서 해당 프로퍼티 a를 발견해서 그 값을 반환하기 때문에다.
간단히 얘기하자면 앞에 (window.)이 생략된 것이라는 뜻.
따라서 var로 변수를 선언하면 window 프로퍼티에 직접 할당을 해도 똑같은 동작을 한다 라고 생각이 들 수도 있다. 거의 맞긴하지만 100프로 그런 것은 아니다

var a = 1
console.log(a, this.a, window.a)//1 1 1

window.b = 2
console.log(b, this.b, window.b)// 2 2 2

this.c = 3
console.log(c, this.c, window.c)//3 3 3

하지만 삭제를 하는경우

var a = 1
delete window.a // false
console(a, window.a, this.a) //1 1 1

var b = 2
delete b // false
console(b, window.b, this.b) //2 2 2

window.c = 2
delete window.c // true
console(c, window.c, this.c) //Uncaught ReferenceError: c is not defined

window.d = 2
delete d // true
console(c, window.d, this.d) //Uncaught ReferenceError: d is not defined

전역객체의 프로퍼티로 할당을 한 경우에는 삭제가 되지만, 전역 변수로 선언을 할 경우 삭제가 되지 않는다.
유저가 의도치 않게 삭제하는 것을 방지하기 위해 마련한 나름의 방어전략이다.
즉 전역변수를 선언하면 JS엔진이 이를 자동으로 전역객체의 프로퍼티로 할당하면서 추가적으로 해당 프로퍼티의 configuable 속성을 false로 정의하는 것.
따라서 var로 선언한 전역변수와 전역객체의 프로퍼티는 호이스팅 여부 및 configuable 여부에서도 차이를 보인다!

2. 메소드로서 호출할 때 그 메소드 내부에서의 this

함수 vs 메소드

함수를 실행하는 방법은 여러가지가 있는데 그 중 가장 일반적인 방법은
1. 함수로서 호출하는 경우
2. 메소드로 호출하는 경우
이다. 이 둘은 독립성에서 차이가 나며, 함수는 그 자체적으로 독립적인 기능을 수행하지만, 메소드는 자신을 호출한 대상 객체에 관한 동작을 수행한다.
메소드는 단순히 객체의 프로퍼티에 할당된 함수가 아니다. 객체의 메소드로서 호출할 경우에만 메소드로 동작하고, 그렇지 않으면 함수로 동작한다.

var func = function(x){
	console.log(this, x)
 }
func(1) // Window{...} 1

var obj = {
	method: func
}
obj.method(2) //{method: ƒ} 2

해당 코드를 해석하자면, func라는 변수에 익명함수를 할당 하였고, 호출했더니 this로 전역객체 window가 출력이 된다, obj라는 변수에 객체를 할당하고, method프로퍼티 앞에 func라는 함수를 할당한 후, 실행을 하니 여기선this가 method라고 한다. 즉 원래의 익명함수는 그대로인데 이를 변수에 담아 호출한 경우와 obj객체의 프로퍼티에 할당해 호출한 경우 this가 달라지는 것이다.
그렇다고하면 '함수로서의 호출' 과 '메소드로서의 호출'을 어떻게 구분할수 있는가?
-> 함수앞에 '.' 으로 구분 할 수 있다.
혹은 대괄호 표기법을 따르는지 여부로도 구분이 가능하다


var obj = {
	method: function(x){
	console.log(this, x)
 }
}
obj.method(1) //{method: ƒ} 1
obj['method'](2) //{method: ƒ} 2

메소드 내부에서의 this

this에는 호출한 주체에 대한 정보가 담긴다.
어떤 함수를 메소드로서 호출하는 경우 호출 주제는 바로 함수명 앞의 객체다.


var obj = {
	methodA: function(x){console.log(this)},
  inner:{
  	methodB: functoin(){console.log(this)}}
}
obj.methodA() //{inner: {…}, methodA: ƒ} === obj
obj['methodA']() //{inner: {…}, methodA: ƒ} === obj

obj.inner.methodB() //{methodB: ƒ} === obj.inner
obj.inner['methodB'] //{methodB: ƒ} === obj.inner
obj['inner'].mothodB()//{methodB: ƒ} === obj.inner
obj['inner']['methodB']//{methodB: ƒ} === obj.inner

3. 함수로서 호출할 때 그 함수 내부에서의 this

함수 내부에서의 this

어떤 함수를 함수로서 호출할 경우 this가 지정되지 않는다. 왜냐하면 this에서는 호출한 주체에 대한 정보가 담겨야하는데, 함수로서 호출 하는 것은 호출 주체(객체지향언어에서의 객체)를 명시하시 않고, 개발자가 코드에 직접 관여해서 실행한 것이기 때문이다. 실행컨텍스트에를 활성화할 당시에 this가 지정되지 않은 경우 전역 객체를 바라본다고 했으니, 따라서 함수에서의 this는 전역 객체를 가르킨다.

var obj1 = {
  outer: function(){
    console.log(this)
    var innerFunc = function(){
      console.log(this)
    }
    innerFunc() // window..

    var obj2 = {
      innerMethod: innerFunc
    }
    obj2.innerMethod() //obj2
  }
}
obj1.outer() // obj1

this 바인딩에 관해서는 함수를 실행하는 당시의 주변환경은 중요하지 않고, 오직 해당 함수를 호출하는 구문 앞에 점 또는 대괄호 표기가 없는지가 관건이다.

메소드 내부 함수에서의 this를 우회하는 방법

위의 방식대로 하면 this에 대한 구분은 명확하게 할 수 있지만, 우리가 알던 this와는 많이 달라진다. 호출 주체가 없을땐 주변 환경의 this를 상속받아 사용할 수는 없는걸까?
아쉽게도 ES5까지는 자체적으로 내부함수에 this를 상속할 방법이 없지만 다행히 이를 우회하는 방법은 있었다.

var obj1 = {
  outer: function () {
    console.log(this); //{outer: ƒ}
    var innerFunc1 = function () {
      console.log(this); //Window...
    };
    innerFunc1();
    var self = this;
    var innerFunc2 = function () {
      console.log(self); //{outer: ƒ}
    };
    innerFunc2();
  },
};
obj1.outer();

outer스코프에서 self라는 변수에 this를 저장한 상태에서 호출한 innerFunc2의 경우 self에는 객체 obj가 출력된다.그저 상위 스코프의 this를 저장하여 내부함수에서 활용하려는 수단이다.

this를 바인딩하지 않는 함수

ES6에서는 함수 내부에서 this가 전역객체를 바라보는 것을 보완하고자, this를 바인딩 하지 않는 함수 화살표함수를 새로 도입했습니다. 화살표 함수를 실행컨텍스트를 생성할 때 this 바인딩 과정 자체가 빠지게되어, 상위 스코프 this를 활용 할 수 있다.(call, apply등 메소드를 활용해서 명시적으로 this를 지정 할 수 있다!)

var obj1 = {
  outer: function () {
    console.log(this); //{outer: ƒ}
 	var innerFunc = () =>{
	console.log(this); //{outer: ƒ}
    }
    innerFunc()
}}
obj1.outer();

4. 콜백 함수 호출 시 그 함수 내부에서의 this

함수 A의 제어권을 다른 함수 에게 넘겨주는경우 함수 A를 콜백함수라고 부른다(자세한건 다음에)
이때 콜백함수도 함수이기 때문에 this가 전역객체를 참조하지만, 제어권을 받은 함수에서 콜백 함수에 별도로 this가 될 대상을 지정한 경우는 그 대상을 참조하게 된다

setTimeout(function(){console.log(this)}, 300)// 300ms뒤에 전역객체가 출력된다

[1,2,3,4,5].forEach(function(x){
    console.log(this,x)
}) // 전역객체과 각 배열의 각 요소가 5번 출력된다

document.body.innerHTML +=`<button id='a'>클릭</button>`
document.body.querySelector('#a')
  .addEventListener('click', function(e){
      console.log(this,e)
  }) // 버튼을 클릭하면 앞서 지정한 엘리먼트와 클릭 이벤트에 관한 정보의 객체가 출력된다.

5. 생성자 함수 내부에서의 this

생성자 함수는 어떤 공통된 성질을 지니는 객체들을 생성하는데 사용하는 함수다.
JS는 함수에 생성자로서의 역할을 함께 부여했는데, new 명령어와 함꼐 함수를 호출하게 되면 해당 함수가 그 역하을 한다. 그리고 어떤 함수가 생성자 함수로서 호출된 경우 내부에서의 this는 곧 새로만들 구체적인 인스턴스 자신이 된다.
생성자 함수를 호출하면 우선 생성자의 prototype 프로퍼티를 참조하는 proto라는 프로퍼티가 있는 객체를 만들고 미리 준비된 공통 속성 및 개성을 해당 객체(this)에 부여한다

var Dog = function(name, age){
  this.bark = '멍멍';
  this.name = name;
  this.age = age;
}

var cion = new Dog('숑', 4)
var bangoo = new Dog('방구', 2)
console.log(cion, bangoo)
//Dog {bark: "멍멍", name: "숑", age: 4} Dog {bark: "멍멍", name: "방구", age: 2}
profile
가로

0개의 댓글