Javascript에서 함수의 신비를 해결할 수 없습니다


16

나는 자바 스크립트의 커튼 장면을 이해하려고 노력하고 있으며 내장 객체, 특히 객체기능객체 간의 관계 를 이해하는 데 집중 하고 있습니다.

Array, String 등과 같은 모든 내장 객체가 Object에서 확장 (상 속됨)이라는 것을 읽을 때 Object는 객체가 생성되고 나머지 객체가 상속되는 첫 번째 내장 객체라고 가정했습니다. 그러나 객체는 함수로만 만들 수 있지만 함수는 함수의 객체 일 뿐이라는 것을 알면 의미가 없습니다. 암탉과 닭의 딜레마처럼 들리기 시작했습니다.

다른 매우 혼란스러운 것은 console.log(Function.prototype)기능을 인쇄 console.log(Object.prototype)하지만 인쇄하면 객체를 인쇄한다는 것입니다. Function.prototype함수가 목적인 이유는 무엇 입니까?

또한 Mozilla 문서에 따르면 모든 자바 스크립트 functionFunction객체의 확장 이지만 console.log(Function.prototype.constructor)다시 함수 일 때입니다. 이제 어떻게 무언가를 사용하여 스스로 만들 수 있습니까 (마음 = 날려).

마지막 Function.prototype으로 함수이지만 constructor함수를 사용하여 액세스 할 수 있습니다. Function.prototype.constructorFunction.prototype, prototype객체 를 반환하는 함수입니다.


함수는 객체이므로 함수가 Function.prototype될 수 있고 내부 필드를 가질 수 있습니다. 따라서 프로토 타입 함수가 구조를 통과 할 때 실행하지 않습니다. 마지막으로 객체와 기능이 아마 엔진 내에서가 아니라 자바 스크립트에서 특별한 참조처럼 생성되도록, 자바 스크립트를 해석 엔진이 있음을 기억 Function.prototype하고 Object.prototype바로 엔진에 의해 특별한 방법으로 해석 될 수 있습니다.
Walfrat

1
표준 호환 JavaScript 컴파일러를 구현하려는 경우가 아니라면 실제로 이러한 사항에 대해 걱정할 필요가 없습니다. 유용한 일을하고자한다면 당연한 일입니다.
Jared Smith

5
참고로 "암탉과 닭의 딜레마"에 대한 영어의 일반적인 문구는 "닭과 계란 문제", 즉 "치킨이나 계란이 먼저 왔습니까?"입니다. (물론 답은 계란입니다. 황달 동물은 닭보다 수백만 년 전이었습니다.)
Eric Lippert

답변:


32

나는 자바 스크립트의 커튼 장면을 이해하려고 노력하고 있으며 내장 객체, 특히 객체 및 기능과 객체 간의 관계를 이해하는 데 집중하고 있습니다.

복잡하고, 오해하기 쉬우 며, 많은 초보 자바 스크립트 책이 잘못 이해하므로 읽은 모든 것을 신뢰하지 마십시오.

저는 1990 년대와 표준화위원회에서 Microsoft JS 엔진을 구현 한 사람 중 한 사람이었으며이 답변을 구성하는 데 많은 실수를했습니다. (15 년 이상이 일을 해본 적이 없어서 용서받을 수 있습니다.) 까다로운 일입니다. 그러나 프로토 타입 상속을 이해하면 모든 것이 의미가 있습니다.

Array, String 등과 같은 모든 내장 객체가 Object에서 확장 (상 속됨)이라는 것을 읽을 때 Object는 객체가 생성되고 나머지 객체가 상속되는 첫 번째 내장 객체라고 가정했습니다.

클래스 기반 상속에 대해 알고있는 모든 것을 버리십시오. JS는 프로토 타입 기반 상속을 사용합니다.

