Translate

2020년 6월 27일 토요일

[Vue.js] methods 함수 안에서의 this에 대한 고찰 (this를 찾을 수 없는경우)






methods의 함수 내에서 this가 없어서 수행이 안되거나,
프로그램 실행은 되는데 개발자 도구에서 디버깅모드로 this를 참조하려고 할 시 this가 undefied로 나오는 경우가 있다.



Vue.js methods 함수내에서 this를 사용하기위해 주의할 점에 대해서 알아봤다.






이슈

1004lucifer
async 키워드가 들어간 함수에서 console.log 를 이용하여 this 객체를 로그로 남겼는데, 로그는 정상적으로 찍히지만 디버깅모드에서 this 객체가 'undefied' 로 나오는것 있었다.

실제로는 this 객체가 없지만 브라우저에서 버그가 있거나 문제없게 수정을 해주는걸까 싶어서 원인을 알아보기 시작했다.







소스

1004lucifer

export default {
  name: 'App',
  components: {
    HelloWorld
  },
  data: function() {
    return {
      testData1: 'testData1',
      testData2: 'testData2'
    }
  },
  created() {
    console.log('created');
  },
  mounted: function() {
    this.test1();
    this.test2();
    this.test3();
    this.test4();
    this.test5();
    this.test6();
    this.test7();
  },
  methods: {
    // 일반적인 함수 생성
    test1: function() {
      // eslint-disable-next-line no-debugger
      debugger;
      console.log('test1: ', this);
    },

    // 함수에 async 키워드 추가
    test2: async function() {
      // eslint-disable-next-line no-debugger
      debugger;
      console.log('test2: ', this);
    },

    // ECMAScript2015 - 단축 구문 함수 생성
    test3() {
      // eslint-disable-next-line no-debugger
      debugger;
      console.log('test3: ', this);
    },

    // ECMAScript2015 - 단축 구문 함수 생성 (async 키워드 추가)
    async test4() {
      // eslint-disable-next-line no-debugger
      debugger;
      console.log('test4: ', this);
    },

    // Arrow Function 지정
    test5: () => {
      // eslint-disable-next-line no-debugger
      debugger;
      console.log('test5: ', this);
    },


    callbackTest: function(obj) {
      // eslint-disable-next-line no-debugger
      debugger;
      obj.callback(obj.data);
    },
    test6: function() {
      this.callbackTest({
        data: this.testData1,
        // 콜백함수 정상적으로 생성
        callback: function(res) {
          // eslint-disable-next-line no-debugger
          debugger;
          console.log(res, this);
        }
      });
    },
    test7: function() {
      this.callbackTest({
        data: this.testData2,
        // 콜백함수 Arrow Function 으로 생성
        callback: (res) => {
          // eslint-disable-next-line no-debugger
          debugger;
          console.log(res, this);
        }
      });
    },
    test8: function() {
      // this 객체를 복사
      var context = this;
      this.callbackTest({
        data: this.testData2,
        callback: function(res) {
          // eslint-disable-next-line no-debugger
          debugger;
          console.log(res, context);
        }
      });
    },
  }
}




분석


1. Vue 프로젝트 빌드


프로젝트 빌드 시 아래와 같이 dist 디렉토리 하위에 빌드된 파일이 생성이 된다.
1004lucifer



2. 각 함수별 빌드파일과 비교




test1 함수

- 내용이 동일하다.
// ====== 원래 소스 ======
test1: function() {
  console.log('test1: ', this);
}

// ===== 빌드된 소스 =====
test1: function() {
	console.log("test1: ", this)
}



test2
1004lucifer
- async 키워드로 인해 기존의 내용이 wrapping 되었으며, 해당 객체에 this 객체를 똑같이 맞춰준 후 돌려준다.
그렇기 때문에 수행 시 Local 영역에 this 객체가 들어있다.
// ====== 원래 소스 ======
test2: async function() {
  console.log('test2: ', this);
}

// ===== 빌드된 소스 =====
test2: function() {
	var t = Object(a["a"])(regeneratorRuntime.mark((function t() {
		return regeneratorRuntime.wrap((function(t) {
			while (1)
				switch (t.prev = t.next) {
				case 0:
					console.log("test2: ", this);
				case 2:
				case "end":
					return t.stop()
				}
		}
		), t, this)
	}
	)));
	function e() {
		return t.apply(this, arguments)
	}
	return e
}()



test3

- 내용이 동일하다.
// ====== 원래 소스 ======
test3() {
  console.log('test3: ', this);
}

