JavaScript 시리즈 심층 이해(18): 객체지향 프로그래밍의 ECMAScript 구현_기본 지식

WBOY
풀어 주다: 2016-05-16 16:11:12
원래의
1347명이 탐색했습니다.

소개

이번 장은 ECMAScript의 객체지향 구현에 대한 두 번째 부분입니다. 첫 번째 부분에서는 CEMAScript의 소개와 비교를 다루었습니다. 첫 번째 부분을 읽지 않으셨다면 이 장을 진행하시기 바랍니다. 이 글은 너무 길기 때문에(35페이지) 첫 번째 글을 읽어보세요.

영문 원문:http://dmitrysoshnikov.com/ecmascript/chapter-7-2-oop-ecmascript-implementation/
참고: 이 기사의 길이로 인해 오류가 불가피하며 지속적으로 수정됩니다.

소개에서 ECMAScript로 확장했습니다. 이제 OOP 구현을 알았으니 정확하게 정의해 보겠습니다.

코드 복사 코드는 다음과 같습니다.

ECMAScript는 프로토타입 기반 상속 위임을 지원하는 객체지향 프로그래밍 언어입니다.

ECMAScript는 프로토타입 기반 위임 상속을 지원하는 객체 지향 언어입니다.
가장 기본적인 데이터 유형부터 분석하겠습니다. 가장 먼저 이해해야 할 것은 ECMAScript는 엔터티를 구별하기 위해 기본 값과 객체를 사용한다는 것입니다. 따라서 일부 기사에서 "JavaScript에서는 모든 것이 객체입니다"라고 말하는 것은 잘못되었습니다. 오른쪽), 원시값은 여기서 논의할 데이터 유형 중 일부입니다.

데이터 유형

ECMAScript는 동적으로 유형을 변환할 수 있는 동적으로 약한 유형의 언어이지만 여전히 데이터 유형이 있습니다. 즉, 객체는 실제 유형에 속해야 합니다.
표준 사양에는 9가지 데이터 유형이 정의되어 있지만 ECMAScript 프로그램에서 직접 액세스할 수 있는 데이터 유형은 정의되지 않음, Null, Boolean, String, Number 및 Object입니다.

다른 세 가지 유형은 구현 수준에서만 액세스할 수 있으며(ECMAScript 객체는 이러한 유형을 사용할 수 없음) 사양에서 일부 작동 동작을 설명하고 중간 값을 저장하는 데 사용됩니다. 이 3가지 유형은 참조, 목록 및 완성입니다.

따라서 참조는 delete, typeof 및 this와 같은 연산자를 설명하는 데 사용되며 기본 객체와 속성 이름을 포함합니다. 목록은 매개변수 목록의 동작을 설명합니다(새 표현식 및 함수 호출에서 사용됨). break, continue, return 및 throw 문의 동작을 설명합니다.

기본 값 유형
ECMAScript 프로그램에서 사용되는 6가지 데이터 유형을 살펴보면 처음 5개는 Undefine, Null, Boolean, String, Number 및 Object를 포함한 기본 값 유형입니다.
기본 값 유형 예:

코드 복사 코드는 다음과 같습니다.

var a = 정의되지 않음;
var b = null;
var c = true;
var d = '테스트';
변수 e = 10;

이 값은 하단 레이어에 직접 구현됩니다. 객체가 아니므로 프로토타입이나 생성자가 없습니다.

삼촌의 메모: 이러한 기본 값은 우리가 일반적으로 사용하는 값(Boolean, String, Number, Object)과 이름이 유사하지만 동일한 것은 아닙니다. 따라서 typeof(Boolean)의 결과는 함수이므로 Boolean, String 및 Number 함수에는 프로토타입이 있으므로 typeof(true)와 typeof(Boolean)의 결과는 다릅니다(아래 속성 읽기 및 쓰기 장에서도 언급됨). .

어떤 유형의 데이터인지 알고 싶다면 typeof를 사용하는 것이 가장 좋습니다. typeof를 사용하여 null 유형을 판별하는 경우 결과는 객체입니다. 왜? null의 유형이 Null로 정의되어 있기 때문입니다.

코드 복사 코드는 다음과 같습니다.

Alert(typeof null); // "객체"

"object"가 표시되는 이유는 사양에서 typeof 문자열 값이 Null 값에 대해 "object"를 반환하도록 규정하기 때문입니다.

사양에서는 이를 설명할 수 없다고 생각하지만, JavaScript 창시자인 Brendan Eich는 객체를 null 참조로 설정하는 등 정의되지 않은 대신 null이 객체가 나타나는 위치에 주로 사용된다는 점에 주목했습니다. 그러나 일부 문서의 일부 사람들은 이를 버그로 간주하고 Brendan Eich도 토론에 참여했다는 버그를 버그 목록에 올렸습니다. 그 결과 typeof null의 결과가 object로 설정되었습니다(262-3에도 불구하고). 표준에서는 null의 유형을 Null로 정의하고 있으며, 262-5에서는 null의 유형이 객체라고 표준을 수정했습니다.

객체 유형

다음으로, Object 유형(Object 생성자와 혼동하지 마세요. 지금은 추상 유형만 논의하고 있습니다)은 ECMAScript 객체를 설명하는 유일한 데이터 유형입니다.

객체는 키-값 쌍의 순서가 지정되지 않은 모음입니다.
객체는 키-값 쌍의 정렬되지 않은 모음입니다

객체의 키 값을 속성이라고 하며, 속성은 기본 값과 기타 객체를 담는 컨테이너입니다. 속성의 값이 함수인 경우 이를 메소드라고 부릅니다.

예:

코드 복사 코드는 다음과 같습니다.

var x = { // 객체 "x"에는 a, b, c의 3가지 속성이 있습니다
a: 10, // 원래 값
b: {z: 100}, // 객체 "b"에는 z 속성이 있습니다
c: 함수() { // 함수(메서드)
Alert('메서드 x.c');
}
};

경고(x.a); // 10
경고(x.b); // [객체 객체]
경고(x.b.z); // 100
x.c(); // '메서드 x.c'

동적

17장에서 지적했듯이 ES의 객체는 완전히 동적입니다. 이는 프로그램이 실행되는 동안 객체의 속성을 마음대로 추가, 수정 또는 삭제할 수 있음을 의미합니다.

예:

코드 복사 코드는 다음과 같습니다.

var foo = {x: 10};

//새 속성 추가
foo.y = 20;
console.log(foo); // {x: 10, y: 20}

// 속성 값을 함수로 수정합니다
foo.x = 함수 () {
console.log('foo.x');
};

foo.x(); // 'foo.x'

// 속성 삭제
foo.x 삭제;
console.log(foo); // {y: 20}

일부 속성은 수정할 수 없습니다(읽기 전용 속성, 삭제된 속성 또는 구성할 수 없는 속성). 나중에 속성 속성에서 설명하겠습니다.

또한 ES5 사양에서는 정적 객체를 새로운 속성으로 확장할 수 없으며 해당 속성 페이지를 삭제하거나 수정할 수 없다고 규정하고 있습니다. 이는 Object.freeze(o) 메소드를 적용하여 얻을 수 있는 소위 고정된 객체입니다.