다음으로, "상속"이 의미하는 바를 머리 속에 아주 명확하게 정의하십시오. C #, Java 또는 C ++와 같은 OO 언어에 익숙한 사람들은 상속이 하위 유형을 의미한다고 생각하지만 상속이 하위 유형을 의미하는 것은 아닙니다. 상속은 한 가지 일원이 다른 일의 일원임을 의미합니다 . 반드시 그런 것들 사이에 하위 유형 관계가 있다는 것을 의미 하지는 않습니다 ! 유형 이론에 대한 많은 오해는 사람들이 차이가 있다는 것을 깨닫지 못한 결과입니다.

그러나 객체는 함수로만 만들 수 있지만 함수는 함수의 객체 일 뿐이라는 것을 알면 의미가 없습니다.

이것은 단순히 거짓입니다. 일부 함수는 일부 함수 를 호출 하여 작성 되지 않습니다 . 일부 객체는 JS 런타임에 의해 전혀 생성되지 않습니다. 닭이 낳지 않은 알이있다 . 시작시 런타임에 의해 생성되었습니다.new FF

규칙이 무엇인지, 어쩌면 도움이 될지 말해 봅시다.

  • 모든 객체 인스턴스에는 프로토 타입 객체가 있습니다.
  • 경우에 따라 해당 프로토 타입이 될 수 있습니다 null.
  • 객체 인스턴스의 멤버에 액세스하고 객체에 해당 멤버가없는 경우 객체는 프로토 타입을 연기하거나 프로토 타입이 null 인 경우 중지됩니다.
  • prototype객체 의 멤버는 일반적으로 객체 의 프로토 타입이 아닙니다 .
  • 오히려 prototype함수 객체 F 의 멤버는로 만든 객체의 프로토 타입이 될 객체입니다 new F().
  • 일부 구현에서 인스턴스 __proto__는 실제로 프로토 타입을 제공 하는 멤버를 얻습니다 . (이제 더 이상 사용되지 않습니다. 의존하지 마십시오.)
  • 함수 객체는 새로운 기본 객체를 prototype만들 때 할당됩니다 .
  • 함수 객체의 프로토 타입은 물론 Function.prototype입니다.

요약하자.

  • 의 프로토 타입 ObjectFunction.prototype
  • Object.prototype 객체 프로토 타입 객체입니다.
  • 의 프로토 타입 Object.prototypenull
  • 의 프로토 타입 FunctionIS Function.prototype- 이것은 드문 경우 중 하나입니다 Function.prototype사실의 프로토 타입입니다 Function!
  • Function.prototype 함수 프로토 타입 객체입니다.
  • 의 프로토 타입 Function.prototypeObject.prototype

함수 Foo를 만든다고 가정 해 봅시다.

  • 의 프로토 타입은 Foo입니다 Function.prototype.
  • Foo.prototype Foo 프로토 타입 객체입니다.
  • 의 프로토 타입은 Foo.prototype입니다 Object.prototype.

우리가 말한다고 가정 해 봅시다 new Foo()

  • 새로운 객체의 프로토 타입은 Foo.prototype

이해가 되십시오. 그려 봅시다. 타원은 객체 인스턴스입니다. 가장자리는 __proto__"의 프로토 타입"또는 prototype"의 prototype속성 "을 의미합니다 .

여기에 이미지 설명을 입력하십시오

런타임은 모든 객체를 생성하고 그에 따라 다양한 속성을 할당하기 만하면됩니다. 어떻게되는지 알 수있을 것입니다.

이제 지식을 테스트하는 예제를 보자.

function Car(){ }
var honda = new Car();
print(honda instanceof Car);
print(honda.constructor == Car);

이것은 무엇을 인쇄합니까?

무엇을 instanceof의미합니까? honda instanceof Car" 의 프로토 타입 체인 Car.prototype에있는 객체와 동일 honda합니까?"

그렇습니다. honda의 프로토 타입 Car.prototype이 완성되었습니다. 이것은 사실을 인쇄합니다.