// ===== 빌드된 소스 =====
test3: function() {
	console.log("test3: ", this)
}



test4
1004lucifer
- this 객체를 t 라는 변수에 담아서 내부에서 t 객체를 사용하고 있다.
개발자도구에서는 Closure(test4) 에서 this를 사용하는 것으로 보여진다.
// ====== 원래 소스 ======
async test4() {
  console.log('test4: ', this);
}

// ===== 빌드된 소스 =====
test4: function() {
	var t = this;
	return Object(a["a"])(regeneratorRuntime.mark((function e() {
		return regeneratorRuntime.wrap((function(e) {
			while (1)
				switch (e.prev = e.next) {
				case 0:
					console.log("test4: ", t);
				case 2:
				case "end":
					return e.stop()
				}
		}), e)
	})))()
}



test5

- 빌드되면서 this 자리에 엉뚱한게 들어가 버렸다.

* Vue 공식홈페이지에서 화살표 함수를 사용 시 부모 컨텍스트를 바인딩한다고 사용하지 말라고 기술되어 있다.

* 여기서 상위객체의 h가 어떤 것인지 확인이 가능하다.
// ====== 원래 소스 ======
test5: () => {
  console.log('test5: ', this);
}

// ===== 빌드된 소스 =====
test5: function() {
	console.log("test5: ", h)
}



test6

- 내용이 동일하며, callback 함수에서 수행하는 this는 test6 함수의 this와 다른것을 확인 할 수 있다.
// ====== 원래 소스 ======
test6: function() {
  this.callbackTest({
	data: this.testData1,
	callback: function(res) {
	  console.log(res, this);
	}
  });
}

// ===== 빌드된 소스 =====
callbackTest: function(t) {
	t.callback(t.data)
},
test6: function() {
	this.callbackTest({
		data: this.testData1,
		callback: function(t) {
			console.log(t, this)
		}
	})
}



test7

- callback 함수에 화살표 함수를 사용했더니 부모 컨텍스트(test7)에 바인딩되어 this를 사용 할 수 있게 되었다.
// ====== 원래 소스 ======
test7: function() {
  this.callbackTest({
	data: this.testData2,
	callback: (res) => {
	  console.log(res, this);
	}
  });
}

// ===== 빌드된 소스 =====
callbackTest: function(t) {
	t.callback(t.data)
},
test7: function() {
	var t = this;
	this.callbackTest({
		data: this.testData2,
		callback: function(e) {
			console.log(e, t)
		}
	})
}



test8

- 화살표 함수를 사용하지 않고 test7과 같은 효과를 줄 수 있다.
// ====== 원래 소스 ======
test8: function() {
  var context = this;
  this.callbackTest({
	data: this.testData2,
	callback: function(res) {
	  console.log(res, context);
	}
  });
}

// ===== 빌드된 소스 =====
callbackTest: function(t) {
	t.callback(t.data)
},
test8: function () {
	var t = this;
	this.callbackTest({
		data: this.testData2, callback: function (e) {
			console.log(e, t)
		}
	})
}





결론


어떤 방식으로 함수를 작성하느냐에 따라 Vue객체의 this를 사용할수도 있고 사용하지 못할 수도 있다는걸 알게 되었다.

우선 Vue 공식홈페이지에 있는 예제 코드는 모두 function 키워드가 붙은 정석적인 방법으로 사용이 되어있었다.

Babel이 트랜스컴파일링 하게될 때 상황에 따라 의도치 않게 코드가 나와버려서 Vue 공식홈페이지에 있는 방식대로 코딩을 권장하고 싶긴하지만, 많은 IDE 툴에서 ES6 단축 함수 형식으로 자동완성을 해주고 있기에 그렇게 하기도 좀 애매하기도 하다.

주위 개발자에겐 this 문제가 발생한경우 function 키워드를 넣는 방식으로 코드를 수정해 보라고 가이드를 할 예정이다.




PS.
얼핏 알고있는 내용을 정리하면서 확실하게 개념을 확립한것도 그렇고, 크롬 개발자도구의 Scope 항목에 Closure가 나오는 의미에 대해서 알게될 수 있어서 좋은 포스팅이 되었다.



익명함수 및 클로저 개념


댓글 2개 :

  1. ES6의 Arrow Function은 단순히 function() 구문의 줄임뿐만 아니라 scope도 바꾼다는걸 꼭 기억하고 있어야함... ㅎㅎ

    답글삭제
    답글
    1. 그렇죠.. 이걸 모르면 한참을 삽질할수도 있어서 정말 중요한 개념이죠..ㅎ

      삭제