this

Min·2021년 1월 14일
0

CoreJavaScript

목록 보기
3/4
post-thumbnail

1. 상황에 따라 달라지는 this

this는 실행 컨텍스트가 생성될 때 함께 결정되는데
실행 컨텍스트는 함수를 호출할 때 생성되므로
this는 함수를 호출할 때 결정된다.

1) 전역 공간에서의 this

this : 전역 객체
(전역 컨텍스트를 생성하는 주체가 바로 전역 객체이기 때문)

전역 객체
1) 브라우저 환경 : window

console.log(this === window); // true

2) Node.js 환경

console.log(this === global); // true

전역변수를 선언하면 자바스크립트 엔진은
이를 전역객체의 프로퍼티로도 할당한다.

변수이면서 객체의 프로퍼티이다.

var a; // 변수
window.a // 객체의 프로퍼티

자바스크립트의 모든 변수는 실은 특정 객체(LexicalEnvironment)의 프로퍼티로서 동작한다.

실행 컨텍스트는 변수를 수집해서
LexicalEnvironment의 프로퍼티로 저장 후
어떤 변수를 호출하면 LexicalEnvironment를 조회해서 일치하는 프로퍼티가 있을 경우 그 값을 반환한다.

전역 컨텍스트의 경우 LexicalEnvironment는
전역객체를 그대로 참조한다.

var a = 1; // 전역변수
console.log(a); // 1 : 변수, a === window.a
console.log(window.a); // 1 : 전역객체의 프로퍼티
console.log(this.a); // 1 : 전역객체의 프로퍼티

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

전역변수로 선언한 경우 : 삭제 x
처음부터 전역객체의 프로퍼티로 할당한 경우 : 삭제

// 1. 전역변수로 선언한 경우
var a = 1;
delete window.a; // false
console.log(a, window.a, this.a) // 1 1 1

// 2. 처음부터 전역객체의 프로퍼티로 할당한 경우
window.b = 2
delete window.b; // true
delete d; // true
console.log(b, window.b, this.b); // Uncaught ReferenceError: b is not defined

전역변수와 전역객체의 프로퍼티 차이
1) 호이스팅 여부
2) configurable 여부

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

함수 vs 메서드

함수를 실행하는 방법
1) 함수로서 호출하는 경우

a();

2) 메서드로서 호출하는 경우

obj.a();

차이점 : 독립성
1) 함수 : 그 자체로 독립적인 수행
2) 메서드 : 자신을 호출한 대상 객체와 관한 동작 수행

어떤 함수를 객체의 프로퍼티에 할당한다고 해서
그 자체로서 무조건 메서드가 되는 것이 아니라
객체의 메서드로서 호출할 경우에만 메서드로 동작하고,
그렇지 않으면 함수로 동작한다.

구분 : 함수 앞에 점(.)이나 대괄호([]) 표기 여부

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

var obj = {
	method: func
};
obj.method(2); // { method: f } 2
obj['method'](2); // { method: f } 2

메서드 내부에서의 this

어떤 함수를 메서드로 호출하는 경우
호출 주제는 바로 함수명(프로퍼티명) 앞의 객체이다.

var obj = {
	methodA: function () { console.log(this); },
  	inner: {
    	methodB: function () {   console.log(this); }
    }
};
obj.methodA(); // { methodA: f, inner: {...} } === obj
obj.inner.methodB(); // { methodB: f } === obj.inner

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

함수 내부에서의 this

this : 전역 객체

메서드의 내부함수에서의 this

var obj1 = {
	outer: function() {
    	console.log(this); // obj1
      	var innerFunc = function () {
        	console.log(this); // 전역객체(Window)
        }
        innerFunc();
      
      	var obj2 = {
        	innerMethod: innerFunc
        };
      	obj2.innerMethod(); // obj2
    }
};
obj1.outer();

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

변수를 검색하면 우선 가장 가까운 스코프의 LexicalEnvironment를 찾고 없으면
상위 스코프를 탐색하듯이,

this 역시 현재 컨텍스트에 바인딩된 대상이 없으면
직전 컨텍스트의 this를 바라보도록 하는게 이상적이다.

