대망의 this
!!!!!!!!!!!!!!!!!!
내가 너무 알고싶었던 this
!!!!!!!!!!!!!!!!
객체지향의 최고존엄 this
!!!!!!!!!!!!!!!!!!!!!!!!!!!
를 알아보자
메서드는 자신이 속한 객체를 참조할 수 있어야 한다
=> 아하! 자신이 속한 객체의 참조가 this로군
참고로 객체 리터럴{}
방식으로 선언하면, 재귀적으로 참조가능
const obj = {
name:"kim",
getName(){
return obj.name;
}
}
obj.getName() // "kim"
하지만 일반적이지 않고 바람직하지 않다.
짜식아 객체는 생성자 함수나 클래스를 이용하여 인스턴스를 생성 후 사용해야지!
function obj(name){
????.name = name;
}
obj.prototype.getName = function(){
return ????.name
}
//여기까지는 생성자 함수 자신이 생성할 인스턴스를 가리키는 식별자를 알 수 없다.
//obj.xxx로 접근이 불가능하단 소리다.
const instance = new obj("kim"); //이때 instance.xxx로 접근 가능하다.
이처럼 생성자함수 내부에서 생성될 인스턴스의 참조가 필요함.
함수를 호출하면, 암묵적으로 arguments
객체와 this
가 암묵적으로 함수내부에 전달됨
this
의 바인딩은 함수 호출 방법에 의해 동적으로 결정된다!!!!
=> 스코프의 결정방식과 정확히 반대다!. 스코프는 정의 위치에 따라 결정된다
this
는 객체의 프로퍼티나 메서드를 참조하기위한 것이라, 나머지 함수는 의미없음.
=> 그래서 엄격모드에선 undefined가 바인딩 된다.
총 4가지다.
var value = 1;
const obj = {
value: 100,
foo(){
console.log(this.value) //100
}
setTimeout(function(){
console.log(this.value) //1
})
}
이처럼 콜백함수 내부에 일반함수를 호출해도 전역객체가 바인딩됨.
해결법은 this
를 밖에서 변수에 할당 후 참조 및 call, apply, bind
메서드 활용, 화살표 함수를 사용하면 됨.
setTimeout(function(){
console.log(this.value) //100
}.bind(this)).
//or
setTimeout(()=>console.log(this.value)) //100.
//화살표 함수 내부의 this는 상위 스코프의 this임
obj.method
면 obj
가 바인딩.//apply와 call은 함수를 호출하면서 this를 넘긴다
function getThisBinding() {
console.log(arguments);
return this;
}
const thisArg = { a: 1 };
console.log(getThisBinding.apply(thisArg, [1,2,3]));
console.log(getThisBinding.call(thisArg, 1, 2, 3));
//Arguments(3) [1, 2, 3, callee: f, Symbol(Symbol.iterator):f]
//결과는 둘 다똑같다...차이는 인수를 '배열'이냐 '쉼표구분'이냐다.
보통 유사배열객체에 배열 메소드를 사용하는 경우에 쓴다.
function convertArgToArray(){
console.log(arguments);
const arr = Array.prototype.slice.call(arguments);
console.log(arr);
return arr;
}
convertArgToArray(1,2,3) // [1,2,3]
bind는 this
바인딩이 교체된 새로운 함수를 반환한다.
A clouser is the combination of a function and the lexical environment within which that function was declared
뭐라카노?
하지만 23챕터(실행컨텍스트)를 배웠다면...
=> 함수와 함수가 선언된 렉시컬 환경의 조합. 아하! 외부환경을 참조하고있어서 내부 함수가 외부 함수보다 오래 살아있는 거구나 라며 단박에 이해할수 있다.
흠! 그때도 나쁘지 않게 이해하고 있었군.
그래도 이번 챕터자체가 클로저인 만큼, 보다 자세히 알아보자
단, 클로저 자체보다는 활용에 집중하겠다.
[[Environment]]
슬롯에 저장된다상태를 안전하게 변경하고 유지하기위해사용된다.
뭔말이더냐?
아마 전역적으로 사용하지 못하게, 막아버리는듯한데...예제로 알아보자.
let num = 0;
const increase = () => {
return ++num;
}
increase(); //1
increase(); //2
increase(); //3
이 함수의 문제점은 뭘까?
num
은 함수가 호출되기 전까지 변경되지 않아야 한다.increase
함수만이 변경시킬 수 있어야한다.이는 오류를 줄이고 통일성을 위한조건이다.
아무것도 지켜지지 않았다.
일단 전역변수를 지역변수로 바꿔본다.
const increase = () => {
let num = 0;
return ++num;
}
increase(); //1
increase(); //1
increase(); //1
아주 멍청한 생각이었다.
이전상태를 유지하지 못한다.
그러면, 이전상태도 유지하고 지역변수로 활용해보자
const increase = (function(){
let num = 0;
return function(){
return ++num;
}
}());
increase(); //1
increase(); //2
increase(); //3
이게 클로저의 핵심이다.
increase
메소드의 상위 스코프는 메서드가 평가되는 시점에 실행중인 실행 컨텍스트(즉시실행 함수)의 렉시컬 환경이다!
=> 지역변수num
을 참조할수 있는 이유가 이것 때문!
이를 생성자함수나 함수형 프로그래밍으로 나타낼수도 있다
// ----생성자 함수---- //
const Counter = (function(){
let num = 0;
function Counter(){
}
Counter.prototype.increase = function(){
return ++num;
}
return Counter;
}())
const counter = new Counter();
counter.increase(); //1
counter.increase(); //2
// ----함수형 ----- //
function makeCounter(aux){
let count = 0;
return function(){
count = aux(count);
return count;
}
}
//보조함수를 선언하여 고차함수로 넘겨준다
function increase(n){
return ++n;
}
function decrease(n){
return --n;
}
makeCounter(increase()); //1
makeCounter(increase()); //2
makeCounter(decrease()); //-1
//독립된 렉시컬 환경을 갖기에 변수가 공유되지 않음
변수를 공유하려면, 즉시실행함수로 감싸야한다
const counter = (function(){
let count = 0;
return function(aux){
count = aux(count);
return count;
}
}())
//보조함수를 선언하여 고차함수로 넘겨준다
function increase(n){
return ++n;
}
function decrease(n){
return --n;
}
counter(increase); //1
counter(increase); //2
counter(increase); //3
counter(decrease); //2
이는 전의 함수인 makeCounter
가 호출될때마다 렉시컬 환경이 새로 생성되었기 때문이다.
위처럼 즉시실행함수로 호출해버리고 끝나면, 렉시컬 환경이그대로다.
js는 es10(2019)이후 정보은닉을 위해 private기능을 지원하지만, 이왕 클로저 파트에 왔으니 이전에 쓰던 방법을 알아보자.
const Person = (function(){
let _age = 0; //private
function Person(name,age){
this.name = name;
_age = age;
}
Person.prototype.sayHi = function(){
console.log(`Hi my name is ${this.name}, I am ${_age}`)
}
return Person;
}());
const me = new Person('lee', 20);
me.sayHi() // Hi my name is lee, I am 20.
me.name // lee
me._age // undefined
하지만 문제가있다.
어딘갈 잘 보아라.
즉시실행함수로 감쌌기에, 새로운 인스턴스를 생성하여 age
를 넣어주면 _age
의 값이 계속 바뀐다(공유)
이전에는 잘 사용하지 못했구나?
클로저를 사용할때라기보단...var
를 사용할때 생기는 실수다.
아예 쓰지 않으면 좋겠지만, 일단 알아두기나 하자.
var funcs = [];
for(var i = 0; i<3; i++){
funcs[i] = function(){ return i; };
}
//블록레벨 스코프가 지원되지 않는 var = i 는 값이 3이되어버렸다. 따라서 3만 출력된다.
for(var j = 0; j<funcs.length; j++){
console.log(funcs[j]());
}
해결법은 역시 클로저와 즉시실행 함수다
var funcs = [];
for(var i = 0; i<3; i++){
funcs[i] = (function(id){
return function(){
return id
}
}(i))
}
for(var j = 0; j<funcs.length; j++){
console.log(funcs[j]());
}
그냥 const,let
을 사용하는게 마음 편하다.
let
키워드를 사용하면 블록스코프에서 새로운 렉시컬 환경을 생성하기에, 식별자 값 유지가 가능하다.
아니면 함수형 프로그래밍 기법중 하나인 고차함수를 사용하여도 좋다
const funcs = Array.from(new Array(3), (_,i) => () => i);
funcs.forEach(f=>console.log(f())) // 0 1 2
this
와 클로저에대해 정확히 알게되었다.
또한 객체끼리의 의존성을 낮춰야하는것도 깨달았다.
과제를 하며 컴포넌트끼리 왔다갔다하는 일이 빈번해졌는데...더 의존성을 낮춰봐야겠다.