화살표 기능 이해

hzc
풀어 주다: 2020-06-16 09:41:59
앞으로
2519명이 탐색했습니다.

나는 이미 화살표 기능을 아주 잘 이해했고 다시는 속지 않을 거라고 느꼈던 적이 있습니다. 그런데 며칠 전 아주 이상한 문제에 부딪혔습니다. 오랫동안 고민한 결과 화살표 기능으로 인한 함정이라는 것을 알게 되었습니다. 그래서 이런 글이 있군요~

문제 설명

예를 들어 기본 메서드인 sayName을 갖는 기본 클래스인 Animal이 있습니다. 이를 상속받는 각 후속 하위 클래스는 해당 ID를 증명하기 위해 sayName 메서드를 구현해야 합니다. 기본 클래스 코드 구현은 매우 간단합니다.

class Animal { sayName = () => { throw new Error('你应该自己实现这个方法'); } }
로그인 후 복사

이제 Animal 기본 클래스에서 상속하여 Pig 하위 클래스를 구현해야 합니다. 구현도 매우 간단합니다.

class Pig extends Animal { sayName() { console.log('I am a Pig'); } }
로그인 후 복사

이봐, 너무 간단해? 함정은 어디에 있습니까? 그러나 실제로 실행해 보면 결과가 예상과 다르다는 것을 알 수 있습니다.

화살표 기능 이해

야, 이게 왜 이래? 정확히 무엇이 잘못되었나요? 이 짧은 몇 줄의 코드가 오류를 보고하는 이유는 무엇입니까?

문제 발견

많은 고민 끝에 마침내 화살표 기능의 함정이라는 것을 알게 되었습니다. 이 문제를 해결하려면 Animal 기본 클래스의 sayName을 일반 함수로 변경하거나 Pig 하위 클래스의 sayName을 화살표 함수로 변경하기만 하면 됩니다. 그렇다면 화살표 기능에서는 정확히 무슨 일이 벌어지고 있는 걸까요?

이 글을 쓰면서 문득 한 번 이 질문에 대해 면접관에게 인터뷰를 했던 기억이 나네요! 당시 면접관은 클래스의 생성자에 있는 화살표 함수와 클래스의 일반 함수, 바인드 함수의 차이점에 대해 질문했습니다. 당시에는 대답이 명확하고 논리적이었지만 상속에 관해서는 모든 것이 뒤집어졌습니다. 그럼 위의 질문에 답하기 위해 먼저 면접 질문에 답해보겠습니다.

화살표 함수와 일반 함수, 생성자 내 바인드 함수의 차이점은 무엇인가요?

이 문제를 보다 직관적으로 확인하기 위해 Babel의 코드 컴파일 결과를 사용하면 차이점을 더 잘 확인할 수 있습니다.

먼저 간단한 코드를 입력해 보겠습니다

class A { constructor() { this.b = this.b.bind(this); } a() { console.log('a'); } b() { console.log('b') } c = () => { console.log('c') } }
로그인 후 복사

babel이 컴파일할 때 어떤 모습일지 살펴보겠습니다.

"use strict"; function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return !!right[Symbol.hasInstance](left); } else { return left instanceof right; } } function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var A = /*#__PURE__*/function () { function A() { _classCallCheck(this, A); _defineProperty(this, "c", function () { console.log('c'); }); this.b = this.b.bind(this); } _createClass(A, [{ key: "a", value: function a() { console.log('a'); } }, { key: "b", value: function b() { console.log('b'); } }]); return A; }();
로그인 후 복사

컴파일된 코드의 대부분은 보조 함수이므로 핵심 부분만 볼 수 있습니다.

var A = /*#__PURE__*/function () { function A() { _classCallCheck(this, A); _defineProperty(this, "c", function () { console.log('c'); }); this.b = this.b.bind(this); } _createClass(A, [{ key: "a", value: function a() { console.log('a'); } }, { key: "b", value: function b() { console.log('b'); } }]); return A; }();
로그인 후 복사