ES5 : 변수를 활용하는 방법

var obj = {
	outer: function() {
    	console.log(this); // { outer: f }
      	var innerFunc1 = function () {
        	console.log(this); // Window { ... }
        };
        innerFunc1();
      
      	var self = this; // 상위 스코프의 this를 저장해서 내부함수에서 활용
      
      	var innerFunc2 = function () {
        	console.log(self); // { outer: f }
        };
      	innerFunc2();
    }
};
obj.outer();

this를 바인딩하지 않는 함수

ES6 : 화살표 함수
실행 컨텍스트를 생성할 때
this 바인딩 과정 자체가 빠지게 되어,
상위 스코프의 this를 그대로 활용할 수 있다.

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

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

콜백함수

함수 A의 제어권을 다른 함수(또는 메서드)
B에게 넘겨주는 경우

함수 A를 콜백 함수라 하며
B의 내부로직에 따라 실행된다.

1) 기본적으로 콜백함수도 함수이기 때문에
this : 전역 객체
2) 제어권을 받은 함수에 따라 this가 달라짐

// 1. this : 전역객체
setTimeout(function () { console.log(this); }, 300);

// 2. this : 전역객체
[1, 2, 3, 4, 5].forEach(function (x) {
	console.log(this, x);
});

// 3. this : <button id="a">클릭</button>
// Mouse Event { isTrusted: true, ... }

// addEventListener 메서드는 자신의 this를 상속하도록 되어있다.
document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a')
	.addEventListener('click', function (e) {
		console.log(this, e);
	});

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

생성자 함수

어떤 공통된 성질을 지니는 객체들을 생성하는 데 사용하는 함수

생성자

1) 클래스 :구체적인 인스턴스를 만들기 위한 일종의 틀)
2) 인스턴스 : 클래스를 통해 만든 객체

new 명령어와 함께 함수를 호출하면
해당 함수가 생성자로 동작하게 되며
어떤 함수가 생성자 함수로서 호출된 경우
내부에서의 this는
곧 새로 만들 구체적인 인스턴스 자신이 된다.

생성자 함수 호출

new 명령어와 함께 함수를 호출하면
1) 생성자의 prototype 프로퍼티를 참조하는
__proto__라는 프로퍼티가 있는
객체(인스턴스)를 만든다.

2) 미리 준비된 공통 속성 및 개성을
해당 객체(this)에 부여

3) 구체적인 인스턴스가 만들어진다.

var Dog = function (name) {
	this.name = name;
}
var cappy = new Dog('Cappy'); // this : cappy 인스턴스
console.log(cappy) // Dog { name: 'Cappy' }

2. 명시적으로 this를 바인딩하는 방법

this에 어떤 값이 바인딩 되는지를 바꾸는 방법

1) call 메서드

call(this, 인자들)

Function.prototype.call(thisArg[, arg1[, arg2[, ...]]])
var func = function (a, b, c) {
	console.log(this, a , b ,c);
};
func(1, 2, 3); // Window{ ... } 1 2 3
func.call({ 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(2, 3); // 1 2 3
obj.method.call({ a: 4 }, 5, 6); // 4 5 6

2) apply 메서드

apply(this, 인자들의 배열)

Function.prototype.apply(thisArg[, argsArray])
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

3) call / apply 메서드의 활용

유사배열객체에 배열 메서드를 적용

1) 유사배열객체에 배열 메서드를 적용

객체에는 배열 메서드를 직접 적용할 수 없으나,
키가 0 또는 양의 정수인 프로퍼티가 존재하고,
legnth 프로퍼티 값이 0 또는 양의 정수인 객체,

즉, 배열의 구조와 유사한 객체의 경우(유사배열객체)
call 또는 apply 메서드를 이용해 배열 메서드를 차용할 수 있다.

var obj = {
	0: 'a',
  	1: 'b',
  	2: 'C',
  	length: 3
};
// 배열메서드인 push를 객체 obj에 적용
Array.prototype.push.call(obj, 'd');
console.log(obj); // { 0: 'a', 1: 'b', 2: 'c', 3: 'd', length: 4}

