흔히들 메서드는 객체안에 존재하는 함수? 같은걸로 생각을 한다.
뭐 기능면에서는 동작이야 다 잘되겠지만..
일단 아래코드를 보자.
const obj = {
화살표함수: () => {
console.log("화살표함수");
},
메서드() {
console.log("메서드정의방식 메서드");
},
표현식: function () {
console.log("표현식");
},
};
객체 obj에 저렇게 프로퍼티? 메서드를 써봤다.
일단 코드에서 봐서 알겠지만 저기서 메서드라고 적힌 함수?만 메서드다
일단 다 호출해보면 모두 제대로 호출된다.
const obj = {
화살표함수: () => {
console.log("화살표함수");
},
메서드() {
console.log("메서드정의방식 메서드");
},
표현식: function () {
console.log("표현식");
},
};
obj.화살표함수(); //화살표함수 출력
obj.메서드(); // 메서드정의방식 메서드 출력
obj.표현식(); // 표현식 출력
# 1 그렇다면 차이점은 뭘까? 화살표함수, 표현식 vs 메서드
위에 코드만 봐도 알 수 있듯이, 객체리터럴 안에서 프로퍼티키 : 프로퍼티값 으로 썼냐 안썻냐 차이가 있다.
객체 내부에는 프로퍼티냐, 메서드냐 두가지가 있다고 하였는데, 아래코드를 보면 확인을 할 수 있다.
console.log(Object.getOwnPropertyDescriptor(obj, "메서드"));
console.log(Object.getOwnPropertyDescriptor(obj, "화살표함수"));
console.log(Object.getOwnPropertyDescriptor(obj, "표현식"));
첫번째 '메서드'는 value에 메서드라는 이름을 가진 함수가 들어있다고 한다.
두번째 화살표함수는 화살표함수 리터럴이 들어가 있다.
세번째 표현식에는 익명함수객체가 들어가있다.
노드 환경에서는 다 똑같이 뜬다. 이건 브라우저환경에서 뜨는거다.
console.dir로 치면 이걸 알 수 없는데, 이유는 프로퍼티값에 있는 함수객체의 name값에 프로퍼티키(식별자)를 암묵적으로 할당해준 후에 출력해주는거라 그렇다.. 저 getOwnPropertyDescriptor로 하면 암묵적으로 함수객체에 name에 함수이름을 할당해주지 않는게 떠서 썻다..
근데 메서드가 getOwnProperty~라고 프로퍼티 분석해줘 라는 이름인데 메서드도 뜨는건 이상한거아닌가.. 암튼
뭐 사실 중요한건 아닌데,
저 obj객체리터럴내에서 메서드는 오로직 메서드정의방식 메서드(){~~}으로 정의한 메서드만 메서드로 인정한다는것이다.
나머지 화살표함수, 표현식은 '프로퍼티 키'일 뿐이고, 각 프로퍼티 값으로는 함수리터럴이 들어가 있는것이다.
사실 호이스팅여부가 가장 정확할것같긴한데 객체안에 메서드만 호이스팅이 되는건지 안되는건지는 모르겠다.
코드도 객체안의 프로퍼티키로 함수리터럴이나 메서드를 접근하는거라 애초에 함수 접근하기전 obj에 접근할때부터 obj가 undefined라서 테스트가 안되더라. 암튼 중요한건 아니다.
# 2 그렇다면 차이점은 뭘까? 화살표함수, 메서드 vs 표현식
console.dir(obj.메서드);
console.dir(obj.화살표함수);
console.dir(obj.표현식);
저기서 눈빠지게 찾아보면 다 똑같이 생겼는데 prototype이라는 프로퍼티가 표현식에만 있다!
나머지 화살표함수와 메서드에는 prototype이라는 프로퍼티가 없다!
(내부슬롯 [[Prototype]]은 아냐. 이건 함수도 객체라서 얘엄마의 prototype을 알려달라는거임. Function.prototype이곘지.)
https://jacobowl.tistory.com/181
생성자 함수, prototype, __proto__, constructor 관계 구조.
# 기본 코드 const Dog = function (name) { this.name = name; }; const dog1 = new Dog("무찌"); Dog라는 생성자 함수와 이 생성자 함수로 새로운 객체를 만들어, 식별자 dog1에 할당했다. 기본적인 생성자함수의 문법
jacobowl.tistory.com
요 포스팅에서 보면, prototype이라는 프로퍼티를 가진건 constructor일 뿐이다.
이 의미는 저 함수로 객체를 생성할수 있냐 없냐 차이다. (생성자 함수냐).
생성자함수로써 객체를 생성하는 코드를 쳐보면
new obj.메서드(); //타입에러~! 콘스트럭터가 아니래
new obj.화살표함수(); //타입에러~! 콘스트럭터가 아니래
new obj.표현식(); //얘만 에러없음.
함수리터럴을 값으로가진 표현식프로퍼티만 생성자함수로써 객체를 만들 수 있다.
++
const obj = {
화살표함수: () => {
console.log("화살표함수");
},
메서드() {
console.log("메서드정의방식 메서드");
},
표현식: function () {
console.log("표현식");
},
클래스: class {
constructor() {}
},
};
요렇게 클래스라는 프로퍼티키에다가 클래스 리터럴을 갖다넣을 수도 있다.
따라서 객체리터럴에 객체를 만드는 함수로는 화살표함수나, 메서드로 만들 수가 없다.
메서드와 화살표함수는 constructor가 아니다.
# 3 그렇다면 차이점은 뭘까? 화살표함수 vs 메서드, 표현식
코드를 살짝 바꿔보자~
const obj = {
화살표함수: () => {
console.log("화살표함수의 this : ", this);
},
메서드() {
console.log("메서드정의방식 메서드의 this : ", this);
},
표현식: function () {
console.log("표현식의 this : ", this);
},
};
각각에서 this를 참조해보았다.
this 바인딩은 어떻게 될까?
짜잔~! 화살표함수만 전역객체인 window객체가 뜬다.
표현식과 메서드는 자신을 호출한 객체인 obj객체를 출력한다.
단 화살표함수리터럴에서 this가 무조껀 전역객체를 가르키는건 아니다. 아래에 더 쓸거임.
# 추가적으로 화살표함수에서 this 바인딩
일단 화살표함수에서의 this가 무조껀 전역객체가 아니라는 반례를 들어보자면,
const obj = {
화살표함수: () => {
console.log("화살표함수의 this : ", this);
},
메서드() {
console.log("메서드정의방식 메서드의 this : ", this);
},
표현식: function () {
console.log("표현식의 this : ", this);
const 중첩함수 = () => {
console.log("표현식 안에 화살표함수인 중첩함수에서 this : ", this);
};
중첩함수();
},
};
표현식 함수리터럴 안에서 화살표함수인 중첩함수를 만들어서 호출해봤다.
결과적으론?
화살표함수이지만 이번엔 obj객체를 출력하는걸 볼 수 있다.
메서드에서도 똑같이 중첩함수를 만들어보면,
const obj = {
화살표함수: () => {
console.log("화살표함수의 this : ", this);
},
메서드() {
console.log("메서드정의방식 메서드의 this : ", this);
const 중첩함수 = () => {
console.log("메서드 안에 화살표함수인 중첩함수에서 this : ", this);
};
중첩함수();
},
표현식: function () {
console.log("표현식의 this : ", this);
},
};
얘도 똑같이 obj를 출력한다.
때문에 화살표함수에서 this는 스코프체인을 따라서, 상위스코프로 올라가면서 this를 찾다가 스코프체인 최상단까지 갈때까지 못찾으면 전역객체를 바인딩하는거다.
예시로 중첩함수안에서 중중첩함수를 만들어보면,
const obj = {
화살표함수: () => {
console.log("화살표함수의 this : ", this);
},
메서드() {
console.log("메서드정의방식 메서드의 this : ", this);
},
표현식: function () {
console.log("표현식의 this : ", this);
const 중첩함수 = () => {
console.log("표현식 안에 화살표함수인 중첩함수에서 this : ", this);
const 중중첩함수 = () => {
console.log(
"표현식 안에 화살표함수인 중첩함수안에 중중첩함수에서의 this : ",
this
);
};
중중첩함수();
};
중첩함수();
},
};
중중첩함수에서 this를 참조하면 상위함수인 중첩함수에서 또 this를 찾고 없으니까 표현식함수에서 this를 찾는건지,
표현식에서 this바인딩이 끝나고 중첩함수에서 this바인딩이 끝난 상태에서 중중첩함수의 this바인딩이 상위스코프인 중첩함수의 this(obj)를 찾는건지는 모를수도 있는데 이건 실행컨텍스트를 알면 된다.
정답은 후자다. 스택단계에서 this바인딩이 되어있는거라 그렇다.
즉 표현식 함수가 '호출'이 될때 표현식 함수 실행컨텍스트가 생성이 되고 표현식 내부의 this에 obj객체가 바인딩된다
그리고 표현식 함수 내부에 중첩함수가 정의되는 시점에 중첩함수 내부의 this에는 스코프체인으로 표현식함수의 this인 obj객체를 바인딩한다.
그리고 이 중첩함수를 호출할때, 스택이 생겨나고 중첩함수의 실행컨텍스트가 생성이 되며, 아까 정의시점에서 정해진 this를 갖다 쓴다.
호출하면 또다시 안에있는 중중첩함수의 정의코드가 동작하며 이때 this에 스코프체인으로 현재 중첩함수에 바인딩된 this를 갖다 중중첩함수의 this에 바인딩한다.
그리고 중중첩함수가 호출이 되었을때 스택이 생겨나고 중중첩함수의 실행컨텍스트가 생성되며, 아까 바인딩한 this를 갖다 쓰는거다.
뭐 시발 존나 복잡하다.
++ 화살표함수는 정의시점에서 this를 바인딩한다 (정적바인딩)
const 화살표함수 = () => {
console.log("나는 화살표함수에서 this야~", this);
};
const 표현식 = function () {
console.log("나는 표현식에서 this야~", this);
};
const testObj = {
화살표함수,
표현식,
메서드() {
const 중첩함수 = 화살표함수;
console.log("중첩함수에 화살표함수를 넣어서 호출하면?");
중첩함수();
},
};
화살표함수(); //전역에서 화살표함수를 호출하면? -> 전역객체가 바인딩됨
표현식(); //전역에서 표현식으로 정의된 함수를 호출하면? -> 전역객체가 바인딩됨
testObj.화살표함수(); //객체안에 있는 화살표함수를 호출하면? -> 전역객체 바인딩됨
testObj.표현식(); //객체안에있는 표현식으로 정의된 함수를 호출하면? -> 상위스코프 obj객체를 출력함.
testObj.메서드(); //화살표함수 리터럴을 메서드안에 넣고 메서드내부에서 호출하면? -> 전역객체 바인딩
함수를 호출할때 this바인딩은 호출시점에 결정이 된다.
때문에 표현식으로 정의된 함수를 전역에서 호출하면 this에 전역객체가 바인딩되는것이고,
객체리터럴안에 프로퍼티로 똑같은 함수객체를 집어넣고 객체리터럴안에서 함수로 호출을 하면 testObj객체가 this에 바인딩 된다.
위에 중첩함수 코드랑 똑같이 testObj객체내에 프로퍼티로 넣었기 때문에 호출시점에 스코프체인으로 탐색하는거 아냐? 생각할수도 있다, 하지만 반례로 메서드안에 중첩함수라는 변수에다가 저 화살표함수리터럴을 넣어서 호출해도 똑같이 전역객체가 this에 바인딩된다. 만약 위에 obj객체를 쓴 코드에서 메서드안에 화살표함수를 정의, 호출헀다면 this에 obj객체가 출력되듯 동작해야할것이다.
즉 화살표함수에서의 this바인딩은 정의시점에서 this가 바인딩되기 때문에, 처음 함수를 정의할때, 이미 전역객체가 this로 바인딩되어있기때문에 객체 안에 넣어서 호출해도 바인딩된 this는 바뀌지 않는다.
그렇다면 위에 obj객체 리터럴 쓴 코드에서, 표현식 안에 있는 함수안에있는 화살표함수인 중첩함수들은 정의시점에 this가 바인딩 안된거아냐? 라고 생각해볼수도 있겠지만, 실행컨텍스트를 배우면, 스택이 바뀔때 (표현식 함수가 호출될때) 내부의 화살표함수인 중첩함수들이 정의되기때문에, 이 정의시점에 this를 바인딩하는거다. 머시발 복잡하다. 암튼 이건 나중에 더 쓸 예정이니까.. 대충 이정도로 써놓겠다.
# 최종정리~
객체리터럴에서 화살표함수 리터럴 |
객체리터럴에서 표현식 리터럴 |
객체리터럴에서 메서드 |
|
프로퍼티인가? | 넹! | 넹! | 아뇨! |
constructor인가? | 아뇨! | 넹! | 아뇨! |
this바인딩은? | 전역객체가 바인딩~ | 호출한 객체 obj가 바인딩 | 호출한객체 obj가 바인딩 |
'javaScript > jsDeepDive' 카테고리의 다른 글
getter, setter 프로퍼티 (0) | 2023.06.23 |
---|---|
객체를 리턴하는 함수 vs 생성자함수 vs 클래스 (0) | 2023.06.23 |
생성자 함수, prototype, __proto__, constructor 관계 구조. (0) | 2023.06.16 |
객체를 커스텀해보자. 프로퍼티어트리뷰트 (플래그) (0) | 2023.06.12 |
즉시실행함수 (0) | 2023.06.01 |