코드 복사 코드는 다음과 같습니다.

var foo = {x: 10};

// 객체 고정
Object.freeze(foo);
console.log(Object.isFrozen(foo)); // true

//수정불가
foo.x = 100;

// 확장할 수 없습니다
foo.y = 200;

//삭제할 수 없습니다
foo.x 삭제;

console.log(foo); // {x: 10}

ES5 사양에서는 Object.preventExtensions(o) 메서드를 사용하여 확장을 방지하거나 Object.defineProperty(o) 메서드를 사용하여 속성을 정의합니다.

코드 복사 코드는 다음과 같습니다.

var foo = {x : 10};

Object.defineProperty(foo, "y", {
값: 20,
쓰기 가능: false, // 읽기 전용
구성 가능: false // 구성 불가능
});

//수정불가
foo.y = 200;

//삭제할 수 없습니다
foo.y를 삭제합니다. // false

// 예방 및 통제 확장
Object.preventExtensions(foo);
console.log(Object.isExtensible(foo)); // false

//새 속성을 추가할 수 없습니다
foo.z = 30;

console.log(foo); {x: 10, y: 20}

내장 객체, 네이티브 객체, 호스트 객체

명세에서는 내장 객체, 요소 객체, 호스트 객체도 구분한다는 점에 유의할 필요가 있습니다.

내장 객체와 요소 객체는 ECMAScript 사양에 의해 정의되고 구현되며 둘 사이의 차이는 미미합니다. ECMAScript에 의해 구현된 모든 객체는 기본 객체입니다(일부는 내장 객체이고 일부는 사용자 정의 객체와 같이 프로그램이 실행될 때 생성됩니다). 내장 객체는 프로그램이 시작되기 전에 ECMAScript에 내장된 기본 객체의 하위 집합입니다(예:parseInt, Match 등). 모든 호스트 개체는 호스트 환경(일반적으로 브라우저)에서 제공되며 창, 경고 등이 포함될 수 있습니다.

호스트 객체는 사양의 의미를 완전히 준수하여 ES 자체로 구현될 수 있습니다. 이러한 관점에서 볼 때 (이론적으로 가능한 한 빨리) "네이티브 호스트" 개체라고 부를 수 있지만 사양에서는 "네이티브 호스트" 개체의 개념을 정의하지 않습니다.

부울, 문자열 및 숫자 객체

또한 사양에서는 일부 기본 특수 패키징 클래스도 정의합니다.

1. 부울 객체
2. 문자열 객체
3. 디지털 객체

이러한 개체는 해당 내장 생성자를 통해 생성되며 내부 속성으로 기본 값을 포함하며 기본 값을 그 반대로 변환할 수 있습니다.

코드 복사 코드는 다음과 같습니다.

var c = new Boolean(true);
var d = new String('test');
var e = 새 숫자(10);

//원래 값으로 변환
// 새 키워드 없이 함수 사용
с = 부울(c);
d = 문자열(d);
e = 숫자(e);

// 객체로 다시 변환
с = 객체(c);
d = 객체(d);
e = 객체(e);

또한 특수 내장 생성자에 의해 생성된 객체도 있습니다: Function(함수 객체 생성자), Array(배열 생성자) RegExp(정규식 생성자), Math(수학 모듈), Date(날짜 생성자)(컨테이너) 등. 이러한 객체는 Object 객체 유형의 값이기도 합니다. 서로의 차이점은 아래에서 설명하는 내부 속성에 의해 관리됩니다.

직역

객체, 배열 및 정규식이라는 세 가지 개체의 값에는 개체 초기화 프로그램, 배열 초기화 프로그램, 정규 표현식이라는 약식 식별자가 있습니다.

코드 복사 코드는 다음과 같습니다.

// new Array(1, 2, 3)과 동일합니다.
// 또는 배열 = new Array();
// 배열[0] = 1;
// 배열[1] = 2;
// 배열[2] = 3;
var 배열 = [1, 2, 3];

//
과 동일 // var object = new Object();
//객체.a = 1;
//객체.b = 2;
// object.c = 3;
var 객체 = {a: 1, b: 2, c: 3};

// new RegExp("^\d $", "g")
와 동일 var re = /^d $/g;

