소개하다
이 장은 ECMAScript 객체 지향 구현에 대한 두 번째 장입니다. 첫 번째 장에서는 소개와 CemaScript의 비교에 대해 논의하고 있습니다. 이 장을 진행하기 전에 첫 번째 장을 읽지 않은 경우이 장이 너무 길기 때문에 첫 번째 장을 먼저 읽는 것이 좋습니다 (35 페이지).
원본 영어 텍스트 : http://dmitrysoshnikov.com/ecmascript/chapter-7-2-op-ecmascript-implementation/
참고 : 기사가 너무 길기 때문에 오류는 불가피하며 지속적으로 수정됩니다.
소개에서 우리는 ecmascript로 확장됩니다. 이제 OOP 구현이라는 것을 알면 정확하게 정의해 봅시다.
코드 사본은 다음과 같습니다.
ECMAScript는 프로토 타입을 기반으로 상속을 위임하는 객체 지향 프로그래밍 언어입니다.
ECMAScript는 프로토 타입 기반 위임 된 상속을 지원하는 객체 지향 언어입니다.
가장 기본적인 데이터 유형을 분석합니다. 가장 먼저 이해해야 할 것은 ECMAScript가 원시 값과 객체를 사용하여 엔티티를 구별한다는 것입니다. 따라서 일부 기사에서 언급 된 "JavaScript에서는 모든 것이 객체입니다"는 잘못되었으며 (완전히 정확하지 않음) 원시적 값은 여기에서 논의 할 일부 데이터 유형입니다.
데이터 유형
ECMAScript는 유형을 동적으로 변환 할 수있는 동적 약점 유형 언어이지만 여전히 데이터 유형이 있습니다. 다시 말해, 객체는 실제 유형에 속해야합니다.
표준 사양에 정의 된 9 가지 데이터 유형이 있지만 ECMAScript 프로그램에서는 6 개만 직접 액세스 할 수 있습니다. 그것들은 정의되지 않은, 널, 부울, 끈, 숫자 및 객체입니다.
다른 세 가지 유형은 구현 수준에서만 액세스 할 수 있으며 (이러한 유형은 ECMAScript 객체에서 사용할 수 없음) 일부 작업 동작을 설명하고 중간 값을 저장하기 위해 사양에 사용됩니다. 이 3 가지 유형은 참조, 목록 및 완료입니다.
따라서 참조는 Delete, Typeof 및와 같은 연산자를 설명하는 데 사용되며 기본 객체 및 속성 이름을 포함합니다. 목록은 매개 변수 목록의 동작을 설명합니다 (새 표현 및 함수 호출시). 완성은 행동 중단, 계속, 반환 및 던지기 진술을 설명하는 데 사용됩니다.
원시 가치 유형
6의 ECMAScript 프로그램에 사용되는 데이터 유형을 되돌아 보면, 처음 5 개는 정의되지 않은, NULL, 부울, 문자열, 번호 및 객체를 포함한 원시적 값 유형입니다.
원래 값 유형의 예 :
코드 사본은 다음과 같습니다.
var a = 정의되지 않은;
var b = null;
var c = true;
var d = '테스트';
var e = 10;
이 값은 하단 계층에서 직접 구현되며 객체가 아니므로 프로토 타입, 생성자도 없습니다.
UNCLE NOTE : 이러한 기본 값은 우리가 일반적으로 사용하는 것과 비슷하지만 (부울, 문자열, 번호, 개체)와 비슷하지만 동일하지는 않습니다. 따라서 Typeof (True) 및 Typeof (부울)의 결과는 타입 (부울)의 결과가 함수이므로 부울, 문자열 및 숫자에 프로토 타입이 있습니다 (다음 읽기 및 쓰기 속성 장에도 언급).
어떤 유형의 데이터가 가장 좋은지 알고 싶다면 Typeof를 사용하는 것이 가장 좋습니다. 주목해야 할 예가 있습니다. 널 유형을 판단하기 위해 Typeof를 사용하는 경우 결과는 객체입니다. 왜? 널의 유형은 null로 정의되기 때문입니다.
코드 사본은 다음과 같습니다.
경고 (null 타입); // "물체"
"객체"를 표시하는 이유는 사양이 이것을 규정하기 때문입니다 : 널 값의 문자열 값의 유형에 대해 "객체"를 반환합니다.
이 사양은 이것을 설명하는 것을 상상하지는 않지만 Brendan Eich (JavaScript의 발명가)는 NULL이 NULL 참조로 객체를 설정하는 것과 같이 정의되지 않은 것에 대해 객체가 나타나는 데 주로 사용된다는 것을 알았습니다. 그러나 일부 문서는 버그에 귀속하는 성가신 일이 있으며 Brendan Eich도 토론에 참여한 버그 목록에 버그를 넣습니다. 결과적으로 놓으면 NULL 타입의 결과를 객체로 설정해야합니다 (262-3 표준은 NULL의 유형을 NULL로 정의하고 262-5는 표준을 객체로 NULL로 변경했습니다).
객체 유형
다음으로, 객체 유형 (객체 생성자와 혼동하지 않으면 서 초록 유형 만 논의)은 ECMAScript 객체를 설명하는 유일한 데이터 유형입니다.
객체는 비정규적인 키 값 쌍의 모음입니다.
객체는 키 값 쌍이 포함되지 않은 컬렉션입니다
객체의 핵심 값은 속성이라고하며 속성은 원래 값 및 기타 객체의 컨테이너입니다. 속성의 값이 함수 인 경우 방법을 메소드라고합니다.
예를 들어:
코드 사본은 다음과 같습니다.
var x = {// 객체 "x"에는 3 개의 속성이 있습니다 : a, b, c
A : 10, // 원본 값
b : {z : 100}, // 객체 "b"에는 속성 z가 있습니다.
C : function () {// function (method)
경고 ( '메소드 X.C');
}
};
경고 (xa); // 10
경고 (XB); // [객체 개체]
경고 (XBZ); // 100
xc (); // '메소드 X.C'
동적
17 장에서 지적했듯이 ES의 물체는 완전히 역동적입니다. 즉, 프로그램이 실행되면 객체의 속성을 마음대로 추가, 수정 또는 삭제할 수 있습니다.
예를 들어:
코드 사본은 다음과 같습니다.
var foo = {x : 10};
// 새 속성을 추가합니다
foo.y = 20;
Console.log (foo); // {x : 10, y : 20}
// 속성 값을 함수로 수정합니다
foo.x = function () {
console.log ( 'foo.x');
};
foo.x (); // 'foo.x'
// 속성을 삭제합니다
foo.x 삭제;
Console.log (foo); // {y : 20}
일부 속성을 수정할 수 없습니다 (읽기 전용 속성, 삭제 된 특성 또는 구성 불가능한 속성). 나중에 속성 특성으로 설명하겠습니다.
또한 ES5 사양은 정적 객체가 새 속성을 확장 할 수 없으며 해당 속성 페이지를 삭제하거나 수정할 수 없음을 규정합니다. 그것들은 소위 냉동 객체이며, 객체를 적용하여 얻을 수 있습니다. freeze (o) 방법.
코드 사본은 다음과 같습니다.
var foo = {x : 10};
// 객체를 얼립니다
객체. freeze (foo);
Console.log (Object.Ifrrozen (foo)); // 진실
// 수정할 수 없습니다
foo.x = 100;
// 확장 할 수 없습니다
foo.y = 200;
// 삭제할 수 없습니다
foo.x 삭제;
Console.log (foo); // {x : 10}
ES5 사양에서 object.preventextensions (o) 메소드는 확장을 방지하는 데 사용됩니다.
코드 사본은 다음과 같습니다.
var foo = {x : 10};
object.defineProperty (foo, "y", {
가치 : 20,
쓰기 가능 : 거짓, // 읽기 전용
구성 가능 : false // 구성 가능하지 않습니다
});
// 수정할 수 없습니다
foo.y = 200;
// 삭제할 수 없습니다
foo.y 삭제; // 거짓
// 예방 및 제어 확장
object.preventextensions (foo);
console.log (object.isextensible (foo)); // 거짓
// 새 속성을 추가 할 수 없습니다
foo.z = 30;
Console.log (foo); {x : 10, y : 20}
내장 객체, 기본 객체 및 호스트 객체
사양은 이러한 내장 객체, 요소 개체 및 호스트 객체를 구별합니다.
내장 객체 및 요소 객체는 ECMAScript 사양에 의해 정의되고 구현되며 둘의 차이는 사소합니다. 모든 ecmAScript 구현 객체는 기본 객체입니다 (일부는 내장 객체이며 일부는 사용자 정의 객체와 같이 프로그램이 실행될 때 생성됩니다). 내장 객체는 기본 객체의 하위 집합이며 프로그램이 시작되기 전에 ECMAScript (예 : Parseint, Match 등)에 내장됩니다. 모든 호스트 객체는 호스트 환경 (일반적으로 브라우저)에서 제공되며 예를 들어 창, 경고 등이 포함될 수 있습니다.
호스트 객체는 ES 자체에 의해 구현 될 수 있으며 규범 적 의미를 완전히 준수합니다. 이와 관련하여, 그것들은 "기본 호스트"객체라고 불릴 수 있지만 (가능한 한 빨리) 표준은 "기본 호스트"객체의 개념을 정의하지 않습니다.
부울, 문자열 및 번호 개체
또한 사양은 일부 기본 특수 래퍼 클래스를 정의하며 이러한 객체는 다음과 같습니다.
1. 부울 대상
2. 문자열 객체
3. 디지털 객체
이 객체는 해당 내장 생성자에 의해 생성되며 내부 특성으로 기본 값을 포함합니다. 이 객체는 원래 값을 저장하기 위해 변환 될 수 있으며 그 반대도 마찬가지입니다.
코드 사본은 다음과 같습니다.
var c = 새로운 부울 (true);
var d = 새 문자열 ( 'test');
var e = 새 번호 (10);
// 원래 값으로 변환합니다
// 새 키워드없이 함수를 사용합니다
с = 부울 (c);
d = 문자열 (d);
e = 숫자 (e);
// 객체로 재확인합니다
с = 물체 (c);
d = 객체 (d);
e = 객체 (e);
또한 특수 내장 생성자가 만든 객체가 있습니다 : 함수 (함수 객체 생성기), 배열 (배열 생성자), RegeXP (정규식 생성자), 수학 (수학 모듈), 날짜 (날짜 생성자) 등이 있습니다. 차이점은 내부 속성에 의해 관리됩니다. 우리는 아래에서 이러한 것들을 논의 할 것입니다.
오자
객체, 배열 및 정규 표현식의 세 가지 객체의 값의 경우, 객체 이니셜 라이저, 어레이 이니셜 라이저 및 정규식 초기화라고하는 식별자를 축약했습니다.
코드 사본은 다음과 같습니다.
// 새 배열과 동일합니다 (1, 2, 3);
// 또는 array = new Array ();
// 배열 [0] = 1;
// 배열 [1] = 2;
// 배열 [2] = 3;
var array = [1, 2, 3];
// 동등합니다
// var Object = new Object ();
// object.a = 1;
// object.b = 2;
// object.c = 3;
var object = {a : 1, b : 2, c : 3};
// new regexp ( "^// d+$", "g")에 해당합니다.
var re =/^/d+$/g;
위의 세 객체가 새로운 유형으로 재 할당되면 후속 구현 시맨틱은 새로운 유형에 따라 사용됩니다. 예를 들어, 현재 Rhino와 Spidermonkey 1.7의 이전 버전에서는 새로운 키워드의 생성자로 객체가 성공적으로 생성되지만 일부 구현 (현재 스파이더/Tracemonkey) 리터럴의 의미는 유형이 변경된 후에 변경되지 않을 수 있습니다.
코드 사본은 다음과 같습니다.
var getClass = Object.Prototype.toString;
객체 = 숫자;
var foo = 새로운 대상;
ALERT ([foo, getClass.call (foo)]); // 0, "[객체 번호]"
var bar = {};
// Rhino, Spidermonkey 1.7-0, "[개체 번호]"
// 기타 : "[대상 개체]", "[개체 객체]"
ALERT ([bar, getClass.call (bar)]);
// 배열은 동일한 효과를 가지고 있습니다
배열 = 숫자;
foo = 새로운 배열;
ALERT ([foo, getClass.call (foo)]); // 0, "[객체 번호]"
bar = [];
// Rhino, Spidermonkey 1.7-0, "[개체 번호]"
// 기타 : 여전히 "", "[Object Object]"
ALERT ([bar, getClass.call (bar)]);
// regexp의 경우 리터럴의 의미는 변경되지 않습니다. 문자의 의미
// 테스트 된 모든 구현에서 변경되지 않습니다
regexp = 숫자;
foo = 새로운 regexp;
ALERT ([foo, getClass.call (foo)]); // 0, "[객체 번호]"
bar = /(?!) /g;
ALERT ([bar, getClass.call (bar)]); ///(?!)/g, "[Object Regexp]"
Regex Literals 및 Regexp 객체
다음 두 가지 예에서 제 3 판의 사양에서 정규 표현의 의미는 동일합니다. Regexp 리터럴은 한 문장으로 만 존재하며 구문 분석 단계에서 생성됩니다. 그러나 regexp 생성자는 새로운 객체를 생성하므로 테스트 중에 LastIndex 값이 잘못되면 일부 문제가 발생할 수 있습니다.
코드 사본은 다음과 같습니다.
for (var k = 0; k <4; k ++) {
var re = /ecma /g;
경고 (re.lastindex); // 0, 4, 0, 4
Alert (Re.test ( "ecmascript")); // true, false, true, false
}
// 비교하다
for (var k = 0; k <4; k ++) {
var re = 새로운 regexp ( "ecma", "g");
경고 (re.lastindex); // 0, 0, 0, 0
Alert (Re.test ( "ecmascript")); // 진실, 참, 진실, 참
}
참고 : 그러나 이러한 문제는 5 판의 ES 사양에서 수정되었습니다. 리터럴 또는 생성자를 기반으로하는지 여부에 관계없이 새 개체를 만듭니다.
연관 배열
텍스트, JavaScript 객체 (객체 이니셜 라이저 {}로 종종 생성)에 대한 다양한 정적 토론은 해시 테이블 및 해시 테이블 또는 기타 간단한 제목이라고합니다. 해시 (루비 또는 PERL의 개념), 관리 어레이 (PHP의 개념), 사전 (Python의 개념) 등.
주로 구조가 유사하기 때문에 "Key-Value"쌍을 사용하여 객체를 저장하기 때문에 "연관 배열"또는 "해시 테이블"이론에 의해 정의 된 데이터 구조를 완전히 준수하기 때문입니다. 또한 해시 테이블 초록 데이터 유형은 일반적으로 구현 수준에서 사용됩니다.
그러나 개념은 용어로 설명되지만 실제로는 실수입니다. ECMAScript의 관점에서 : ECMAScript에는 하나의 객체, 유형 및 하위 유형이 있으며 "키 값"과 스토리지에 이르지 않으므로 이에 대한 특별한 개념이 없습니다. 모든 객체의 내부 속성은 키 값 "쌍으로 저장 될 수 있으므로 :
코드 사본은 다음과 같습니다.
var a = {x : 10};
a [ 'y'] = 20;
AZ = 30;
var b = 새 번호 (1);
BX = 10;
x = 20;
b [ 'z'] = 30;
var c = 새로운 함수 ( '');
CX = 10;
cy = 20;
C [ 'z'] = 30;
// 등의 하위 유형 "하위 유형"
또한 ECMAScript에서 객체가 비어있을 수 있으므로 "해시"의 개념도 여기에서 잘못되었습니다.
코드 사본은 다음과 같습니다.
object.prototype.x = 10;
var a = {}; // 빈 "해시"생성
경고 (A [ "X"]); // 10이지만 비어 있지 않습니다
경고 (A.ToString); // 기능
[ ""y "] = 20; // "hash"에 새 키 값 쌍을 추가
경고 (A [ "Y"]); // 20
object.prototype.y = 20; // 프로토 타입 속성을 추가합니다
[ "y"]를 삭제합니다. // 삭제
경고 (A [ "Y"]); //하지만 여기서 열쇠와 가치는 여전히 가치가 있습니다.
ES5 표준을 사용하면 비비 형 객체 (Object.Create (NULL) 메소드를 사용하여 구현 됨)를 만들 수 있습니다. 이러한 관점에서 이러한 객체를 해시 테이블이라고 할 수 있습니다.
코드 사본은 다음과 같습니다.
var ahashtable = object.create (null);
Console.log (ahashtable.tostring); // 한정되지 않은
또한 일부 속성에는 특정 Getter/Setter 방법이 있으므로이 개념에 대한 혼란을 초래할 수도 있습니다.
코드 사본은 다음과 같습니다.
var a = 새 문자열 ( "foo");
[ '길이'] = 10;
경고 ([ '길이']); // 3
그러나 "해시"에 "프로토 타입"(예 : 루비 또는 파이썬에서 해시 객체를 위임하는 클래스)이 있더라도,이 용어는 두 표기법 (예 : 도트 표기법 AB와 [ "b"] 표기법 사이에 의미 론적 차이가 없기 때문에 ECMAScript에서 정확하지 않습니다.
ECMAScript에서 "속성 속성"의 개념은 의미 적으로 "키", 배열 인덱스 및 메소드와 분리되어 있지 않습니다. 여기서, 객체의 모든 속성을 읽고 쓰는 것은 통합 규칙을 따라야합니다. 프로토 타입 체인을 확인하십시오.
다음 루비 예에서는 시맨틱 차이를 볼 수 있습니다.
코드 사본은 다음과 같습니다.
a = {}
A.class # 해시
A.length # 0
# 새로운 "키 값"쌍
[ '길이'] = 10;
# 의미 적으로, 키가 아닌 점으로 액세스하는 속성 또는 메소드
A. length # 1
# 인덱서는 해시에서 키에 액세스합니다
[ '길이'] # 10
# 기존 개체에서 해시 클래스를 동적으로 선언하는 것과 유사합니다.
# 그런 다음 새 속성 또는 메소드를 선언하십시오
클래스 해시
데프 z
100
끝
끝
# 새로운 속성에 액세스 할 수 있습니다
AZ # 100
# 그러나 "키"는 아닙니다.
A [ 'Z'] # nil
ECMA-262-3 표준은 "해시"(및 유사한)의 개념을 정의하지 않습니다. 그러나 그러한 구조적 이론이 있다면, 그 이름을 따서 명명 된 대상이 될 수 있습니다.
객체 변환
객체를 기본 값으로 변환하려면 값의 메소드를 사용할 수 있습니다. 우리가 말했듯이, 함수의 생성자를 함수 (일부 유형의 경우)로 불리면 새 키워드를 사용하지 않으면 객체를 원래 값으로 변환하면 암시 적 값의 메소드 값과 동일합니다.
코드 사본은 다음과 같습니다.
var a = 새 번호 (1);
var primitivea = 숫자 (a); // 암시 적 "가치"호출
var alsoprimitivea = a.valueof (); // 명시 적 전화
알리다([
A 형, // "Object"
Primitivea의 유형, // "숫자"
Alsoprimitivea // 유형 "번호"
]);
이 방법은 객체가 다음과 같은 다양한 작업에 참여할 수 있습니다.
코드 사본은 다음과 같습니다.
var a = 새 번호 (1);
var b = 새 숫자 (2);
경고 (A + B); // 3
// 심지어
var c = {
x : 10,
Y : 20,
valueof : function () {
this.x + this.y;
}
};
var d = {
x : 30,
Y : 40,
// C의 가치와 같은 함수
가치 : C. valueof
};
경고 (C + D); // 100
value의 기본값은 객체의 유형에 따라 변경됩니다 (재정의되지 않은 경우). 일부 객체의 경우 객체 .prototype.valueof () 및 계산 된 값 : date.prototype.valueof ()가 날짜와 시간을 반환합니다.
코드 사본은 다음과 같습니다.
var a = {};
경고 (a.valueof () === a); // true, "valueof"가 이것을 반환합니다
var d = 새로운 날짜 ();
경고 (d.Valueof ()); // 시간
ALERT (d.Valueof () === D.GetTime ()); // 진실
또한 객체에는보다 원시적 인 표현 - 문자열 디스플레이가 있습니다. 이 tostring 방법은 신뢰할 수 있으며 일부 작업에서 자동으로 사용됩니다.
코드 사본은 다음과 같습니다.
var a = {
valueof : function () {
반환 100;
},
Tostring : function () {
'__test'를 반환합니다.
}
};
//이 작업에서 TOSTRING 메소드가 자동으로 호출됩니다.
경고 (a); // "__시험"
// 그러나 여기서 값은 () 메소드가 호출됩니다
경고 (a + 10); // 110
// 그러나 값이 삭제되면 일단 값이 삭제됩니다
// Tostring은 자동으로 다시 호출 할 수 있습니다
a.valueof 삭제;
경고 (a + 10); // "_test10"
object.prototype에 정의 된 tostring 방법은 특별한 의미를 가지며 아래에서 논의 할 내부 [class] 속성 값을 반환합니다.
원래 값 (toprimitive)으로 변환하는 것과 비교하여 값을 객체 유형으로 변환하는 변환 사양 (toobject)도 있습니다.
명백한 방법은 내장 객체 생성자를 toobject를 호출하는 함수로 사용하는 것입니다 (새 키워드를 사용하는 것과 같은 것은 괜찮습니다).
코드 사본은 다음과 같습니다.
var n = 객체 (1); // [객체 번호]
var s = 객체 ( 'test'); // [객체 문자열]
// 새 연산자와 일부 유사점도 가능합니다.
var b = 새로운 객체 (true); // [Object Boolean]
// 매개 변수 새 개체를 적용하면 간단한 개체를 만듭니다.
var o = new Object (); // [객체 개체]
// 매개 변수가 기존 객체 인 경우
// 생성의 결과는 단순히 객체를 반환하는 것입니다.
var a = [];
경고 (a === new Object (a)); // 진실
경고 (a === 객체 (a)); // 진실
내장 생성자를 호출하는 것과 관련하여 생성자에 따라 새 연산자를 사용하거나 적용하지 않는 일반적인 규칙은 없습니다. 예를 들어, 배열 또는 기능은 새 연산자의 생성자 또는 새 연산자를 사용하지 않는 간단한 기능을 사용하여 동일한 결과를 생성합니다.
코드 사본은 다음과 같습니다.
var a = 배열 (1, 2, 3); // [객체 배열]
var b = 새로운 배열 (1, 2, 3); // [객체 배열]
var c = [1, 2, 3]; // [객체 배열]
var d = 함수 ( ''); // [객체 함수]
var e = 새로운 함수 ( ''); // [객체 함수]
일부 연산자가 사용되면 일부 디스플레이 및 암시 적 변환이 있습니다.
코드 사본은 다음과 같습니다.
var a = 1;
var b = 2;
// 암시 적
var c = a + b; // 3, 숫자
var d = a + b + '5'// "35", 문자열
// 명시 적
var e = '10'; // "10", 문자열
var f = +e; // 10, 숫자
var g = parseint (e, 10); // 10, 숫자
// 등
속성의 속성
모든 속성에는 많은 속성이있을 수 있습니다.
1. {readonly} - 속성에 값을 할당하는 쓰기 작동을 무시하지만 호스트 환경 동작에 의해 읽기 전용 속성을 변경할 수 있습니다. 즉, "일정한 값"이 아닙니다.
2. {dontenum}-속성은 for..in 루프에 의해 열거 될 수 없습니다
3. {dontdelete}-삭제 연산자의 동작은 무시됩니다 (즉, 삭제할 수 없습니다).
4. {내부} - 이름이없는 내부 속성 (구현 레벨에서만 사용). 이러한 속성은 ECMAScript에서 액세스 할 수 없습니다.
es5 {readonly}, {dontenum} 및 {dontdelete}에서 [[writable]], [[enumerable]] 및 [[configurable]]로 이름이 바뀌 었으며 이러한 속성은 Object.DefineProperty 또는 이와 유사한 방법을 통해 수동으로 관리 할 수 있습니다.
코드 사본은 다음과 같습니다.
var foo = {};
object.defineProperty (foo, "x", {
가치 : 10,
쓰기 가능 : true, // 즉, {readonly} = false
열거 가능 : 거짓, // 즉, {dontenum} = true
구성 가능 : true // 즉, {dontdelete} = false
});
Console.log (foo.x); // 10
// 설명을 통해 속성을 가져옵니다
var desc = object.getOwnPropertyDescriptor (foo, "x");
Console.log (desc.enumerable); // 거짓
Console.log (Desc.Writable); // 진실
// 등
내부 속성 및 방법
객체는 내부 속성 (구현 수준의 일부)을 가질 수 있으며 ECMAScript 프로그램은 직접 액세스 할 수 없습니다 (그러나 일부 구현이 일부 속성에 액세스 할 수 있음을 아래에서 볼 수 있습니다). 이 특성은 중첩 브래킷을 통해 액세스됩니다 [[]]. 이 속성 중 일부를 살펴 보겠습니다. 이러한 속성에 대한 설명은 사양에서 찾을 수 있습니다.
각 객체는 다음 내부 속성과 방법을 구현해야합니다.
1. [[프로토 타입]] - 객체의 프로토 타입 (아래에 자세히 소개 될 예정)
2. [[class]] - 문자열 객체의 표현 (예 : 객체 배열, 함수 객체, 함수 등); 물체를 구별하는 데 사용됩니다
3. [[get]]-속성 값을 얻는 메소드
4. [[put]]-속성 값을 설정하는 방법
5. [[canput]] - 속성이 쓸 수 있는지 확인하십시오
6. [[Hasproperty]]-개체에 이미이 속성이 있는지 확인하십시오
7. [[delete]] - 객체 에서이 속성을 삭제합니다
8. [[defaultValue]] 객체의 원래 값을 반환합니다 (값의 메소드를 호출하면 일부 객체는 TypeError 예외를 던질 수 있습니다).
내부 속성 [[class]]의 값은 객체를 통해 간접적으로 얻을 수 있으며, 다음 문자열을 반환해야합니다. "[Object" + [[class]] + "]". 예를 들어:
코드 사본은 다음과 같습니다.
var getClass = Object.Prototype.toString;
getClass.call ({}); // [객체 개체]
getClass.call ([]); // [객체 배열]
getClass.call (새 번호 (1)); // [객체 번호]
// 등
이 함수는 일반적으로 객체를 확인하는 데 사용되지만 사양에서 호스트 객체의 [[class]]는 내장 객체의 [[class]] 속성의 값을 포함하여 어떤 값이든 이론적으로 100% 정확할 수 없습니다. 예를 들어, document.childnodes.item (...) 메소드의 [[class]] 속성은 IE에서 "string"을 반환하지만 다른 구현에서 반환 된 "함수"는 실제로입니다.
코드 사본은 다음과 같습니다.
// 즉, "String", 기타 - "함수"에서
alert (getClass.call (document.childnodes.Item));
건설자
따라서 위에서 언급했듯이 ECMAScript의 객체는 소위 생성자를 통해 생성됩니다.
생성자는 새로 생성 된 객체를 생성하고 초기화하는 함수입니다.
생성자는 새로 생성 된 개체를 생성하고 초기화하는 함수입니다.
객체 생성 (메모리 할당)은 생성자의 내부 방법 [[Construct]]의 책임입니다. 이 내부 방법의 동작은 정의되며 모든 생성자는이 방법을 사용하여 새 개체에 대한 메모리를 할당합니다.
초기화는 기능을 새 개체를 위아래로 호출하여 관리되며, 이는 생성자 [[Call]]의 내부 메소드에 의해 책임이 있습니다.
초기화 단계에서 다른 객체를 반환 할 수 있지만 사용자 코드는 초기화 단계에서만 액세스 할 수 있습니다 (첫 번째 단계에서 생성 된 TIHS 개체를 무시 함).
코드 사본은 다음과 같습니다.
기능 a () {
// 새로 생성 된 개체를 업데이트합니다
this.x = 10;
// 반환은 다른 객체입니다
반환 [1, 2, 3];
}
var a = 새로운 a ();
Console.log (Ax, A); 정의되지 않은, [1, 2, 3]
15 장 기능 - 기능 생성을위한 알고리즘 섹션을 참조하면, 우리는 함수가 [[construct]] 및 [[call]] 및 [[call]] 특성과 미래의 프로토 타입의 프로토 타입을 포함하여 기본 객체임을 알 수 있습니다 (주 : NativeObject는 아래의 pseudo -code에 사용되는 기본 객체에 대한 대상입니다).
코드 사본은 다음과 같습니다.
f = new NativeObject ();
f. [[class]] = "function"
.... // 다른 속성
f. [[call]] = <함수에 대한 참조> // 함수 자체
f. [[Construct]] = 내부 공자 // 일반 내부 생성자
.... // 다른 속성
// f 생성자가 만든 객체 프로토 타입
__objectPrototype = {};
__objectPrototype.constructor = f // {dontenum}
f. prototype = __objectPrototype
[[call]]]는 [[class]] 속성을 제외한 객체를 구별하는 주요 방법 (여기서 "함수"와 동일)이므로 객체의 내부 [[call]] 속성을 함수라고합니다. 이러한 객체가 연산자 유형을 사용하면 "함수"를 반환합니다. 그러나 주로 기본 객체와 관련이 있습니다. 경우에 따라 구현은 window.alert (...)의 효과와 같은 값을 다르게 얻기 위해 Typeof를 사용합니다.
코드 사본은 다음과 같습니다.
// 즉, 브라우저 - "개체", "개체", 기타 브라우저 - "function", "function"
alert (object.prototype.tostring.call (window.alert));
경고 (typeof window.alert); // "물체"
내부 방법 [[Construct]]는 새로운 연산자와 함께 생성자를 사용하여 활성화됩니다. 매개 변수가없는 경우 생성자를 호출하기위한 괄호도 생략 할 수 있습니다.
코드 사본은 다음과 같습니다.
함수 a (x) {// 생성자 а
this.x = x || 10;
}
// 매개 변수를 전달하지 않으면 브래킷도 생략 할 수 있습니다.
var a = 새로운 a; // 또는 새 a ();
경고 (AX); // 10
// 매개 변수 x를 명시 적으로 전달합니다
var b = 새로운 a (20);
경고 (BX); // 20
또한 생성자의 SHI (초기화 단계)가 새로 생성 된 객체로 설정되어 있음을 알고 있습니다.
객체 생성에 대한 알고리즘을 연구합시다.
객체 생성 알고리즘
내부 방법 [[Construct]]의 동작은 다음과 같이 설명 할 수 있습니다.
코드 사본은 다음과 같습니다.
f. [[Construct]] (초기 파라미터) :
o = New NativeObject ();
// 속성 [[class]]는 "object"로 설정됩니다.
o. [[class]] = "object"
// f.prototype를 참조 할 때 객체 g를 가져옵니다
var __objectPrototype = F. 프로로 타입;
// __objectPrototype이 객체 인 경우 :
o. [[프로토 타입]] = __objectPrototype
// 그렇지 않으면:
o. [[프로토 타입]] = Object.Prototype;
// 여기 o. [[[프로토 타입]]은 객체 객체의 프로토 타입입니다.
// f. [[Call]]은 새로 생성 된 개체가 초기화되면 적용됩니다.
// 이것을 새로 생성 된 개체로 설정합니다
// 매개 변수는 f의 초기 파라미터와 동일합니다.
r = f. [[call]] (초기 파라미터); 이것은 === O;
// 여기 r은 [[call]]의 반환 값입니다.
// JS에서 : 이와 같이 :
// r = F.apply (O, InitialParameters);
// r이 객체 인 경우
반환 r
// 그렇지 않으면
반환 o
두 가지 주요 기능에 유의하십시오.
첫째, 새로 생성 된 객체의 프로토 타입은 현재 기능의 프로토 타입 속성에서 얻어진다 (이는 동일한 생성자에 의해 생성 된 두 개의 생성 된 개체의 프로토 타입 특성이 다른 기능의 프로토 타입 특성이 다를 수 있기 때문에 다를 수 있음을 의미한다).
2. 둘째, 위에서 언급했듯이, [[call]]이 개체가 초기화 될 때 객체를 반환하면 전체 새 연산자에게 사용 된 결과입니다.
코드 사본은 다음과 같습니다.
함수 a () {}
a.prototype.x = 10;
var a = 새로운 a ();
경고 (AX); // 10 프로토 타입에서 가져옵니다
// .prototype 속성을 새 개체로 설정합니다
// 명시 적으로 선언 된 .constructor 속성이 아래에 설명되어 있습니다.
A. 프로토 타입 = {
생성자 : A,
Y : 100
};
var b = 새로운 a ();
// 객체 "B"에는 새로운 속성이 있습니다
경고 (BX); // 한정되지 않은
경고 (by); // 100 프로토 타입에서 얻습니다
// 그러나 객체 A의 프로토 타입은 여전히 원래 결과를 얻을 수 있습니다.
경고 (AX); // 10- 프로토 타입에서 얻습니다
함수 b () {
this.x = 10;
새 배열 ()을 반환합니다.
}
// "B"생성자가 반환되지 않으면 (또는 반환)
// 그러면이 객체를 사용할 수 있지만 다음 상황은 배열을 반환합니다.
var b = 새로운 b ();
경고 (BX); // 한정되지 않은
Alert (object.prototype.tostring.call (b)); // [객체 배열]
프로토 타입에 대해 자세히 알아 보겠습니다
원기
각 객체에는 프로토 타입이 있습니다 (일부 시스템 객체 제외). 프로토 타입 통신은 [[[프로토 타입]] 프로토 타입 속성에 대한 내부, 암시 적 및 간접 액세스를 통해 수행됩니다. 프로토 타입은 물체 또는 널 값일 수 있습니다.
속성 생성자
위의 예에는 두 가지 중요한 지식이 있습니다. 첫 번째는 함수의 생성자 속성의 프로토 타입 속성에 관한 것입니다. 함수 생성 알고리즘에서, 우리는 생성자 속성이 함수 생성 단계에서 기능의 프로토 타입 속성으로 설정되어 있음을 알고 있습니다. 생성자 속성의 값은 함수 자체에 대한 중요한 참조입니다.
코드 사본은 다음과 같습니다.
함수 a () {}
var a = 새로운 a ();
경고 (a.constructor); // 대표단에 의해 함수 a () {}
경고 (a.constructor === a); // 진실
일반적 으로이 경우 오해가 있습니다. 새로 생성 된 객체 자체로 속성을 구성하는 것은 잘못이지만, 우리가 볼 수 있듯이이 속성은 프로토 타입에 속하며 상속을 통해 객체에 액세스합니다.
생성자 속성의 인스턴스를 상속하면 프로토 타입 객체에 대한 참조를 간접적으로 얻을 수 있습니다.
코드 사본은 다음과 같습니다.
함수 a () {}
a.prototype.x = 새 숫자 (10);
var a = 새로운 a ();
경고 (a.constructor.prototype); // [객체 개체]
경고 (AX); // 10, 프로토 타입
// a. [[프로토 타입]]. x와 동일한 효과
경고 (a.constructor.prototype.x); // 10
Alert (a.constructor.prototype.x === Ax); // 진실
그러나 객체가 생성 된 후 함수의 생성자 및 프로토 타입 특성을 재정의 할 수 있습니다. 이 경우 객체는 위에서 언급 한 메커니즘을 잃습니다. 함수의 프로토 타입 속성 (새 개체 추가 또는 기존 객체를 수정)을 통해 요소의 프로토 타입 프로토 타입을 편집하면 인스턴스에 새로 추가 된 속성이 표시됩니다.
그러나 기능의 프로토 타입 속성 (새 개체를 할당함으로써)을 완전히 변경하면 생성 된 객체에 생성자 특성이 포함되지 않기 때문에 원래 생성자에 대한 참조가 손실됩니다.
코드 사본은 다음과 같습니다.
함수 a () {}
A. 프로토 타입 = {
x : 10
};
var a = 새로운 a ();
경고 (AX); // 10
경고 (a.constructor === a); // 거짓!
따라서 기능에 대한 프로토 타입 참조는 수동으로 복원되어야합니다.
코드 사본은 다음과 같습니다.
함수 a () {}
A. 프로토 타입 = {
생성자 : A,
x : 10
};
var a = 새로운 a ();
경고 (AX); // 10
경고 (a.constructor === a); // 진실
생성자 속성이 원래 누락 된 프로토 타입과 비교하여 수동으로 복원되지만 {dontenum} 기능은 사라졌습니다.
코드 사본은 다음과 같습니다.
var foo = {x : 10};
object.defineProperty (foo, "y", {
가치 : 20,
열거 가능한 : false // aka {dontenum} = true
});
Console.log (foo.x, foo.y); // 10, 20
for (foo in foo) {
Console.log (k); // "X"만
}
var XDESC = Object.GetOwnPropertyDescriptor (foo, "x");
var ydesc = object.getOwnPropertyDescriptor (foo, "y");
Console.log (
xdesc.enumerable, // true
ydesc.enumerable // false
);
명시 적 프로토 타입 및 암시 적 [[프로토 타입]] 특성
일반적으로 함수의 프로토 타입 속성을 통해 객체의 프로토 타입을 명시 적으로 참조하는 것은 부정확합니다. 객체의 [[[프로토 타입]] 속성과 동일한 객체를 나타냅니다.
a. [[프로토 타입]] ---> 프로토 타입 <---- A. 프로로 타입
또한, 인스턴스의 [[프로토 타입]] 값은 실제로 생성자의 프로토 타입 속성에 대해 얻어진다.
그러나 프로토 타입 속성을 제출하면 생성 된 프로토 타입에 영향을 미치지 않습니다 (생성자 변경의 프로토 타입 속성에만 영향을 미치는 경우에만 영향을 미칩니다), 즉, 새로 생성 된 객체에는 새 프로토 타입이 있으며, 생성 된 객체는 여전히 원래의 기존 프로토 타입을 참조합니다 (이 프로토 타입은 더 이상 수정할 수 없습니다).
코드 사본은 다음과 같습니다.
// A.prototype 프로토 타입을 수정하기 전에 상황
a. [[프로토 타입]] ---> 프로토 타입 <---- A. 프로로 타입
// 수정 후
A. prototype ---> 새로운 프로토 타입 // 새로운 개체는이 프로토 타입을 갖습니다.
a. [[프로토 타입]] ---> 프로토 타입 // 부츠의 원래 프로토 타입
예를 들어:
코드 사본은 다음과 같습니다.
함수 a () {}
a.prototype.x = 10;
var a = 새로운 a ();
경고 (AX); // 10
A. 프로토 타입 = {
생성자 : A,
x : 20
Y : 30
};
// 객체 A는 암시 적 [[프로토 타입]을 통해 원유의 프로토 타입에서 얻은 값입니다.
경고 (AX); // 10
Alert (ay) // 정의되지 않았습니다
var b = 새로운 a ();
// 그러나 새로운 객체는 새로운 프로토 타입에서 얻은 값입니다.
경고 (BX); // 20
경고 (by) // 30
따라서 일부 기사는 "프로토 타입의 동적 수정은 새로운 프로토 타입이있는 모든 객체에 영향을 미칠 것"이라고 말하는 것은 잘못이며, 새로운 프로토 타입은 프로토 타입이 수정 된 후에 새로 생성 된 객체에만 적용됩니다.
여기서 주요 규칙은 객체의 프로토 타입이 객체가 생성 될 때 생성되며 그 후 새 객체로 수정할 수 없습니다. 동일한 객체가 여전히 참조되면 생성자의 명시 적 프로토 타입을 통해 참조 할 수 있습니다. 객체가 생성 된 후 프로토 타입의 특성을 추가하거나 수정할 수 있습니다.
비표준 __proto__ 속성
그러나 일부 구현 (예 : Spidermonkey)은 대상의 프로토 타입을 참조하기 위해 대표적인 __proto__ 명시 적 특성을 제공합니다.
코드 사본은 다음과 같습니다.
함수 a () {}
a.prototype.x = 10;
var a = 새로운 a ();
경고 (AX); // 10
var __newPrototype = {
생성자 : A,
x : 20,
Y : 30
};
// 새 개체에 대한 참조
A. 프로토 타입 = __newPrototype;
var b = 새로운 a ();
경고 (BX); // 20
경고 (by); // 30
// "A"객체는 여전히 이전 프로토 타입을 사용하고 있습니다.
경고 (AX); // 10
경고 (ay); // 한정되지 않은
// 프로토 타입을 명시 적으로 수정합니다
a .__ proto__ = __newprototype;
// 이제 "r"객체는 새 개체를 나타냅니다.
경고 (AX); // 20
경고 (ay); // 30
注意,ES5提供了Object.getPrototypeOf(O)方法,该方法直接返回对象的[[Prototype]]属性――实例的初始原型。 然而,和__proto__相比,它只是getter,它不允许set值。
코드 사본은 다음과 같습니다.
var foo = {};
Object.getPrototypeOf(foo) == Object.prototype; // 진실
对象独立于构造函数
因为实例的原型独立于构造函数和构造函数的prototype属性,构造函数完成了自己的主要工作(创建对象)以后可以删除。原型对象通过引用[[Prototype]]属性继续存在:
코드 사본은 다음과 같습니다.
function A() {}
A.prototype.x = 10;
var a = 새로운 a ();
alert(ax); // 10
// 设置A为null - 显示引用构造函数
A = null;
// 但如果.constructor属性没有改变的话,
// 依然可以通过它创建对象
var b = new a.constructor();
alert(bx); // 10
// 隐式的引用也删除掉
delete a.constructor.prototype.constructor;
delete b.constructor.prototype.constructor;
// 通过A的构造函数再也不能创建对象了
// 但这2个对象依然有自己的原型
alert(ax); // 10
alert(bx); // 10
instanceof操作符的特性
我们是通过构造函数的prototype属性来显示引用原型的,这和instanceof操作符有关。该操作符是和原型链一起工作的,而不是构造函数,考虑到这一点,当检测对象的时候往往会有误解:
코드 사본은 다음과 같습니다.
if (foo instanceof Foo) {
...
}
这不是用来检测对象foo是否是用Foo构造函数创建的,所有instanceof运算符只需要一个对象属性――foo.[[Prototype]],在原型链中从Foo.prototype开始检查其是否存在。instanceof运算符是通过构造函数里的内部方法[[HasInstance]]来激活的。
让我们来看看这个例子:
코드 사본은 다음과 같습니다.
function A() {}
A.prototype.x = 10;
var a = 새로운 a ();
alert(ax); // 10
alert(a instanceof A); // 진실
// 如果设置原型为null
A.prototype = null;
// ..."a"依然可以通过a.[[Prototype]]访问原型
alert(ax); // 10
// 不过,instanceof操作符不能再正常使用了
// 因为它是从构造函数的prototype属性来实现的
alert(a instanceof A); // 错误,A.prototype不是对象
另一方面,可以由构造函数来创建对象,但如果对象的[[Prototype]]属性和构造函数的prototype属性的值设置的是一样的话,instanceof检查的时候会返回true:
코드 사본은 다음과 같습니다.
function B() {}
var b = 새로운 b ();
alert(b instanceof B); // 진실
function C() {}
var __proto = {
constructor: C
};
C.prototype = __proto;
b.__proto__ = __proto;
alert(b instanceof C); // 진실
alert(b instanceof B); // 거짓
原型可以存放方法并共享属性
大部分程序里使用原型是用来存储对象的方法、默认状态和共享对象的属性。
事实上,对象可以拥有自己的状态,但方法通常是一样的。 因此,为了内存优化,方法通常是在原型里定义的。 这意味着,这个构造函数创建的所有实例都可以共享找个方法。
코드 사본은 다음과 같습니다.
function A(x) {
this.x = x || 100;
}
A.prototype = (function () {
// 컨텍스트를 초기화합니다
// 使用额外的对象
var _someSharedVar = 500;
function _someHelper() {
alert('internal helper: ' + _someSharedVar);
}
function method1() {
alert('method1: ' + this.x);
}
function method2() {
alert('method2: ' + this.x);
_someHelper();
}
// 原型自身
반품 {
constructor: A,
method1: method1,
method2: method2
};
}) ();
var a = new A(10);
var b = new A(20);
a.method1(); // method1: 10
a.method2(); // method2: 10, internal helper: 500
b.method1(); // method1: 20
b.method2(); // method2: 20, internal helper: 500
// 2个对象使用的是原型里相同的方法
alert(a.method1 === b.method1); // true
alert(a.method2 === b.method2); // true
读写属性
正如我们提到,读取和写入属性值是通过内部的[[Get]]和[[Put]]方法。这些内部方法是通过属性访问器激活的:点标记法或者索引标记法:
코드 사본은 다음과 같습니다.
// 写入
foo.bar = 10; // 调用了[[Put]]
console.log(foo.bar); // 10, 调用了[[Get]]
console.log(foo['bar']); // 效果一样
让我们用伪代码来看一下这些方法是如何工作的:
[[Get]]方法
[[Get]]也会从原型链中查询属性,所以通过对象也可以访问原型中的属性。
O.[[Get]](P):
코드 사본은 다음과 같습니다.
// 如果是自己的属性,就返回
if (O.hasOwnProperty(P)) {
return OP;
}
// 否则,继续分析原型
var __proto = O.[[Prototype]];
// 如果原型是null,返回undefined
// 这是可能的:最顶层Object.prototype.[[Prototype]]是null
if (__proto === null) {
return undefined;
}
// 否则,对原型链递归调用[[Get]],在各层的原型中查找属性
// 直到原型为null
return __proto.[[Get]](P)
请注意,因为[[Get]]在如下情况也会返回undefined:
코드 사본은 다음과 같습니다.
if (window.someObject) {
...
}
这里,在window里没有找到someObject属性,然后会在原型里找,原型的原型里找,以此类推,如果都找不到,按照定义就返回undefined。
注意:in操作符也可以负责查找属性(也会查找原型链):
코드 사본은 다음과 같습니다.
if ('someObject' in window) {
...
}
这有助于避免一些特殊问题:比如即便someObject存在,在someObject等于false的时候,第一轮检测就通不过。
[[Put]]方法
[[Put]]方法可以创建、更新对象自身的属性,并且掩盖原型里的同名属性。
O.[[Put]](P, V):
코드 사본은 다음과 같습니다.
// 如果不能给属性写值,就退出
if (!O.[[CanPut]](P)) {
반품;
}
// 如果对象没有自身的属性,就创建它
// 所有的attributes特性都是false
if (!O.hasOwnProperty(P)) {
createNewProperty(O, P, attributes: {
ReadOnly: false,
DontEnum: false,
DontDelete: false,
Internal: false
});
}
// 如果属性存在就设置值,但不改变attributes特性
OP = V
반품;
예를 들어:
코드 사본은 다음과 같습니다.
Object.prototype.x = 100;
var foo = {};
console.log(foo.x); // 100, 继承属性
foo.x = 10; // [[Put]]
console.log(foo.x); // 10, 自身属性
delete foo.x;
console.log(foo.x); // 重新是100,继承属性
请注意,不能掩盖原型里的只读属性,赋值结果将忽略,这是由内部方法[[CanPut]]控制的。
// 例如,属性length是只读的,我们来掩盖一下length试试
function SuperString() {
/* nothing */
}
SuperString.prototype = new String("abc");
var foo = new SuperString();
console.log(foo.length); // 3, "abc"的长度
// 尝试掩盖
foo.length = 5;
console.log(foo.length); // 依然是3
但在ES5的严格模式下,如果掩盖只读属性的话,会保存TypeError错误。
属性访问器
内部方法[[Get]]和[[Put]]在ECMAScript里是通过点符号或者索引法来激活的,如果属性标示符是合法的名字的话,可以通过“.”来访问,而索引方运行动态定义名称。
코드 사본은 다음과 같습니다.
var a = {testProperty: 10};
alert(a.testProperty); // 10, 点
alert(a['testProperty']); // 10, 索引
var propertyName = 'Property';
alert(a['test' + propertyName]); // 10, 动态属性通过索引的方式
这里有一个非常重要的特性――属性访问器总是使用ToObject规范来对待“.”左边的值。这种隐式转化和这句“在JavaScript中一切都是对象”有关系,(然而,当我们已经知道了,JavaScript里不是所有的值都是对象)。
如果对原始值进行属性访问器取值,访问之前会先对原始值进行对象包装(包括原始值),然后通过包装的对象进行访问属性,属性访问以后,包装对象就会被删除。
예를 들어:
코드 사본은 다음과 같습니다.
var a = 10; // 原始值
// 但是可以访问方法(就像对象一样)
alert(a.toString()); // "10"
// 此外,我们可以在a上创建一个心属性
a.test = 100; // 好像是没问题的
// 但,[[Get]]方法没有返回该属性的值,返回的却是undefined
alert(a.test); // undefined
那么,为什么整个例子里的原始值可以访问toString方法,而不能访问新创建的test属性呢?
答案很简单:
首先,正如我们所说,使用属性访问器以后,它已经不是原始值了,而是一个包装过的中间对象(整个例子是使用new Number(a)),而toString方法这时候是通过原型链查找到的:
코드 사본은 다음과 같습니다.
// 执行a.toString()的原理:
1. wrapper = new Number(a);
2. wrapper.toString(); // "10"
3. delete wrapper;
接下来,[[Put]]方法创建新属性时候,也是通过包装装的对象进行的:
코드 사본은 다음과 같습니다.
// 执行a.test = 100的原理:
1. wrapper = new Number(a);
2. wrapper.test = 100;
3. delete wrapper;
我们看到,在第3步的时候,包装的对象以及删除了,随着新创建的属性页被删除了――删除包装对象本身。
然后使用[[Get]]获取test值的时候,再一次创建了包装对象,但这时候包装的对象已经没有test属性了,所以返回的是undefined:
코드 사본은 다음과 같습니다.
// 执行a.test的原理:
1. wrapper = new Number(a);
2. wrapper.test; // undefined
这种方式解释了原始值的读取方式,另外,任何原始值如果经常用在访问属性的话,时间效率考虑,都是直接用一个对象替代它;与此相反,如果不经常访问,或者只是用于计算的话,到可以保留这种形式。
상속
我们知道,ECMAScript是使用基于原型的委托式继承。链和原型在原型链里已经提到过了。其实,所有委托的实现和原型链的查找分析都浓缩到[[Get]]方法了。
如果你完全理解[[Get]]方法,那JavaScript中的继承这个问题将不解自答了。
经常在论坛上谈论JavaScript中的继承时,我都是用一行代码来展示,事实上,我们不需要创建任何对象或函数,因为该语言已经是基于继承的了,代码如下:
코드 사본은 다음과 같습니다.
alert(1..toString()); // "1"
我们已经知道了[[Get]]方法和属性访问器的原理了,我们来看看都发生了什么:
1.首先,从原始值1,通过new Number(1)创建包装对象
2.然后toString方法是从这个包装对象上继承得到的
为什么是继承的? 因为在ECMAScript中的对象可以有自己的属性,包装对象在这种情况下没有toString方法。 因此它是从原理里继承的,即Number.prototype。
注意有个微妙的地方,在上面的例子中的两个点不是一个错误。第一点是代表小数部分,第二个才是一个属性访问器:
코드 사본은 다음과 같습니다.
1.toString(); // 语法错误!
(1).toString(); // OK
1..toString(); // OK
1['toString'](); // OK
原型链
让我们展示如何为用户定义对象创建原型链,非常简单:
코드 사본은 다음과 같습니다.
기능 a () {
alert('A.[[Call]] activated');
this.x = 10;
}
A.prototype.y = 20;
var a = 새로운 a ();
alert([ax, ay]); // 10 (自身), 20 (继承)
function B() {}
// 最近的原型链方式就是设置对象的原型为另外一个新对象
B. prototype = 새로운 a ();
// 修复原型的constructor属性,否则的话是A了
B.prototype.constructor = B;
var b = 새로운 b ();
alert([bx, by]); // 10, 20, 2个都是继承的
// [[Get]] bx:
// bx (no) -->
// b.[[Prototype]].x (yes) - 10
// [[Get]] by
// by (no) -->
// b.[[Prototype]].y (no) -->
// b.[[Prototype]].[[Prototype]].y (yes) - 20
// where b.[[Prototype]] === B.prototype,
// and b.[[Prototype]].[[Prototype]] === A.prototype
这种方法有两个特性:
首先,B.prototype将包含x属性。乍一看这可能不对,你可能会想x属性是在A里定义的并且B构造函数也是这样期望的。尽管原型继承正常情况是没问题的,但B构造函数有时候可能不需要x属性,与基于class的继承相比,所有的属性都复制到后代子类里了。
尽管如此,如果有需要(模拟基于类的继承)将x属性赋给B构造函数创建的对象上,有一些方法,我们后来来展示其中一种方式。
其次,这不是一个特征而是缺点――子类原型创建的时候,构造函数的代码也执行了,我们可以看到消息"A.[[Call]] activated"显示了两次――当用A构造函数创建对象赋给B.prototype属性的时候,另外一场是a对象创建自身的时候!
下面的例子比较关键,在父类的构造函数抛出的异常:可能实际对象创建的时候需要检查吧,但很明显,同样的case,也就是就是使用这些父对象作为原型的时候就会出错。
코드 사본은 다음과 같습니다.
function A(param) {
if (!param) {
throw 'Param required';
}
this.param = param;
}
A.prototype.x = 10;
var a = new A(20);
alert([ax, a.param]); // 10, 20
function B() {}
B.prototype = new A(); // Error
此外,在父类的构造函数有太多代码的话也是一种缺点。
解决这些“功能”和问题,程序员使用原型链的标准模式(下面展示),主要目的就是在中间包装构造函数的创建,这些包装构造函数的链里包含需要的原型。
코드 사본은 다음과 같습니다.
기능 a () {
alert('A.[[Call]] activated');
this.x = 10;
}
A.prototype.y = 20;
var a = 새로운 a ();
alert([ax, ay]); // 10 (自身), 20 (集成)
함수 b () {
// 或者使用A.apply(this, arguments)
B.superproto.constructor.apply(this, arguments);
}
// 继承:通过空的中间构造函数将原型连在一起
var F = function () {};
F.prototype = A.prototype; // 引用
B.prototype = new F();
B.superproto = A.prototype; // 显示引用到另外一个原型上, "sugar"
// 修复原型的constructor属性,否则的就是A了
B.prototype.constructor = B;
var b = 새로운 b ();
alert([bx, by]); // 10 (自身), 20 (集成)
注意,我们在b实例上创建了自己的x属性,通过B.superproto.constructor调用父构造函数来引用新创建对象的上下文。
我们也修复了父构造函数在创建子原型的时候不需要的调用,此时,消息"A.[[Call]] activated"在需要的时候才会显示。
为了在原型链里重复相同的行为(中间构造函数创建,设置superproto,恢复原始构造函数),下面的模板可以封装成一个非常方面的工具函数,其目的是连接原型的时候不是根据构造函数的实际名称。
코드 사본은 다음과 같습니다.
function inherit(child, parent) {
var F = function () {};
F.prototype = parent.prototype
child.prototype = new F();
child.prototype.constructor = child;
child.superproto = parent.prototype;
return child;
}
因此,继承:
코드 사본은 다음과 같습니다.
function A() {}
A.prototype.x = 10;
function B() {}
inherit(B, A); // 连接原型
var b = 새로운 b ();
alert(bx); // 10, 在A.prototype查找到
也有很多语法形式(包装而成),但所有的语法行都是为了减少上述代码里的行为。
例如,如果我们把中间的构造函数放到外面,就可以优化前面的代码(因此,只有一个函数被创建),然后重用它:
코드 사본은 다음과 같습니다.
var inherit = (function(){
function F() {}
return function (child, parent) {
F.prototype = parent.prototype;
child.prototype = new F;
child.prototype.constructor = child;
child.superproto = parent.prototype;
return child;
};
}) ();
由于对象的真实原型是[[Prototype]]属性,这意味着F.prototype可以很容易修改和重用,因为通过new F创建的child.prototype可以从child.prototype的当前值里获取[[Prototype]]:
코드 사본은 다음과 같습니다.
function A() {}
A.prototype.x = 10;
function B() {}
inherit(B, A);
B.prototype.y = 20;
B.prototype.foo = function () {
alert("B#foo");
};
var b = 새로운 b ();
alert(bx); // 10, 在A.prototype里查到
function C() {}
inherit(C, B);
// 使用"superproto"语法糖
// 调用父原型的同名方法
C.ptototype.foo = function () {
C.superproto.foo.call(this);
alert("C#foo");
};
var c = new C();
alert([cx, cy]); // 10, 20
c.foo(); // B#foo, C#foo
注意,ES5为原型链标准化了这个工具函数,那就是Object.create方法。ES3可以使用以下方式实现:
코드 사본은 다음과 같습니다.
Object.create ||
Object.create = function (parent, properties) {
function F() {}
F.prototype = parent;
var child = new F;
for (var k in properties) {
child[k] = properties[k].value;
}
return child;
}
// 용법
var foo = {x: 10};
var bar = Object.create(foo, {y: {value: 20}});
console.log(bar.x, bar.y); // 10, 20
此外,所有模仿现在基于类的经典继承方式都是根据这个原则实现的,现在可以看到,它实际上不是基于类的继承,而是连接原型的一个很方便的代码重用。
결론적으로
本章内容已经很充分和详细了,希望这些资料对你有用,并且消除你对ECMAScript的疑问,如果你有任何问题,请留言,我们一起讨论。