컴파일된 결과에서 이들 간의 차이점을 볼 수 있습니다.

  • 일반 함수: babel이 컴파일된 후 함수의 프로토타입에 배치됩니다.

  • 생성자의 바인드 함수: 컴파일 후, is는 함수의 프로토타입에 배치될 뿐만 아니라 인스턴스화될 때마다 현재 인스턴스 컨텍스트에 바인딩된 변수가 생성됩니다(this.b = this.b.bind(this)).

  • Arrow 함수: babel이 컴파일된 후 인스턴스화될 때마다 DefineProperty가 호출되어 화살표 함수 내용을 현재 인스턴스 컨텍스트에 바인딩합니다.

컴파일된 결과로 볼 때 실제 개발을 위해 컨텍스트 바인딩이 필요한 경우 화살표 기능을 사용하는 것이 가장 좋습니다. 바인드 메소드를 사용하면 프로토타입 함수뿐만 아니라 각 인스턴스화에 대한 추가 함수도 생성되기 때문입니다.

Update

Yu Tengjing의 댓글을 읽은 후 더 중요한 것을 배웠습니다.

class = 기호로 선언된 메서드와 변수는 인스턴스의 속성으로 사용되고, = 기호 없이 선언된 속성은 프로토타입 체인에 배치됩니다. 예를 들어

class A { a() { } b = 2; c = () => { } }
로그인 후 복사

이 클래스의 경우 인스턴스화되면 b와 c가 인스턴스의 속성으로 사용되고 a는 프로토타입 체인에 배치됩니다.

그렇다면 왜 이것이 달성되는 걸까요? 실제로 tc39 사양에서 이것이 언급되어 있음을 볼 수 있습니다: 필드 선언

등호 선언이 직접 작성된 인스턴스의 경우 실제로는 필드 선언의 구문이며 이러한 인스턴스 속성을 직접 선언하는 것과 동일합니다.

주제로 돌아가기

이전 질문을 해결한 후 다시 주제로 돌아가겠습니다. 실제 컴파일 조건에서 클래스의 화살표 함수의 컴파일 결과를 이해하고 나면 실제로 문제를 이해하는 것이 더 쉽습니다.

Q: 하위 클래스가 일반 함수를 사용하여 sayName을 선언하면 실행 문제가 발생하는 이유는 무엇입니까?

A: 서브클래스가 일반 함수를 사용하여 sayName을 선언하는 경우 서브클래스에서 선언한 sayName은 생성자의 프로토타입에 배치됩니다. 그러나 기본 클래스의 sayName은 화살표 함수를 사용하므로 각 인스턴스에는 직접 sayName 변수가 있습니다. 자바스크립트 변수의 접근 규칙에 따라 변수 자체를 먼저 검색하고, 찾을 수 없으면 프로토타입 체인에서 검색하게 됩니다. 따라서 sayName을 검색하면 기본 클래스에서 선언한 sayName 함수를 직접 찾게 되고, 프로토타입 체인에서는 찾지 않으므로 문제가 발생한다.

Q: 하위 클래스가 sayName을 선언하기 위해 화살표 함수를 사용하는 이유는 무엇입니까? 실행에는 문제가 없습니다.

A: es6 클래스가 초기화되면 먼저 기본 클래스의 생성자를 실행한 다음 자체 생성자를 실행합니다. 따라서 기본 클래스가 초기화된 후에는 하위 클래스에서 선언한 화살표 함수 sayName이 기본 클래스의 화살표 함수를 재정의하므로 실행에는 문제가 없습니다.

요약

한때 화살표 기능을 잘 안다고 생각했는데, 역시 배움에는 끝이 없군요! 그러나 나는 또한 클래스 내 화살표 기능에 대해 더 깊이 이해하고 있습니다.

그러나 댓글 영역의 모든 사람들에게 상기시켜 준 결과 실제로는 화살표 함수로 인해 문제가 발생하지 않는다는 것을 알았습니다. = 기호 클래스로 선언된 변수는 Field 선언의 구문에 속합니다. 이런 방식으로 선언된 변수의 경우 프로토타입 체인에 마운트되는 대신 인스턴스의 속성에 직접 마운트됩니다.

추천 튜토리얼: "JS Tutorial"

위 내용은 화살표 기능 이해의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
js
원천:juejin.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!