위의 세 개체가 새 유형에 다시 할당되면 새로 할당된 유형에 따라 후속 구현 의미가 사용됩니다. 예를 들어 현재 Rhino 구현과 SpiderMonkey 1.7의 이전 버전에서는 객체는 new 키워드의 생성자를 사용하여 성공적으로 생성되었지만 일부 구현(현재 Spider/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, "[객체 번호]"

바 = [];

// Rhino, SpiderMonkey 1.7 - 0, "[개체 번호]"
// 기타: 여전히 "", "[객체 객체]"
Alert([bar, getClass.call(bar)]);

// 그러나 RegExp의 경우 리터럴의 의미는 변경되지 않습니다. 리터럴의 의미
// 테스트된 모든 구현에서 변경되지 않았습니다

RegExp = 숫자;

foo = 새로운 RegExp;
Alert([foo, getClass.call(foo)]); // 0, "[객체 번호]"

바 = /(?!)/g;
Alert([bar, getClass.call(bar)]); // /(?!)/g, "[object RegExp]"

정규식 리터럴 및 RegExp 객체

다음 두 예에서 정규 표현식의 의미는 사양의 세 번째 버전에서 동일합니다. 정규 표현식 리터럴은 한 문장에만 존재하며 구문 분석 단계에서 생성되지만 RegExp 생성자에 의해 생성됩니다. is 새로운 개체이므로 일부 문제가 발생할 수 있습니다. 예를 들어 테스트 중에 lastIndex 값이 잘못되었습니다.

코드 복사 코드는 다음과 같습니다.

for (var k = 0; k var re = /ecma/g;
경고(re.lastIndex); // 0, 4, 0, 4
Alert(re.test("ecmascript")); // 참, 거짓, 참, 거짓
}

// 비교

for (var k = 0; k var re = new RegExp("ecma", "g");
경고(re.lastIndex); // 0, 0, 0, 0
Alert(re.test("ecmascript")); // 참, 참, 참, 참
}

참고: 그러나 이러한 문제는 ES 사양 5판에서 수정되었습니다. 리터럴 기반이든 생성자 기반이든 일반 규칙은 새 객체를 생성합니다.

연관배열

다양한 텍스트 정적 토론, JavaScript 객체(종종 객체 이니셜라이저 {}를 사용하여 생성됨)를 해시 테이블, 해시 테이블 또는 기타 간단한 이름이라고 합니다. 해시(Ruby 또는 Perl의 개념), 관리 배열(PHP의 개념) , 사전(파이썬의 개념) 등

그러한 용어만 있는데, 그 이유는 주로 구조가 유사하기 때문입니다. 즉, "키-값" 쌍을 사용하여 객체를 저장하는 것은 "연관 배열" 또는 "해시" 이론에 의해 정의된 데이터 구조와 완전히 일치합니다. 테이블". 또한 해시 테이블 추상 데이터 유형은 일반적으로 구현 수준에서 사용됩니다.

그러나 용어에서는 이 개념을 설명하지만 실제로는 ECMAScript의 관점에서 볼 때 실수입니다. ECMAScript에는 하나의 객체와 유형 및 해당 하위 유형만 있으며 이는 "키-값" 쌍 저장소와 다르지 않으므로 이에 대한 특별한 개념은 없습니다. 모든 객체의 내부 속성은 키-값 쌍으로 저장될 수 있기 때문입니다.

코드 복사 코드는 다음과 같습니다.

var a = {x: 10};
a['y'] = 20;
a.z = 30;

var b = 새 숫자(1);
b.x = 10;
b.y = 20;
b['z'] = 30;

var c = 새 함수('');
c.x = 10;
c.y = 20;
c['z'] = 30;

// 잠깐, 모든 객체의 하위 유형 "하위 유형"

또한 ECMAScript에서는 객체가 비어 있을 수 있으므로 여기서 "해시" 개념도 올바르지 않습니다.

코드 복사 코드는 다음과 같습니다.

Object.prototype.x = 10;

var a = {}; // 빈 "해시" 생성

Alert(a["x"]); // 10이지만 비어 있지 않음
경고(a.toString); // 함수

a["y"] = 20; // "해시"에 새로운 키-값 쌍을 추가합니다
경보(a["y"]); // 20

Object.prototype.y = 20; // 프로토타입 속성 추가

삭제 a["y"] // 삭제
Alert(a["y"]); // 하지만 여기의 키와 값은 여전히 ​​​​- 20

ES5 표준에서는 프로토타입 없이 객체를 생성할 수 있습니다(Object.create(null) 메서드를 사용하여 구현). 이러한 관점에서 이러한 객체를 해시 테이블이라고 부를 수 있습니다.

코드 복사 코드는 다음과 같습니다.

var aHashTable = Object.create(null);
console.log(aHashTable.toString); // 정의되지 않음

또한 일부 속성에는 특정 getter/setter 메서드가 있으므로 이 개념에 대한 혼란을 초래할 수도 있습니다.
코드 복사 코드는 다음과 같습니다.

var a = new String("foo");
a['길이'] = 10;
경고(a['길이']) // 3

그러나 "해시"가 "프로토타입"(예: Ruby 또는 Python에서 해시 객체를 위임하는 클래스)을 가질 수 있다고 간주하더라도 ECMAScript에서는 둘 사이에 차이가 있기 때문에 이 용어는 올바르지 않습니다. 의미론적 차이는 없습니다(즉, 점 표기법 a.b 및 a["b"] 표기법 사용).

ECMAScript에서 "속성 속성"의 개념과 의미는 "키", 배열 인덱스 및 메서드와 분리되지 않습니다. 여기에서 모든 객체의 속성을 읽고 쓰는 것은 동일한 규칙을 따라야 합니다. 프로토타입 체인을 확인하세요.

다음 Ruby 예에서 의미상의 차이를 확인할 수 있습니다.

코드 복사 코드는 다음과 같습니다.

a = {}
a.class # 해시

a.길이 # 0

# 새로운 "키-값" 쌍
a['길이'] = 10;

# 의미상 점은 키가 아닌 속성이나 메서드에 액세스하는 데 사용됩니다

a.길이#1

#인덱서가 해시의 키에 액세스합니다

a['길이'] # 10

# 기존 객체에 Hash 클래스를 동적으로 선언하는 것과 유사합니다
# 그런 다음 새 속성이나 메서드를 선언합니다

클래스 해시
데프 z
100



# 새로운 속성에 액세스할 수 있습니다

a.z # 100

# 하지만 "키"는 아닙니다

a['z'] # 없음

ECMA-262-3 표준은 "해시"(및 유사) 개념을 정의하지 않습니다. 그러나 그러한 구조론이 있다면 그 이름을 따서 사물의 이름을 붙이는 것이 가능하다.

객체 변환

객체를 기본 값으로 변환하려면 valueOf 메서드를 사용할 수 있습니다. 앞서 말했듯이 함수의 생성자가 (일부 유형의 경우) 함수로 호출되지만 new 키워드가 사용되지 않는 경우 객체는 기본 값으로 변환됩니다. 이는 암시적 valueOf 메서드 호출과 동일합니다.

코드 복사 코드는 다음과 같습니다.

var a = 새 숫자(1);
var 원시A = Number(a); // 암시적 "valueOf" 호출
var alsoPrimitiveA = a.valueOf(); // 명시적 호출

경고([
typeof a, // "객체"
typeof 프리미티브A, // "숫자"
typeof alsoPrimitiveA // "숫자"
]);

이 접근 방식을 사용하면 객체가 다음과 같은 다양한 작업에 참여할 수 있습니다.
코드 복사 코드는 다음과 같습니다.

var a = 새 숫자(1);
var b = 새 숫자(2);

경고(a b); // 3

// 심지어

var c = {
x:10,
예: 20,
valueOf: 함수 () {
this.x this.y를 반환하세요.
}
};

var d = {
x: 30,
y: 40,
//c
의 valueOf 함수와 동일 valueOf: c.valueOf
};

경고(cd); // 100

valueOf의 기본값은 객체 유형에 따라 변경됩니다(재정의되지 않은 경우). 일부 객체의 경우 다음을 반환합니다(예: Object.prototype.valueOf() 및 계산된 값). .valueOf()는 날짜와 시간을 반환합니다.

코드 복사 코드는 다음과 같습니다.

var a = {};
Alert(a.valueOf() === a); // true, "valueOf"는 이것을 반환합니다

var d = new Date();
경보(d.valueOf()); // 시간
Alert(d.valueOf() === d.getTime()) // true

또한 객체에는 보다 원시적인 표현, 즉 문자열 표현이 있습니다. 이 toString 메소드는 안정적이며 특정 작업에 자동으로 사용됩니다.
코드 복사 코드는 다음과 같습니다.

var a = {
valueOf: 함수 () {
100을 반환합니다;
},
toString: 함수 () {
'__test'를 반환합니다.
}
};

// 이 작업에서는 toString 메서드가 자동으로 호출됩니다.
경고(a); // "__테스트"

// 하지만 여기서는 valueOf() 메서드가 호출됩니다
경고(a 10) // 110

// 단, valueOf가 삭제되면
// toString은 자동으로 다시 호출될 수 있습니다
a.valueOf; 삭제
경고(a 10); // "_test10"

Object.prototype에 정의된 toString 메서드는 아래에서 설명할 내부 [[Class]] 속성 값을 반환하는 특별한 의미를 갖습니다.

기본값으로 변환하는 것(ToPrimitive)에 비해 값을 객체형으로 변환하는 것에도 변환 사양(ToObject)이 있습니다.

명시적인 방법은 내장된 Object 생성자를 ToObject를 호출하는 함수로 사용하는 것입니다(new 키워드와 다소 유사).

코드 복사 코드는 다음과 같습니다.

var n = Object(1); // [객체 번호]
var s = Object('test'); // [객체 문자열]

//비슷한 것으로 new 연산자를 사용할 수도 있습니다
var b = new Object(true); // [객체 부울]

// new Object 매개변수를 사용하면 간단한 객체가 생성됩니다.
var o = new Object(); // [객체 객체]

// 매개변수가 기존 객체인 경우
// 생성 결과는 단순히 객체를 반환하는 것입니다
var a = [];
Alert(a === new Object(a)); // true
Alert(a === Object(a)); // 참

내장 생성자 호출에 대한 일반적인 규칙은 없습니다. new 연산자를 사용할지 여부는 생성자에 따라 다릅니다. 예를 들어 배열이나 함수는 new 연산자를 사용하는 생성자로 사용되거나 new 연산자를 사용하지 않는 간단한 함수로 사용될 때 동일한 결과를 생성합니다.

코드 복사 코드는 다음과 같습니다.

var a = Array(1, 2, 3); // [객체 배열]
var b = new Array(1, 2, 3); // [객체 배열]
var c = [1, 2, 3]; // [객체 배열]

var d = Function(''); // [객체 함수]
var e = new Function(''); // [객체 함수]

일부 연산자를 사용하면 명시적 및 암시적 변환도 있습니다.
코드 복사 코드는 다음과 같습니다.

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. {Internal} - 내부 속성, 이름 없음(구현 수준에서만 사용됨), 이러한 속성은 ECMAScript에서 액세스할 수 없습니다.

ES5에서 {ReadOnly}, {DontEnum} 및 {DontDelete}는 [[Writable]], [[Enumerable]] 및 [[Configurable]]로 이름이 바뀌었으며 Object.defineProperty 또는 이와 유사한 것을 통해 수동으로 전달할 수 있습니다. 이러한 속성을 관리하는 방법.

코드 복사 코드는 다음과 같습니다.

var foo = {};

Object.defineProperty(foo, "x", {
값: 10,
쓰기 가능: true, // 즉, {ReadOnly} = false
열거 가능: false, // 즉, {DontEnum} = true
configurable: true // 즉, {DontDelete} = false
});

console.log(foo.x); // 10

// 설명자를 통해 기능 세트 속성
을 가져옵니다. var desc = Object.getOwnPropertyDescriptor(foo, "x");

console.log(desc.enumerable); // false
console.log(desc.writable); // 참
// 잠깐

내부 속성 및 메서드

객체는 ECMAScript 프로그램에서 직접 액세스할 수 없는 내부 속성(구현 수준의 일부)을 가질 수도 있습니다(그러나 아래에서 볼 수 있듯이 일부 구현에서는 이러한 속성에 대한 액세스를 허용합니다). 이러한 속성은 중첩된 대괄호[[ ]]를 통해 액세스됩니다. 그 중 일부를 살펴보겠습니다. 이러한 속성에 대한 설명은 사양에서 확인할 수 있습니다.

모든 객체는 다음과 같은 내부 속성과 메서드를 구현해야 합니다.

1.[[프로토타입]] - 객체의 프로토타입 (아래에서 자세히 소개하겠습니다)
2.[[클래스]] - 객체를 구별하는 데 사용되는 문자열 객체(예: 객체 배열, 함수 객체, 함수 등)의 표현입니다.
3.[[Get]]——속성값을 얻는 방법
4.[[Put]]——속성값 설정 방법
5.[[CanPut]]——속성이 쓰기 가능한지 확인
6.[[HasProperty]]——객체에 이미 이 속성이 있는지 확인
7.[[삭제]]——객체에서 속성을 삭제합니다
8.[[DefaultValue]]는 객체의 원래 값을 반환합니다(valueOf 메서드를 호출하면 일부 객체는 TypeError 예외가 발생할 수 있습니다).
내부 속성 [[Class]]의 값은 Object.prototype.toString() 메서드를 통해 간접적으로 얻을 수 있으며, 이 메서드는 "[object " [[Class]] "]" 문자열을 반환해야 합니다. 예:

코드 복사 코드는 다음과 같습니다.

var getClass = Object.prototype.toString;

getClass.call({}); // [객체 객체]
getClass.call([]); // [객체 배열]
getClass.call(new Number(1)); // [객체 번호]
// 잠깐

이 함수는 주로 객체를 확인할 때 사용하는데, 사양에 따르면 호스트 객체의 [[Class]]는 내장 객체의 [[Class]] 속성 값을 포함해 어떤 값이라도 될 수 있다고 나와 있으므로 이론적으로는 100% 정확하다고 보장할 수 없습니다. 예를 들어 document.childNodes.item(...) 메서드의 [[Class]] 속성은 IE에서 "String"을 반환하지만 다른 구현에서는 "Function"을 반환합니다.
코드 복사 코드는 다음과 같습니다.

// IE에서는 - "문자열", 기타 - "함수"
Alert(getClass.call(document.childNodes.item));

생성자

그래서 위에서 언급했듯이 ECMAScript의 객체는 소위 생성자를 통해 생성됩니다.

생성자는 새로 생성된 객체를 생성하고 초기화하는 함수입니다.
생성자는 새로 생성된 객체를 생성하고 초기화하는 함수입니다.
객체 생성(메모리 할당)은 생성자의 내부 메서드 [[Construct]]에 의해 처리됩니다. 이 내부 메서드의 동작은 잘 정의되어 있으며 모든 생성자는 이 메서드를 사용하여 새 개체에 대한 메모리를 할당합니다.

초기화는 생성자의 내부 메소드 [[Call]]을 담당하는 새 객체에 대해 이 함수를 호출하여 관리됩니다.

사용자 코드는 초기화 단계에서만 액세스할 수 있지만 초기화 단계에서는 다른 개체를 반환할 수 있습니다(첫 번째 단계에서 생성된 tihs 개체 무시).

코드 복사 코드는 다음과 같습니다.

함수 A() {
// 새로 생성된 객체 업데이트
this.x = 10;
// 하지만 다른 객체를 반환합니다
[1, 2, 3]을 반환합니다.
}

var a = new A();
console.log(a.x, a); 정의되지 않음, [1, 2, 3]

15장 함수 - 함수 생성 알고리즘을 참조하면 함수가 [[Construct]] ] 및 [[Call]] ] 속성과 표시된 프로토타입 프로토타입 속성( future 객체의 프로토타입(참고: NativeObject는 기본 객체에 대한 규칙이며 아래 의사 코드에서 사용됩니다).

코드 복사 코드는 다음과 같습니다.

F = 새로운 NativeObject();

F.[[클래스]] = "함수"

.... // 기타 속성

F.[[Call]] = <함수 참조> //함수 자체

F.[[Construct]] = InternalConstructor // 일반 내부 생성자

.... // 기타 속성

// F 생성자에 의해 생성된 객체 프로토타입
__objectPrototype = {};
__objectPrototype.constructor = F // {DontEnum}
F.prototype = __objectPrototype

[[Call]]]은 [[Class]] 속성(여기서는 "Function"과 동일) 이외의 개체를 구별하는 주요 방법이므로 개체의 내부 [[Call]] 속성을 기능. 이러한 객체에 대해 typeof 연산자를 사용하면 "함수"가 반환됩니다. 그러나 이는 주로 기본 객체와 관련이 있습니다. 어떤 경우에는 값을 얻기 위해 typeof를 사용하는 구현이 다릅니다. 예: IE에서 window.alert(...)의 효과:

코드 복사 코드는 다음과 같습니다.

// IE 브라우저 - "Object", "object", 기타 브라우저 - "Function", "function"
경고(Object.prototype.toString.call(window.alert));
Alert(typeof window.alert); // "객체"

내부 메소드 [[Construct]]는 new 연산자와 함께 생성자를 사용하여 활성화됩니다. 앞서 말했듯이 이 메소드는 메모리 할당 및 객체 생성을 담당합니다. 매개변수가 없으면 생성자를 호출하는 괄호도 생략할 수 있습니다.

코드 복사 코드는 다음과 같습니다.

function A(x) { // 생성자 А
this.x = x || 10;
}

// 매개변수가 전달되지 않으면 대괄호를 생략할 수 있습니다.
var a = new A; // 또는 new A();
경고(a.x); // 10

//x 매개변수를 명시적으로 전달
var b = 새로운 A(20);
경고(b.x); // 20

또한 생성자(초기화 단계)의 shis가 새로 생성된 객체로 설정된다는 것도 알고 있습니다.

객체 생성 알고리즘을 연구해 보겠습니다.

객체 생성 알고리즘

내부 메소드 [[Construct]]의 동작은 다음과 같이 설명할 수 있습니다.

코드 복사 코드는 다음과 같습니다.

F.[[구성]](초기 매개변수):