두 번째는 어떻습니까?

honda.constructor존재하지 않으므로 프로토 타입을 참조하십시오 Car.prototype. Car.prototype객체가 생성 되면 에 자동으로 constructor동일한 속성이 부여 Car되었으므로 이는 사실입니다.

이제 이건 어때?

var Animal = new Object();
function Reptile(){ }
Reptile.prototype = Animal;
var lizard = new Reptile();
print(lizard instanceof Reptile);
print(lizard.constructor == Reptile);

이 프로그램은 무엇을 인쇄합니까?

다시 말하지만, lizard instanceof Reptile" 의 프로토 타입 체인 Reptile.prototype에있는 객체와 동일 lizard합니까?"

그렇습니다. lizard의 프로토 타입 Reptile.prototype이 완성되었습니다. 이것은 사실을 인쇄합니다.

이제는 어떻습니까

print(lizard.constructor == Reptile);

lizard으로 구성 new Reptile되었지만 잘못 되었을 수도 있으므로 이것이 사실이라고 생각할 수도 있습니다 . 그것을 추론하십시오.

  • 않습니다 lizardconstructor속성을? 따라서 프로토 타입을 살펴 봅니다.
  • 의 프로토 타입은 lizard이다 Reptile.prototypeAnimal.
  • 않습니다 Animalconstructor속성을? 아닙니다. 그래서 우리는 프로토 타입을 봅니다.
  • 의 프로토 타입 Animal이며 Object.prototype, 그리고 Object.prototype.constructor런타임에 의해 만들어 같다 Object.
  • 따라서 이것은 거짓을 인쇄합니다.

우리는 Reptile.prototype.constructor = Reptile;어느 시점에서 말해야 했지만 기억하지 못했습니다!

모든 것이 당신에게 의미가 있는지 확인하십시오. 여전히 혼란 스러우면 일부 상자와 화살표를 그립니다.

다른 매우 혼란스러운 것은 console.log(Function.prototype)기능을 인쇄 console.log(Object.prototype)하지만 인쇄하면 객체를 인쇄한다는 것입니다. Function.prototype함수가 목적인 이유는 무엇 입니까?

함수 프로토 타입은 호출 될 때를 반환하는 함수로 정의됩니다 undefined. 우리는 이미 알고 Function.prototype는 IS Function이상하게도, 프로토 타입. 그러므로 Function.prototype()합법적이며, 그렇게하면 undefined다시 돌아옵니다. 기능입니다.

Object프로토 타입이 속성이 없습니다; 호출 할 수 없습니다. 그것은 단지 객체입니다.

당신 console.log(Function.prototype.constructor)은 다시 함수입니다.

Function.prototype.constructor그냥 Function분명히. 그리고 Function기능입니다.

이제 어떻게 무언가를 사용하여 스스로 만들 수 있습니까 (마음 = 날려).

당신은 이것을 너무 많이 생각하고 있습니다. 필요한 것은 런타임이 시작될 때 많은 객체를 생성한다는 것입니다. 객체는 문자열을 객체와 연결하는 조회 테이블입니다. 런타임이 시작되면이 할 수있는 모든이 할당 시작 후 수십 빈 객체를 생성하고, 인 prototype, __proto__, constructor, 그래서 그들이해야하는 그래프를 만들 때까지 각 개체의 속성에.

위에서 설명한 다이어그램을 가져 와서 constructor가장자리를 추가하면 도움이됩니다 . 이 그래프는 매우 간단한 객체 그래프이며 런타임에 생성시 아무런 문제가 없음을 곧 알 수 있습니다.

좋은 운동은 스스로하는 것입니다. 여기부터 시작하겠습니다. 우리는 사용합니다 my__proto__의미 "의 프로토 타입 객체"하고 myprototype"의 프로토 타입 속성"을 의미 할 수 있습니다.