// slice 메서드를 통해 객체를 배열로 전환
var arr = Array.prototype.slice.call(obj);
console.log(arr); // [ 'a', 'b', 'c', 'd' ]

2) arguments, NodeList에 배열 메서드를 적용
함수 내부에서 접근할 수 있는 arguments 객체도 유사배열객체므로
위의 방법으로 배열로 전환해서 활용할 수 있다.

function a () {
	var argv = Array.prototype.slice.call(arguments);
  	argv.forEach(function (arg) {
    	console.log(arg);
    });

};
a(1, 2, 3); // 1 2 3

Node 선택자(querySelectorAll, getElementsByClassName 등)로
선택한 결과인 NodeList도 마찬가지이다.

document.body.innerHTML = '<div>a</div><div>b</div><div>c</div>';
var nodeList = document.querySelectorAll('div');
var nodeArr = Array.prototype.slice.call(nodeList);                           
nodeArr.forEach(function (node) {
	console.log(node); // <div>a</div><div>b</div><div>c</div>
});

3) 문자열에 배열 메서드 적용
유사배열객체에는 call/apply 메서드를 이용해 모든 배열 메서드를 적용할 수 있다.

배열처럼 인덱스와 length 프로퍼티를 지니는 문자열에도 가능하지만
문자열의 경우 length 프로퍼티가 읽기 전용이기 때문에

원본 문자열 변경이 가능한 메서드(push, pop, shift, unshift, splice 등)은
에러를 던지며,

concat처럼 대상이 반드시 배열이어야 하는 경우에는
에러는 나지 않지만 제대로된 결과를 얻을 수 없다.

var str = 'abc def';

// 1. push/call
Array.prototype.push.call(str, ',pushed string');
// Uncaught TypeError: Cannot assign to read only property 'length' of object '[object String]

// 2. concat/call
Array.prototype.concat.call(str, 'string');
// [String {"abc def"}, "string"]

// 3. every/call
Array.prototype.every.call(str, function(char) {
	return char !== ' ';
}); // false

// 4. some/call
Array.prototype.some.call(str, function(char) {
	return char === ' '; 
}); // true

// 5. map/call
var newArr = Array.prototype.map.call(str, function(char) {
	return char + '!';
});
console.log(newArr); // ["a!", "b!", "c!", " !", "d!", "e!", "f!"]

// 6. reduce/apply
var newArr = Array.prototype.reduce.apply(str, [
  	function(string, char, i) {
		return string + char + i; }, ''
]);
console.log(newArr); // "a0b1c2 3d4e5f"

4) ES6의 Array.from 메서드

var obj = {
	0: 'a',
  	1: 'b',
  	2: 'C',
  	length: 3
};

var arr = Array.from(obj);
console.log(arr); // [ 'a', 'b', 'c']

생성자 내부에서 다른 생성자를 호출

생성자 내부에 다른 생성자와 공통된 내용이 있을 경우
call 또는 apply를 다른 생성자를 호출하면 간단한게 반복을 줄일 수 있다.

Student, Employee 생성자 함수 내부에서
Person 생성자 함수를 호출해서
인스턴스의 속성을 정의하도록 구현

function Person(name, gender) {
	this.name = name;
  	this.gender = gender;
};

function Student(name, gender, school) {
	Person.call(this, name, gender);
  	this.school = school;
};

function Employee(name, gender, company) {
	Person.apply(this, [name, gender]);
  	this.company = company;
};
var by = new Student('보영', 'female', '단국대);
var jn = new Employee('재난', 'male', '구골');

여러 인수를 묶어 하나의 배열로 전달하고 싶을 때 - apply 활용

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); // 45 3

ES6 Spread 연산자 이용

const numbers = [10, 20, 3, 16, 45];
const max = Math.max(...numbers);
const min = Math.min(...numbers);
console.log(max, min); // 45 3

4) bind 메서드

Function.prototype.bind(thisArg[, arg1[, arg2[, ...]]])

name 프로퍼티

상위 컨텍스트의 this를 내부함수나 콜백 함수에 전달하기

5) 화살표 함수의 예외사항

6) 별도의 인자로 this를 받는 경우(콜백 함수 내에서의 this)

profile
slowly but surely

0개의 댓글