O = 새로운 NativeObject();

// [[Class]] 속성은 "Object"로 설정됩니다
O.[[클래스]] = "객체"

// F.prototype을 참조할 때 g
객체를 가져옵니다. var __objectPrototype = F.prototype;

// __objectPrototype이 객체인 경우:
O.[[프로토타입]] = __objectPrototype
// 그렇지 않은 경우:
O.[[프로토타입]] = Object.prototype;
// 여기서 O.[[프로토타입]]은 Object 객체의 프로토타입입니다

// F.[[Call]]
은 새로 생성된 객체를 초기화할 때 적용됩니다. // 새로 생성된 객체 O
에 설정합니다. //매개변수는 F
의 초기 매개변수와 동일합니다. R = F.[[Call]](initialParameters); this === O;
// 여기서 R은 [[Call]]
의 반환 값입니다. // JS에서 다음과 같이 봅니다:
// R = F.apply(O,initialParameters);

// R이 객체인 경우
R 반환
// 그렇지 않으면
O를 돌려주세요

두 가지 주요 기능을 참고하세요.

1. 먼저 새로 생성된 객체의 프로토타입은 현재 시점의 함수의 프로토타입 속성에서 가져옵니다(즉, 동일한 생성자에 의해 생성된 두 객체의 프로토타입은 의 프로토타입 속성이 다를 수 있음을 의미). 기능도 다를 수 있습니다).
2. 둘째, 위에서 언급한 것처럼 객체가 초기화될 때 [[Call]]이 객체를 반환하는 경우 이는 정확히 전체 new 연산자에 사용된 결과입니다.

코드 복사 코드는 다음과 같습니다.

함수 A() {}
A.prototype.x = 10;

var a = new A();
Alert(a.x); // 10 – 프로토타입에서
가져오기
// .prototype 속성을 새 객체로 설정합니다.
// .constructor 속성을 명시적으로 선언하는 이유는 아래에 설명되어 있습니다
A.프로토타입 = {
생성자: A,
y: 100
};

var b = 새로운 A();
// 객체 "b"에는 새로운 속성이 있습니다
경고(b.x); // 정의되지 않음
Alert(b.y); // 100 – 프로토타입에서
가져오기
// 그러나 객체의 프로토타입은 여전히 ​​원래 결과를 얻을 수 있습니다
Alert(a.x); // 10 - 프로토타입에서
가져오기
함수 B() {
this.x = 10;
새로운 배열()을 반환합니다.
}

// "B" 생성자가 반환하지 않는 경우(또는 이를 반환하는 경우)
// 그러면 이 객체를 사용할 수 있으나, 다음과 같은 경우에는 array
가 반환됩니다. var b = 새로운 B();
경고(b.x); // 정의되지 않음
Alert(Object.prototype.toString.call(b)); // [객체 배열]

프로토타입을 자세히 살펴보겠습니다

시제품

모든 객체에는 프로토타입이 있습니다(일부 시스템 객체 제외). 프로토타입 통신은 내부적이고 암시적이며 직접 액세스할 수 없는 [[Prototype]] 프로토타입 속성을 통해 수행됩니다. 프로토타입은 객체이거나 null 값일 수 있습니다.

속성 생성자

위 예제에는 두 가지 중요한 지식 포인트가 있습니다. 첫 번째는 함수 생성 알고리즘에서 생성자 속성이 프로토타입 속성으로 설정된다는 것을 알고 있습니다. 함수 생성 단계에서 생성자 속성의 값은 함수 자체에 대한 중요한 참조입니다.

코드 복사 코드는 다음과 같습니다.

함수 A() {}
var a = new A();
Alert(a.constructor); // 함수 A() {}, 위임에 의한
Alert(a.constructor === A); // true

일반적으로 이 경우 오해가 있습니다. 생성자 생성자 속성은 새로 생성된 객체 자체의 속성으로 잘못되었지만, 보시다시피 이 속성은 프로토타입에 속하며 상속을 통해 액세스됩니다.

생성자 속성의 인스턴스를 상속함으로써 프로토타입 객체에 대한 참조를 간접적으로 얻을 수 있습니다.

코드 복사 코드는 다음과 같습니다.

함수 A() {}
A.prototype.x = 새 번호(10);

var a = new A();
Alert(a.constructor.prototype); // [객체 객체]

Alert(a.x); // 10, 프로토타입을 통해
// a.[[Prototype]].x
와 동일한 효과 경고(a.constructor.prototype.x) // 10

Alert(a.constructor.prototype.x === a.x) // true

그러나 함수의 생성자와 프로토타입 속성은 객체가 생성된 후에 재정의될 수 있다는 점에 유의하세요. 이 경우 개체는 위에서 설명한 메커니즘을 잃습니다. 함수의 프로토타입 속성(새 개체 추가 또는 기존 개체 수정)을 통해 요소의 프로토타입을 편집하면 인스턴스에 새로 추가된 속성이 표시됩니다.

그러나 (새 객체를 할당하여) 함수의 프로토타입 속성을 완전히 변경하면 우리가 생성한 객체에 생성자 속성이 포함되어 있지 않기 때문에 원래 생성자에 대한 참조가 손실됩니다.

코드 복사 코드는 다음과 같습니다.

함수 A() {}
A.프로토타입 = {
x:10
};

var a = new A();
경고(a.x); // 10
Alert(a.constructor === A); // 거짓!

따라서 함수에 대한 프로토타입 참조를 수동으로 복원해야 합니다.
코드 복사 코드는 다음과 같습니다.

함수 A() {}
A.프로토타입 = {
생성자: A,
x:10
};

var a = new A();
경고(a.x); // 10
Alert(a.constructor === A); // true

생성자 속성을 수동으로 복원했지만 원래 손실된 프로토타입과 비교하면 {DontEnum} 기능을 더 이상 사용할 수 없습니다. 즉, A.prototype의 for..in 루프 문은 지원되지 않지만, 사양 5판에서는 [[Enumerable]] 속성을 통해 열거 가능한 열거 가능 상태를 제어하는 ​​기능을 제공합니다.

코드 복사 코드는 다음과 같습니다.