var myobjectprototype = new Object();
var myfunctionprototype = new Object();
myfunctionprototype.my__proto__ = myobjectprototype;
var myobject = new Object();
myobject.myprototype = myobjectprototype;

등등. "실제"Javascript 내장 오브젝트와 동일한 토폴로지를 갖는 오브젝트 세트를 구성하기 위해 나머지 프로그램을 채울 수 있습니까? 그렇게하면 매우 쉽다는 것을 알게 될 것입니다.

JavaScript의 객체는 문자열을 다른 객체와 연결하는 조회 테이블 일뿐 입니다. 그게 다야! 여기 마법이 없습니다. 모든 객체가 생성자에 의해 생성되어야하는 것처럼 실제로 존재하지 않는 구속 조건을 상상하기 때문에 매듭에 묶여 있습니다.

함수는 추가 기능이있는 객체 일뿐입니다. 따라서 작은 시뮬레이션 프로그램을 살펴보고 .mycallable호출 가능한지 여부를 나타내는 속성을 모든 객체에 추가하십시오 . 그렇게 간단합니다.


9
마지막으로, JavaScript에 대한 짧고 간결하며 이해하기 쉬운 설명! 우수한! 우리는 어떻게 혼동 될 수 있습니까? :) 모든 진지함에있어, 룩업 테이블 인 객체에 대한 마지막 비트가 실제로 핵심입니다. 광기에 대한 방법이 있지만 --- 그것은 여전히 광기입니다 ...
Greg Burghardt

4
@ GregBurghardt : 처음에는 복잡해 보이지만 단순한 규칙의 결과로 복잡성이 발생합니다. 모든 객체에는가 있습니다 __proto__. __proto__객체의 프로토 타입은 null입니다. __proto__의가 new X()있다 X.prototype. 모든 함수 객체에는 __proto__함수 프로토 타입 자체 를 제외하고 함수 프로토 타입이 있습니다. Object그리고 Function함수 프로토 타입은 함수입니다. 이러한 규칙은 모두 간단하며 초기 객체 그래프의 토폴로지를 결정합니다.
Eric Lippert

6

당신은 이미 훌륭한 답변을 많이 가지고 있지만, 나는이 모든 것이 어떻게 작동하는지에 대한 당신의 대답에 짧고 명확한 대답을하고 싶습니다. 그 대답은 다음과 같습니다.

마법!!!

정말 그렇습니다.

ECMAScript를 실행 엔진을 구현하는 사람들은 필요가 구현 인 ECMAScript의 규칙을하지만 준수 그들에 의해 내에서 구현.

ECMAScript 사양에 따르면 A는 B에서 상속되지만 B는 A의 인스턴스입니까? 문제 없어요! 의 프로토 타입 포인터로 A를 먼저 작성하고 A NULL의 인스턴스로 B를 작성한 다음 A의 프로토 타입 포인터를 수정하여 나중에 B를 가리 키십시오. 쉬워요.

ECMAScript에서 프로토 타입 포인터를 변경할 수있는 방법이 없습니다. 그러나, 여기에 일이 :이 코드가 실행되지 에서 ECMA 스크립트 엔진이 코드는 ECMA 스크립트 엔진. 그것은 않습니다 엔진에 ECMAScript를 코드 실행은하지 않는 객체의 내부에 액세스 할 수 있습니다. 한마디로 : 원하는대로 할 수 있습니다.

그건 그렇고, 당신이 정말로 원한다면, 당신은 이것을 한 번만하면됩니다 : 나중에, 당신은 예를 들어 ECMAScript 엔진을 시작할 때마다 내부 메모리를 덤프 하고이 덤프를로드 할 수 있습니다.

ECMAScript 엔진 자체가 ECMAScript로 작성된 경우에도이 모든 사항이 여전히 적용됩니다 (예 : Mozilla Narcissus의 경우와 동일). 그렇다하더라도, 그 ECMAScript를 코드 구현 엔진이 여전히되어 엔진에 대한 전체 액세스 권한이 구현을 은 물론이되어 엔진에 액세스하지 않지만, 실행중인를 .


