자바스크립트 핵심 개념과 동작 원리[3] – 자바스크립트 this 키워드, 화살표 함수(Arrow Function), 원시 타입과 참조 타입

this 키워드

  • this keyword/variable: 모든 execution context마다(모든 함수마다) 생기는, “owner” of the function을 가리키는 특별 변수
    • owner of the function
  • NOT Static, 함수가 실제로 호출될 때 할당된다.
  • this의 예시
    • Method의 this
      • <Object that is calling the method>
    • Simple function call의 this
      • undefined(in strict mode, otherwise window(in the browser)
    • Arrow functions의 this
      • <this of surrounding function (lexical this)>
      • 자기 자신의 this를 갖지는 않는다.
    • Event listener의 this
      • <DOM element that the handler is attached to>
      • this를 브라우저에서 출력하면(console.log(this);) window가 출력된다.
    • new, call, apply, bind
      • later…
  • NOT point to the function itself
  • NOT the its variable environment

Method?

const jonas = {
  name: 'jonas',
  year: 1989,
  calcAge: function() {
    return 2037 - this.year
  }
};

jonas.calcAge()
  • 위 예시에서 calcAge가 method
const calcAge = function (birthYear) {
  console.log(2037 - birthYear);
  console.log(this);
}
calcAge(1991);

const calcAgeArrow = birthYear => {
  console.log(2037 - birthYear);
  console.log(this);
}
calcAgeArrow(1980);
  • 위 함수는 undefined, 아래 함수는 window가 출력된다.
const jonas = {
  year: 1991,
  calcAge: function () {
    console.log(this);
  }
}

jonas.calcAge();
  • jonas object가 출력된다.
const jonas = {
  year: 1991,
  calcAge: function () {
    console.log(this);
  }
};

jonas.calcAge(); // jonas

const matilda = {
  year: 2017,
};

matilda.calcAge = jonas.calcAge;
matilda.calcAge(); // matilda

const f = jonas.calcAge
f(); // undefined
  • this 키워드는 동적으로 할당 되는 것을 염두해두자.

일반 함수(Regular Function) vs. 화살표 함수(Arrow Function)

method로 화살표 함수를 사용할 경우 주의할 점

const jonas = {
  firstName: 'jonas',
  year: 1989,
  calcAge: function () {
    return 2037 - this.year
  },
  greet: () => console.log(`Hey ${this.firstName}`),
};

jonas.greet(); // Hey undefined
  • 화살표 함수(Arrow function)의 this는 부모의 this를 따르는데, 이 함수의 부모는 global scope이다. global scope의 this는 window이고, window에는 firstName이 정의되지 않았으므로 undefined가 출력된다.
    • 에러가 나지 않고 undefined가 출력되는 것 기억해놓기…🥹
  • 그래서 global scope에 var firstName을 선언해놓았으면 해당 변수의 값이 출력된다.
  • 이러한 상황을 막기 위해서 애초에 method에는 화살표 함수 쓰지 말고 function expression을 쓸 것…!

method 안에서 일반 함수를 쓸 때 주의할 점

const jonas = {
  firstName: 'jonas',
  year: 1989,
  calcAge: function () {
    console.log(this);
    console.log(2037 - this.year);
    const isMillenial = function () {
      console.log(this.year >= 1981 && this.year <= 1996);
    };
    isMillenial();
  },
  greet: () => console.log(`Hey ${this.firstName}`),
};

jonas.calcAge();
  • 이 상황에서는 TypeError가 나게 된다.
  • isMillenial은 일반 함수이기 때문에 thisundefined이고, undefined에는 year 프로퍼티가 없기 때문이다.
const jonas = {
  firstName: 'jonas',
  year: 1989,
  calcAge: function () {
    console.log(this);
    console.log(2037 - this.year);

    const self = this; // self or that
    const isMillenial = function () {
      console.log(self.year >= 1981 && self.year <= 1996);
    };
    isMillenial();
  },
  greet: () => console.log(`Hey ${this.firstName}`),
};
  • 위 상황의 경우 일반적으로 스코프 체인을 이용, jonas object를 가리키는 this를 새로운 변수(self)에 저장해서 사용했다.
  • 하지만 ES6에서는 화살표 함수를 사용해서 해결할 수 있다.
const jonas = {
  firstName: 'jonas',
  year: 1989,
  calcAge: function () {
    console.log(this);
    console.log(2037 - this.year);

    // const self = this; // self or that
    // const isMillenial = function () {
    //   console.log(self.year >= 1981 && self.year <= 1996);
    // };


    const isMillenial = () => {
      console.log(this); // jonas object
      console.log(self.year >= 1981 && self.year <= 1996);
    };
    isMillenial();
  },
  greet: () => console.log(`Hey ${this.firstName}`),
};
  • 화살표 함수의 this는 부모(jonas)의 this 를 가리키므로 jonas object이기 때문이다.

arguments

const addExpr = function (a, b) {
  console.log(arguments);
  return a + b;
}

addExpr(2, 5); // Arguments(2)
const v = addExpr(2, 5, 8, 12); // Arguments(4)
console.log(v); // 7
  • arguments에는 배열로 인자 값들이 들어가있다.
  • 정해진 인자 개수를 넘으면 에러가 나야할 것 같지만 잘 실행된다. 🥹
var addArrow = (a, b) => {
  console.log(arguments);
  return a + b;
}

addArrow(2, 5); // ReferenceError
  • 화살표 함수에는 arguments를 사용할 수 없다.

Primitives vs. Objects(Reference Types)

  • Primitives: Number, String, Boolean, Undefined, Null, Symbol, Bigint = 원시 타입
    • 콜 스택에 실제 데이터 값이 저장된다.
    • 같은 값을 가질 경우 같은 주소값을 갖는다.
    • 변수에 변수를 할당할 경우 같은 주소값을 갖는다.(당연)
  • Objects: object literal, Arrays, Functions 등… = 참조 타입
    • 에 실제 데이터 값이 저장된다.
    • 콜 스택 메모리 주소에 저장된 값은 힙의 메모리 주소이다.
    • 같은 값을 갖더라도 다른 주소에 저장된다.
    • 변수에 변수를 할당할 경우 같은 주소값을 갖는다.(당연)
let age = 30;
let oldAge = age;
age = 31;
console.log(age); // 31
console.log(oldAge); // 30

const me = {
  name: 'Jonas',
  age: 30,
};
const friend = me;
friend.age = 27;
console.log(me.age); // 27
console.log(friend.age); // 27
  • 원시 타입인 age는 값을 변경하면 새로운 주소에 새로운 값이 저장되고, 새로운 메모리 주소를 갖게 된다. oldAge는 아직 이전 메모리 주소에 저장된 값을 가리키고 있으므로 값이 그대로이다.
  • 참조 타입인 me는 값이 변경되면 주소는 그대로고 값만 바뀐다. friend는 me 주소의 값을 똑같이 바라보고 있으므로 바뀐 값이 출력된다.
  • const의 값이 immutable 하다는 것은 원시 타입일 때 해당되고, 참조 타입일 경우 데이터를 추가하거나 삭제하거나 하는 등의 변경이 가능하다.
    • 하지만 const에 새로운 객체를 할당하는 등의 변경은 불가능하다.(e.g. const me = {};는 TypeError 발생)

Object.assign()

const meCopy = Object.assign({}, me);
meCopy.age = 30
console.log(me.age); // 27
console.log(meCopy.age); // 30
  • 새로운 객체로 할당하기 위해서는 Object.assign() 함수를 사용하면 된다.
  • 하지만 중첩된 object의 경우 여전히 같은 메모리를 가리키고 있다. (=shallow copy)
  • deep copy를 하려면 Lo-dash 같은 외부 라이브러리를 사용하기도 한다. 이건 다음에 공부…
  • c.f. 파이썬에서도 list, set 등은 참조 변수로 작동하기 때문에 copy 혹은 deepcopy함수를 사용해야 하는 경우가 있다. (is, id()로 같은 객체를 가리키는 것을 확인할 수 있음)

Leave a Reply

Your email address will not be published. Required fields are marked *