var foo = {x: 10};

Object.defineProperty(foo, "y", {
값: 20,
열거 가능: false // 일명 {DontEnum} = true
});

console.log(foo.x, foo.y); // 10, 20

for (var k 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 // 거짓
);

명시적 프로토타입 및 암시적 [[Prototype]] 속성

일반적으로 함수의 프로토타입 속성을 통해 객체의 프로토타입을 명시적으로 참조하는 것은 잘못된 객체, 즉 객체의 [[Prototype]] 속성을 참조합니다.

a.[[프로토타입]] ----> 프로토타입 <---- A.프로토타입

또한 인스턴스의 [[Prototype]] 값은 실제로 생성자의 프로토타입 속성에서 가져옵니다.

그러나 프로토타입 속성을 제출해도 이미 생성된 객체의 프로토타입에는 영향을 미치지 않습니다(생성자의 프로토타입 속성이 변경되는 경우에만 영향을 받습니다). 즉, 새로 생성된 객체만 새 프로토타입을 갖게 됩니다. 이미 생성된 객체는 여전히 원래의 이전 프로토타입을 참조하여 새 프로토타입을 갖게 됩니다(이 프로토타입은 더 이상 수정할 수 없습니다).

코드 복사 코드는 다음과 같습니다.

// A.prototype 프로토타입을 수정하기 전의 상황
a.[[프로토타입]] ----> 프로토타입 <---- A.프로토타입

//수정 후
A.prototype ----> 새로운 프로토타입 // 새로운 객체는 이 프로토타입을 갖게 됩니다
a.[[프로토타입]] ----> 프로토타입 // 원본 프로토타입 부팅

예:

코드 복사 코드는 다음과 같습니다.

함수 A() {}
A.prototype.x = 10;

var a = new A();
경고(a.x); // 10

A.프로토타입 = {
생성자: A,
x:20
y: 30
};

// 객체 a는 암시적 [[Prototype]] 참조를 통해 원유의 프로토타입에서 얻은 값입니다.
경고(a.x); // 10
경고(a.y) // 정의되지 않음

var b = 새로운 A();

// 하지만 새 객체는 새 프로토타입에서 얻은 값입니다
경고(b.x); // 20
경보(b.y) // 30

따라서 일부 기사에서는 "프로토타입을 동적으로 수정하면 모든 개체에 영향을 미치고 모든 개체는 새로운 프로토타입을 갖게 됩니다"라고 말하지만, 새 프로토타입은 프로토타입이 수정된 후에 새로 생성된 개체에만 적용됩니다.

여기서 주요 규칙은 객체가 생성될 때 객체의 프로토타입이 생성되고 이후에도 새 객체로 수정될 수 없다는 것입니다. 여전히 동일한 객체를 참조하는 경우 명시적인 프로토타입을 통해 참조할 수 있습니다. 객체가 생성된 후에는 프로토타입의 속성만 추가하거나 수정할 수 있습니다.

비표준 __proto__ 속성

그러나 SpiderMonkey와 같은 일부 구현에서는 객체의 프로토타입을 참조하기 위해 비표준 __proto__ 명시적 속성을 제공합니다.

코드 복사 코드는 다음과 같습니다.

함수 A() {}
A.prototype.x = 10;

var a = new A();
경고(a.x); // 10

var __newPrototype = {
생성자: A,
x: 20,
y: 30
};

//새 객체 참조
A.prototype = __newPrototype;

var b = 새로운 A();
경고(b.x); // 20
경고(b.y); // 30

// "a" 객체는 여전히 이전 프로토타입을 사용합니다
경고(a.x); // 10
경고(a.y); // 정의되지 않음

// 프로토타입을 명시적으로 수정
a.__proto__ = __newPrototype;

// 이제 "а" 개체는 새 개체를 참조합니다.
경고(a.x); // 20
경고(a.y); // 30

ES5는 객체의 [[Prototype]] 속성(인스턴스의 초기 프로토타입)을 직접 반환하는 Object.getPrototypeOf(O) 메서드를 제공합니다. 그러나 __proto__와 비교하면 getter일 뿐이며 설정 값을 허용하지 않습니다.
코드 복사 코드는 다음과 같습니다.

var foo = {};
Object.getPrototypeOf(foo) == Object.prototype; // true

생성자에 독립적인 객체
인스턴스의 프로토타입은 생성자 및 생성자의 프로토타입 속성과 독립되어 있기 때문에 주요 작업(객체 생성)이 완료된 후 생성자를 삭제할 수 있습니다. 프로토타입 객체는 [[Prototype]] 속성을 참조하여 계속 존재합니다.

코드 복사 코드는 다음과 같습니다.

함수 A() {}
A.prototype.x = 10;

var a = new A();
경고(a.x); // 10

// A를 null로 설정 - 참조 생성자 표시
A = null;

// 하지만 .constructor 속성이 변경되지 않은 경우
// 이를 통해 객체를 생성할 수 있습니다
var b = new a.constructor();
경고(b.x); // 10

// 암시적 참조도 삭제됩니다
a.constructor.prototype.constructor 삭제;
b.constructor.prototype.constructor 삭제;

// 더 이상 A의 생성자를 통해 객체를 생성할 수 없습니다.
// 하지만 이 두 객체에는 여전히 자체 프로토타입이 있습니다
경고(a.x); // 10
경고(b.x); // 10

instanceof 연산자의 특징
instanceof 연산자와 관련된 생성자의 프로토타입 속성을 통해 참조 프로토타입을 표시합니다. 이 연산자는 생성자가 아닌 프로토타입 체인과 함께 작동합니다. 이를 염두에 두고 객체를 감지할 때 종종 오해가 있습니다.

코드 복사 코드는 다음과 같습니다.

if (foo 인스턴스of Foo) {
...
}

이는 Foo 생성자를 사용하여 객체 foo가 생성되었는지 여부를 감지하는 데 사용되지 않습니다. 모든 인스턴스 오브 연산자에는 foo.[[Prototype]]라는 하나의 객체 속성만 필요하며 프로토타입 체인의 Foo.prototype에서 시작하여 그 존재를 확인합니다. instanceof 연산자는 생성자의 내부 메서드 [[HasInstance]]를 통해 활성화됩니다.

다음 예를 살펴보겠습니다.

코드 복사 코드는 다음과 같습니다.

함수 A() {}
A.prototype.x = 10;

var a = new A();
경고(a.x); // 10

경고(A 인스턴스); // true

// 프로토타입이 null로 설정된 경우
A.prototype = null;

// ..."a"는 여전히 a를 통해 프로토타입에 액세스할 수 있습니다.[[Prototype]]
경고(a.x); // 10

// 하지만, instanceof 연산자는 더 이상 정상적으로 사용할 수 없습니다.
// 생성자의 프로토타입 속성에서 구현되기 때문입니다
Alert(A 인스턴스); // 오류, A.prototype은 객체가 아닙니다

반면, 객체는 생성자에 의해 생성될 수 있지만, 객체의 [[Prototype]] 속성과 생성자의 프로토타입 속성 값이 같은 값으로 설정되면, 인스턴스오브는 true를 반환합니다. 확인 시:

코드 복사 코드는 다음과 같습니다.