3

ECMA 사양 1에서

ECMAScript에는 C ++, Smalltalk 또는 Java와 같은 적절한 클래스가 포함되어 있지 않지만, 객체에 대한 스토리지를 할당하고 속성에 초기 값을 할당하여 객체의 전체 또는 일부를 초기화하는 코드를 실행하여 객체를 생성하는 생성자를 지원합니다. 생성자를 포함한 모든 함수는 객체이지만 모든 객체가 생성자는 아닙니다.

나는 그것이 더 이상 어떻게 될 수 있는지 알지 못한다! </sarcasm>

더 아래로 우리는 참조하십시오 :

프로토 타입 프로토 타입은 ECMAScript에서 구조, 상태 및 동작 상속을 구현하는 데 사용되는 객체입니다. 생성자가 객체를 만들면 해당 객체는 속성 참조를 확인하기 위해 생성자의 관련 프로토 타입을 암시 적으로 참조합니다. 생성자의 관련 프로토 타입은 프로그램 표현식 constructor.prototype으로 참조 할 수 있으며, 프로토 타입을 공유하는 모든 객체는 상속을 통해 객체의 프로토 타입에 추가 된 속성을 공유합니다.

따라서 프로토 타입은 객체이지만 함수 객체 일 필요는 없습니다.

또한, 우리는이 흥미로운 가슴을 가지고 있습니다

http://www.ecma-international.org/ecma-262/8.0/index.html#sec-object-objects

개체 생성자는 % Object % 내장 개체이며 전역 개체의 Object 속성 초기 값입니다.

함수 생성자는 % Function % 내장 객체이며 전역 객체의 Function 속성 초기 값입니다.


이제 그렇습니다. ECMA6을 사용하면 클래스를 만들고 클래스에서 객체를 인스턴스화 할 수 있습니다.
ncmathsadist

2
@ncmathsadist ES6 클래스는 단지 구문 설탕이며 의미론은 동일합니다.
Hamza Fatmi

1
sarcasm그렇지 않으면, 당신의 이름은이 텍스트가 초보자에게는 정말 불투명합니다.
Robert Harvey

사실, 나중에 더 추가, 파고해야합니다
Ewan

1
음? 문서에서 명확하지 않다는 것을 지적하기 위해
Ewan

1

다음 유형은 JavaScript의 모든 값을 포함합니다.

  • boolean
  • number
  • undefined(단일 값 포함 undefined)
  • string
  • symbol (참조로 비교되는 고유 한 "사물")
  • object

JavaScript의 모든 객체 (즉, 모든 것)에는 일종의 객체 인 프로토 타입이 있습니다.

프로토 타입에는 함수도 포함되는데,이 함수는 일종의 객체이기도합니다 1 .

객체는 또한 생성자 (함수)이며 일종의 객체입니다.

중첩

재귀 적이지만 JavaScript 코드와 달리 JavaScript 함수를 호출하지 않고도 객체를 만들 수 있기 때문에 구현이 자동으로 수행 할 수 있습니다 (객체는 구현이 제어하는 ​​메모리 일 뿐이므로).

많은 동적 유형 언어의 대부분의 객체 시스템은 이와 같이 원형 2 입니다. 예를 들어, 파이썬, 클래스 객체, 및 클래스의 클래스이며 type, 그래서 type따라서 자신의 인스턴스이다.

가장 좋은 아이디어는 언어가 제공하는 도구를 사용하는 것입니다.

1 함수는 호출 가능하기 때문에 다소 특별하며, 불투명 한 데이터를 포함 할 수있는 유일한 값입니다 (본문 및 클로저).

2 실제로는 고문을받는 가지 모양의 리본이 거꾸로 구부러져 있지만 "원형"정도면 충분합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.