함수 B() {}
var b = 새로운 B();

경고(b 인스턴스of B); // true

함수 C() {}

var __proto = {
생성자: C
};

C.prototype = __proto;
b.__proto__ = __proto;

경고(C 인스턴스); // true
경고(b 인스턴스of B); // false

프로토타입은 메서드를 저장하고 속성을 공유할 수 있습니다
프로토타입은 대부분의 프로그램에서 객체 메서드, 기본 상태 및 공유 객체 속성을 저장하는 데 사용됩니다.

사실 객체는 자체 상태를 가질 수 있지만 메서드는 일반적으로 동일합니다. 따라서 메서드는 일반적으로 메모리 최적화를 위해 프로토타입에 정의됩니다. 이는 이 생성자에 의해 생성된 모든 인스턴스가 이 메서드를 공유할 수 있음을 의미합니다.

코드 복사 코드는 다음과 같습니다.

함수 A(x) {
this.x = x || 100;
}

A.prototype = (함수 () {

// 컨텍스트 초기화
// 추가 객체 사용

var _someSharedVar = 500;

함수 _someHelper() {
Alert('내부 도우미: ' _someSharedVar);
}

함수 메서드1() {
Alert('method1: ' this.x);
}

함수 메서드2() {
Alert('method2: ' this.x);
_someHelper();
}

// 프로토타입 자체
복귀 {
​ 생성자: A,
방법1: 방법1,
방법2: 방법2
};

})();

var a = 새로운 A(10);
var b = 새로운 A(20);

a.method1(); // 메소드1: 10
a.method2(); // method2: 10, 내부 도우미: 500

b.method1(); // 메소드1: 20
b.method2(); // 메소드2: 20, 내부 도우미: 500

// 두 객체는 ​​프로토타입에서 동일한 메소드를 사용합니다
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)) {
O.P 반납;
}

// 그렇지 않으면 프로토타입 분석을 계속합니다
var __proto = O.[[프로토타입]];

// 프로토타입이 null이면 정의되지 않음
을 반환합니다. // 가능합니다: 최상위 Object.prototype.[[Prototype]]은 null입니다
if (__proto === null) {
정의되지 않은 상태로 반환;
}

// 그렇지 않으면 프로토타입 체인에서 [[Get]]을 재귀적으로 호출하고 각 레이어의 프로토타입에서 속성을 검색합니다.
// 프로토타입이 null이 될 때까지
return __proto.[[Get]](P)

[[Get]]은 다음과 같은 상황에서도 정의되지 않은 상태를 반환합니다.
코드 복사 코드는 다음과 같습니다.

if (window.someObject) {
...
}

여기서 someObject 속성을 윈도우에서 찾을 수 없으면 프로토타입에서 검색한 다음 프로토타입의 프로토타입에서 검색하는 식으로 찾을 수 없으면 정의에 따라 undefound를 반환합니다.

참고: in 연산자는 속성 검색을 담당할 수도 있습니다(프로토타입 체인도 검색함).

코드 복사 코드는 다음과 같습니다.

if (창의 'someObject') {
...
}

이는 몇 가지 특별한 문제를 방지하는 데 도움이 됩니다. 예를 들어 someObject가 존재하더라도 someObject가 false인 경우 첫 번째 검색 라운드가 실패합니다.

[[Put]] 방법

[[Put]] 메소드는 객체 자체의 속성을 생성 및 업데이트하고 프로토타입에서 동일한 이름의 속성을 마스킹할 수 있습니다.

O.[[Put]](P, V):

코드 복사 코드는 다음과 같습니다.

// 속성에 값을 쓸 수 없으면 종료합니다.
if (!O.[[CanPut]](P)) {
반품;
}

// 객체에 자체 속성이 없으면 생성합니다
// 모든 속성이 false입니다
if (!O.hasOwnProperty(P)) {
createNewProperty(O, P, 속성: {
읽기 전용: 거짓,
DontEnum: 거짓,
삭제하지 마세요: false,
내부: 거짓
});
}

// 속성이 존재하는 경우 값을 설정하지만 속성 속성은 변경하지 마세요.
O.P = 뷔

반품;

예:
코드 복사 코드는 다음과 같습니다.

Object.prototype.x = 100;

var foo = {};
console.log(foo.x); // 100, 상속된 속성

foo.x = 10; // [[넣기]]
console.log(foo.x); // 10개, 고유 속성

foo.x 삭제;
console.log(foo.x); //100으로 재설정, 속성 상속
프로토타입의 읽기 전용 속성은 마스크할 수 없으며 할당 결과는 내부 메서드 [[CanPut]]에 의해 제어됩니다.

// 예를 들어 속성 ​​길이는 읽기 전용이므로 길이를 마스킹해 보겠습니다.

함수 SuperString() {
/* 아무것도 */
}

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의 인덱싱을 통해 활성화됩니다. 속성 식별자가 합법적인 이름인 경우 "."를 통해 액세스할 수 있으며 인덱싱 Party가 동적으로 실행됩니다. 정의된 이름.

코드 복사 코드는 다음과 같습니다.

var a = {testProperty: 10};

Alert(a.testProperty); // 10,
을 클릭하세요. Alert(a['testProperty']); // 10, 인덱스

var propertyName = '속성';
Alert(a['test' propertyName]); // 10, 동적 속성이 색인화됩니다

여기에는 매우 중요한 기능이 있습니다. 속성 접근자는 항상 ToObject 사양을 사용하여 "." 왼쪽의 값을 처리합니다. 이 암시적 변환은 "JavaScript의 모든 것이 객체입니다"라는 말과 관련이 있습니다(그러나 이미 알고 있듯이 JavaScript의 모든 값이 객체는 아닙니다).

속성 접근자를 사용하여 원래 값에 액세스하는 경우 원래 값은 액세스하기 전에 개체(원래 값 포함)로 래핑되고, 속성에 액세스한 후에는 래핑된 개체를 통해 속성에 액세스됩니다. , 래핑된 개체가 삭제됩니다.

예:

코드 복사 코드는 다음과 같습니다.

var a = 10; // 원래 값

// 하지만 메소드는 (객체와 마찬가지로) 접근 가능합니다.
경보(a.toString()); // "10"

// 추가적으로
에 하트 속성을 생성할 수도 있습니다. a.test = 100; // 문제 없을 것 같습니다

// 단, [[Get]] 메소드는 속성값을 반환하지 않고, 정의되지 않은 값을 반환합니다
경고(a.test); // 정의되지 않음

그렇다면 전체 예제의 원래 값은 toString 메서드에 액세스할 수 있지만 새로 생성된 테스트 속성에는 액세스할 수 없는 이유는 무엇입니까?

답은 간단합니다.

우선 앞서 말했듯이 속성 접근자를 사용한 후에는 더 이상 원래 값이 아니라 래핑된 중간 개체가 되며(전체 예제에서는 new Number(a)를 사용함) 여기에 toString 메서드가 전달됩니다. time 프로토타입 체인에서 발견됨:

코드 복사 코드는 다음과 같습니다.

//a.toString() 실행 원리:

1. 래퍼 = 새 번호(a);
2. 래퍼.toString(); // "10"
3. 래퍼 삭제

다음으로, [[Put]] 메소드가 새 속성을 생성할 때 패키지된 객체를 통해서도 수행됩니다.
코드 복사 코드는 다음과 같습니다.

//a.test 실행 원리 = 100:

1. 래퍼 = 새 번호(a);
2. 래퍼.테스트 = 100;
3. 래퍼 삭제

3단계에서는 래핑된 개체가 삭제되고 새로 생성된 속성 페이지가 삭제되어 래핑된 개체 자체가 삭제되는 것을 볼 수 있습니다.

[[Get]]을 사용하여 테스트 값을 가져오면 패키징 개체가 다시 생성되지만 이번에는 래핑된 개체에 더 이상 테스트 속성이 없으므로 정의되지 않음이 반환됩니다.

코드 복사 코드는 다음과 같습니다.

//a.test 실행 원리:

1. 래퍼 = 새 번호(a);
2. 래퍼.테스트; // 정의되지 않음

이 방법은 원래 값을 읽는 방법을 설명합니다. 또한 원래 값이 속성에 액세스하는 데 자주 사용되는 경우 시간 효율성을 고려하여 자주 액세스하지 않거나 그냥 사용하는 경우에는 이를 객체로 직접 대체합니다. 계산 목적으로 이 양식을 유지할 수 있습니다.

상속

우리는 ECMAScript가 프로토타입 기반 위임 상속을 사용한다는 것을 알고 있습니다. 체인과 프로토타입은 이미 프로토타입 체인에서 언급되었습니다. 실제로 위임의 모든 구현과 프로토타입 체인의 검색 및 분석이 [[Get]] 메서드로 압축됩니다.

[[Get]] 메소드를 완전히 이해했다면 JavaScript의 상속 문제는 자명할 것입니다.

저는 포럼에서 JavaScript의 상속에 대해 자주 이야기할 때 이를 보여주기 위해 항상 한 줄의 코드를 사용합니다. 사실 언어는 이미 상속을 기반으로 하기 때문에 어떤 객체나 함수를 만들 필요가 없습니다. 다음과 같습니다.

코드 복사 코드는 다음과 같습니다.

경보(1..toString()); // "1"

우리는 이미 [[Get]] 메서드와 속성 접근자가 어떻게 작동하는지 알고 있습니다. 어떤 일이 일어나는지 살펴보겠습니다.

1. 먼저 원래 값 1부터 새 숫자(1)까지 패키징 개체를 만듭니다.
2. 그런 다음 toString 메소드는 이 패키징 객체

에서 상속됩니다.

왜 상속되나요? ECMAScript의 객체는 자체 속성을 가질 수 있으므로 이 경우 래퍼 객체에는 toString 메서드가 없습니다. 따라서 Number.prototype이라는 원칙을 상속합니다.

미묘한 부분이 있지만 위 예의 점 두 개는 오류가 아닙니다. 첫 번째 점은 소수 부분을 나타내고 두 번째 점은 속성 접근자입니다.

코드 복사 코드는 다음과 같습니다.

1.toString(); // 구문 오류입니다!

(1).toString(); // OK

1..toString(); // OK

1['toString'](); // OK

프로토타입 체인

사용자 정의 객체에 대한 프로토타입 체인을 생성하는 방법을 보여드리겠습니다. 매우 간단합니다.

코드 복사 코드는 다음과 같습니다.

함수 A() {
Alert('A.[[통화]] 활성화됨');
this.x = 10;
}
A.prototype.y = 20;

var a = new A();
Alert([a.x, a.y]); // 10(자체), 20(상속됨)

함수 B() {}

// 가장 최근의 프로토타입 체인 방식은 객체의 프로토타입을 또 다른 새로운 객체로 설정하는 것입니다
B.prototype = new A();

// 프로토타입의 생성자 속성을 복구합니다. 그렇지 않으면 A가 됩니다.
B.prototype.constructor = B;

var b = 새로운 B();
Alert([b.x, b.y]); // 10, 20, 2가 상속됨

// [[Get]] b.x:
// b.x (아니요) -->
// b.[[프로토타입]].x (예) - 10

// [[Get]] b.y
// b.y (아니요) -->
// b.[[프로토타입]].y (아니요) -->
// b.[[프로토타입]].[[프로토타입]].y (예) - 20

// 여기서 b.[[프로토타입]] === B.prototype,
// 및 b.[[프로토타입]].[[프로토타입]] === A.prototype

이 방법에는 두 가지 특징이 있습니다.

먼저 B.prototype에는 x 속성이 포함됩니다. 언뜻 보면 이것이 옳지 않은 것처럼 보일 수 있습니다. x 속성이 A에 정의되어 있고 B 생성자도 이를 예상한다고 생각할 수 있습니다. 프로토타입 상속은 일반적인 상황에서는 문제가 되지 않지만 클래스 기반 상속과 비교하면 B 생성자에는 x 속성이 필요하지 않을 수도 있습니다. 모든 속성은 하위 클래스에 복사됩니다.

그러나 (클래스 기반 상속을 시뮬레이션하기 위해) B 생성자가 생성한 객체에 x 속성을 할당해야 하는 경우 몇 가지 방법이 있으며 그 중 하나는 나중에 보여드리겠습니다.

둘째, 이는 기능은 아니지만 단점 - 서브클래스 프로토타입 생성 시 생성자의 코드도 실행되며 "A.[[Call]] 활성화됨"이라는 메시지가 두 번 표시되는 것을 볼 수 있습니다. - A 생성자를 사용하여 객체를 생성하고 이를 B.prototype 속성에 할당할 때 다른 장면은 객체가 스스로 생성되는 순간입니다!

다음 예는 더 중요합니다. 상위 클래스의 생성자에서 발생한 예외입니다. 실제 개체가 생성될 때 확인해야 할 수도 있지만 분명히 동일한 경우입니다. 즉, 이러한 상위 개체를 다음과 같이 사용할 때도 마찬가지입니다. 프로토타입 뭔가 잘못될 것입니다.

코드 복사 코드는 다음과 같습니다.

함수 A(매개변수) {
if (!param) {
'Param 필수'를 던져보세요.
}
this.param = param;
}
A.prototype.x = 10;

var a = 새로운 A(20);
경보([a.x, a.param]) // 10, 20

함수 B() {}
B.prototype = new A(); // 오류

또한 상위 클래스의 생성자에 코드가 너무 많은 것도 단점입니다.

이러한 "함수"와 문제를 해결하기 위해 프로그래머는 프로토타입 체인의 표준 패턴(아래 참조)을 사용합니다. 주요 목적은 생성자 생성을 중간에 래핑하는 것입니다. 이러한 래핑 생성자의 체인에는 필수 프로토타입이 포함되어 있습니다.

코드 복사 코드는 다음과 같습니다.

함수 A() {
Alert('A.[[통화]] 활성화됨');
this.x = 10;
}
A.prototype.y = 20;

var a = new A();
Alert([a.x, a.y]); // 10(자체), 20(통합)

함수 B() {
// 또는 A.apply(this, 인수)를 사용하세요
B.superproto.constructor.apply(this, 인수);
}

// 상속: 빈 중간 생성자를 통해 프로토타입을 함께 연결합니다
var F = 함수 () {};
F.프로트
관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