Home >Web Front-end >JS Tutorial >In-depth understanding of JavaScript series (18): ECMAScript implementation of object-oriented programming_Basic knowledge

In-depth understanding of JavaScript series (18): ECMAScript implementation of object-oriented programming_Basic knowledge

WBOY
WBOYOriginal
2016-05-16 16:11:121357browse

Introduction

This chapter is the second part about the object-oriented implementation of ECMAScript. In the first part, we discussed the introduction and comparison of CEMAScript. If you have not read the first part, before proceeding with this chapter, I strongly recommend that you read the first part. 1 article, because this article is too long (35 pages).

English original text:http://dmitrysoshnikov.com/ecmascript/chapter-7-2-oop-ecmascript-implementation/
Note: Due to the length of this article, errors are inevitable and are constantly being revised.

In the introduction, we extended to ECMAScript. Now, when we know its OOP implementation, let’s define it accurately:

Copy code The code is as follows:

ECMAScript is an object-oriented programming language supporting delegating inheritance based on prototypes.

ECMAScript is an object-oriented language that supports prototype-based delegated inheritance.
We will analyze it from the most basic data types. The first thing to understand is that ECMAScript uses primitive values ​​and objects to distinguish entities. Therefore, what some articles say "In JavaScript, everything is an object" is Wrong (not quite right), primitive values ​​are some of the data types we are going to discuss here.

Data Type

Although ECMAScript is a dynamically weakly typed language that can dynamically convert types, it still has data types. In other words, an object must belong to a real type.
There are 9 data types defined in the standard specification, but only 6 can be directly accessed in ECMAScript programs. They are: Undefined, Null, Boolean, String, Number, and Object.

The other three types can only be accessed at the implementation level (ECMAScript objects cannot use these types) and are used in specifications to explain some operational behaviors and save intermediate values. These 3 types are: Reference, List and Completion.

Therefore, Reference is used to explain operators such as delete, typeof, and this, and contains a base object and a property name; List describes the behavior of the parameter list (in new expressions and function calls); Completion is used to explain the behavior of break, continue, return and throw statements.

Primitive value types
Looking back at the 6 data types used in ECMAScript programs, the first 5 are primitive value types, including Undefined, Null, Boolean, String, Number, and Object.
Primitive value type example:

Copy code The code is as follows:

var a = undefined;
var b = null;
var c = true;
var d = 'test';
var e = 10;

These values ​​are implemented directly on the bottom layer. They are not objects, so there is no prototype or constructor.

Uncle’s note: Although these native values ​​​​are similar in name to the ones we usually use (Boolean, String, Number, Object), they are not the same thing. Therefore, the results of typeof(true) and typeof(Boolean) are different, because the result of typeof(Boolean) is function, so functions Boolean, String, and Number have prototypes (also mentioned in the reading and writing attributes chapter below).

If you want to know what type of data it is, it is best to use typeof. There is an example that you need to pay attention to. If you use typeof to determine the type of null, the result is object. Why? Because the type of null is defined as Null.

Copy code The code is as follows:

alert(typeof null); // "object"

The reason "object" is displayed is because the specification stipulates that typeof string value returns "object" for a Null value.

The specification doesn't imagine explaining this, but Brendan Eich (the inventor of JavaScript) noticed that null is mostly used in places where objects appear, as opposed to undefined, such as setting an object to a null reference. However, some people in some documents attributed it to a bug, and put the bug in the bug list that Brendan Eich also participated in the discussion. The result was that the result of typeof null was set to object (despite the 262-3 The standard defines that the type of null is Null, and 262-5 has modified the standard to say that the type of null is object).

Object type

Next, the Object type (not to be confused with the Object constructor, we are only discussing abstract types now) is the only data type that describes ECMAScript objects.

Object is an unordered collection of key-value pairs.
An object is an unordered collection of key-value pairs

The key value of an object is called an attribute, and an attribute is a container for primitive values ​​and other objects. If the value of an attribute is a function we call it a method.

For example:

Copy code The code is as follows:

var x = { // Object "x" has 3 attributes: a, b, c
a: 10, // original value
b: {z: 100}, // Object "b" has an attribute z
c: function () { // function (method)
alert('method x.c');
}
};

alert(x.a); // 10
alert(x.b); // [object Object]
alert(x.b.z); // 100
x.c(); // 'method x.c'

Dynamic

As we pointed out in Chapter 17, objects in ES are completely dynamic. This means that we can add, modify or delete the properties of the object at will while the program is executing.

For example:

Copy code The code is as follows:

var foo = {x: 10};

//Add new attribute
foo.y = 20;
console.log(foo); // {x: 10, y: 20}

// Modify attribute value to function
foo.x = function () {
console.log('foo.x');
};

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

// Delete attribute
delete foo.x;
console.log(foo); // {y: 20}

Some properties cannot be modified - (read-only properties, deleted properties or non-configurable properties). We will explain it later in the attribute properties.

In addition, the ES5 specification stipulates that static objects cannot be extended with new properties, and its property pages cannot be deleted or modified. They are so-called frozen objects, which can be obtained by applying the Object.freeze(o) method.

Copy code The code is as follows:

var foo = {x: 10};

// Freeze object
Object.freeze(foo);
console.log(Object.isFrozen(foo)); // true

// Cannot be modified
foo.x = 100;

// Cannot be expanded
foo.y = 200;

// Cannot delete
delete foo.x;

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

In the ES5 specification, the Object.preventExtensions(o) method is also used to prevent extensions, or the Object.defineProperty(o) method is used to define properties:

Copy code The code is as follows:

var foo = {x : 10};

Object.defineProperty(foo, "y", {
value: 20,
writable: false, // read-only
configurable: false // Not configurable
});

// Cannot be modified
foo.y = 200;

// Cannot delete
delete foo.y; // false

// Prevention and Control Extension
Object.preventExtensions(foo);
console.log(Object.isExtensible(foo)); // false

//Cannot add new attributes
foo.z = 30;

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

Built-in objects, native objects and host objects

It is necessary to note that the specification also distinguishes between built-in objects, element objects and host objects.

Built-in objects and element objects are defined and implemented by the ECMAScript specification, and the differences between the two are insignificant. All objects implemented by ECMAScript are native objects (some of them are built-in objects, some are created when the program is executed, such as user-defined objects). Built-in objects are a subset of native objects that are built into ECMAScript before the program starts (for example, parseInt, Match, etc.). All host objects are provided by the host environment, usually the browser, and may include window, alert, etc.

Note that the host object may be implemented by ES itself, fully complying with the semantics of the specification. From this point of view, they can be called "native host" objects (as soon as possible theoretically), but the specification does not define the concept of "native host" objects.

Boolean, String and Number objects

In addition, the specification also defines some native special packaging classes. These objects are:

1. Boolean object
2. String object
3. Digital objects

These objects are created through the corresponding built-in constructors and contain native values ​​as their internal properties. These objects can convert primitive values ​​and vice versa.

Copy code The code is as follows:

var c = new Boolean(true);
var d = new String('test');
var e = new Number(10);

//Convert to original value
// Use function without new keyword
с = Boolean(c);
d = String(d);
e = Number(e);

// Reconvert to object
с = Object(c);
d = Object(d);
e = Object(e);

In addition, there are objects created by special built-in constructors: Function (function object constructor), Array (array constructor) RegExp (regular expression constructor), Math (math module), Date (date constructor) (container), etc. These objects are also values ​​of the Object object type. Their differences from each other are managed by internal properties, which we discuss below.

Literal

For the values ​​of three objects: object, array and regular expression, they have abbreviated identifiers called: object initializer, array initializer, and regular expression. Initializer:

Copy code The code is as follows:

// Equivalent to new Array(1, 2, 3);
// Or array = new Array();
// array[0] = 1;
// array[1] = 2;
// array[2] = 3;
var array = [1, 2, 3];

// Equivalent to
// var object = new Object();
// object.a = 1;
// object.b = 2;
// object.c = 3;
var object = {a: 1, b: 2, c: 3};

// Equivalent to new RegExp("^\d $", "g")
var re = /^d $/g;

Note that if the above three objects are reassigned to new types, then the subsequent implementation semantics will be used according to the newly assigned types. For example, in the current implementation of Rhino and the old version of SpiderMonkey 1.7, it will The object is successfully created using the constructor of the new keyword, but in some implementations (currently Spider/TraceMonkey) the semantics of literals do not necessarily change after the type is changed.

Copy code The code is as follows:

var getClass = Object.prototype.toString;

Object = Number;

var foo = new Object;
alert([foo, getClass.call(foo)]); // 0, "[object Number]"

var bar = {};

// Rhino, SpiderMonkey 1.7 - 0, "[object Number]"
// Others: still "[object Object]", "[object Object]"
alert([bar, getClass.call(bar)]);

//Array has the same effect
Array = Number;

foo = new Array;
alert([foo, getClass.call(foo)]); // 0, "[object Number]"

bar = [];

// Rhino, SpiderMonkey 1.7 - 0, "[object Number]"
// Others: still "", "[object Object]"
alert([bar, getClass.call(bar)]);

// But for RegExp, the semantics of the literal are not changed. semantics of the literal
// isn't being changed in all tested implementations

RegExp = Number;

foo = new RegExp;
alert([foo, getClass.call(foo)]); // 0, "[object Number]"

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

Regular expression literals and RegExp objects

Note that in the following two examples, the semantics of regular expressions are equivalent in the third edition of the specification. The regexp literal only exists in one sentence and is created in the parsing stage, but the one created by the RegExp constructor is It is a new object, so this may cause some problems. For example, the value of lastIndex is wrong during testing:

Copy code The code is as follows:

for (var k = 0; k < 4; k ) {
var re = /ecma/g;
alert(re.lastIndex); // 0, 4, 0, 4
alert(re.test("ecmascript")); // true, false, true, false
}

// Compare

for (var k = 0; k < 4; k ) {
var re = new RegExp("ecma", "g");
alert(re.lastIndex); // 0, 0, 0, 0
alert(re.test("ecmascript")); // true, true, true, true
}

Note: However, these problems have been corrected in the 5th edition of the ES specification. Whether it is based on literals or constructors, the regular rules create new objects.

Associative array

Various textual static discussions, JavaScript objects (often created using object initializer {}) are called hash tables, hash tables, or other simple names: hash (a concept in Ruby or Perl), management Array (a concept in PHP), dictionary (a concept in Python), etc.

There are only such terms, mainly because their structures are similar, that is, using "key-value" pairs to store objects, which is completely consistent with the data structure defined by the theory of "associative array" or "hash table". In addition, the hash table abstract data type is usually used at the implementation level.

However, although the terminology describes this concept, it is actually a mistake. From the perspective of ECMAScript: ECMAScript has only one object and type and its subtypes, which is no different from "key-value" pair storage, so There is no special concept on this. Because the internal properties of any object can be stored as key-value pairs:

Copy code The code is as follows:

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

var b = new Number(1);
b.x = 10;
b.y = 20;
b['z'] = 30;

var c = new Function('');
c.x = 10;
c.y = 20;
c['z'] = 30;

// Wait, a subtype of any object "subtype"

Also, since objects can be empty in ECMAScript, the concept of "hash" is also incorrect here:

Copy code The code is as follows:

Object.prototype.x = 10;

var a = {}; // Create empty "hash"

alert(a["x"]); // 10, but not empty
alert(a.toString); // function

a["y"] = 20; // Add new key-value pair to "hash"
alert(a["y"]); // 20

Object.prototype.y = 20; // Add prototype attributes

delete a["y"]; // Delete
alert(a["y"]); // But the key and value here still have values ​​- 20

Please note that the ES5 standard allows us to create objects without prototypes (implemented using the Object.create(null) method). From this perspective, such objects can be called hash tables:

Copy code The code is as follows:

var aHashTable = Object.create(null);
console.log(aHashTable.toString); // Undefined

Additionally, some properties have specific getter/setter methods, so that can also lead to confusion about this concept:
Copy code The code is as follows:

var a = new String("foo");
a['length'] = 10;
alert(a['length']); // 3

However, even if it is considered that "hash" may have a "prototype" (e.g., a class that delegates hash objects in Ruby or Python), in ECMAScript, this terminology is incorrect because there is a gap between the two representations. There is no semantic difference (i.e. using dot notation a.b and a["b"] notation).

The concept and semantics of "property attribute" in ECMAScript are not separated from "key", array index, and method. The reading and writing of properties of all objects here must follow the same rules: check the prototype chain.

In the following Ruby example, we can see the semantic difference:

Copy code The code is as follows:

a = {}
a.class # Hash

a.length # 0

# new "key-value" pair
a['length'] = 10;

# Semantically, dots are used to access properties or methods, not key

a.length # 1

#The indexer accesses the key in the hash

a['length'] # 10

# It is similar to dynamically declaring a Hash class on an existing object
# Then declare new properties or methods

class Hash
def z
100
end
end

# New attributes can be accessed

a.z # 100

# But not "key"

a['z'] # nil

The ECMA-262-3 standard does not define the concept of "hashes" (and similar). However, if there is such a structural theory, it is possible to name the object after it.

Object conversion

To convert an object into a primitive value, you can use the valueOf method. As we said, when the constructor of the function is called as a function (for some types), but if the new keyword is not used, the object is converted into a primitive value. , which is equivalent to the implicit valueOf method call:

Copy code The code is as follows:

var a = new Number(1);
var primitiveA = Number(a); // Implicit "valueOf" call
var alsoPrimitiveA = a.valueOf(); // Explicit call

alert([
typeof a, // "object"
typeof primitiveA, // "number"
typeof alsoPrimitiveA // "number"
]);

This approach allows objects to participate in various operations, such as:
Copy code The code is as follows:

var a = new Number(1);
var b = new Number(2);

alert(a b); // 3

// Even

var c = {
x: 10,
y: 20,
valueOf: function () {
Return this.x this.y;
}
};

var d = {
x: 30,
y: 40,
//Same as the valueOf function of c
valueOf: c.valueOf
};

alert(c d); // 100

The default value of valueOf will change according to the type of the object (if not overridden). For some objects, it returns this - for example: Object.prototype.valueOf(), and calculated values. : Date.prototype.valueOf() returns the date and time:

Copy code The code is as follows:

var a = {};
alert(a.valueOf() === a); // true, "valueOf" returns this

var d = new Date();
alert(d.valueOf()); // time
alert(d.valueOf() === d.getTime()); // true

In addition, objects have a more primitive representation - a string representation. This toString method is reliable and is used automatically for certain operations:
Copy code The code is as follows:

var a = {
valueOf: function () {
Return 100;
},
toString: function () {
Return '__test';
}
};

// In this operation, the toString method is automatically called
alert(a); // "__test"

// But here, the valueOf() method is called
alert(a 10); // 110

// But, once valueOf is deleted
// toString can be called automatically again
delete a.valueOf;
alert(a 10); // "_test10"

The toString method defined on Object.prototype has a special meaning. It returns the internal [[Class]] attribute value that we will discuss below.

Compared with converting to primitive values ​​(ToPrimitive), converting values ​​into object types also has a conversion specification (ToObject).

An explicit method is to use the built-in Object constructor as a function to call ToObject (somewhat similar to the new keyword):

Copy code The code is as follows:

var n = Object(1); // [object Number]
var s = Object('test'); // [object String]

//Something similar, you can also use the new operator
var b = new Object(true); // [object Boolean]

// If the parameter new Object is used, a simple object is created
var o = new Object(); // [object Object]

// If the parameter is an existing object
// The result of creation is to simply return the object
var a = [];
alert(a === new Object(a)); // true
alert(a === Object(a)); // true

There are no general rules about calling built-in constructors, whether to use the new operator or not, it depends on the constructor. For example, Array or Function produce the same result when used as a constructor using the new operator or a simple function that does not use the new operator:

Copy code The code is as follows:

var a = Array(1, 2, 3); // [object Array]
var b = new Array(1, 2, 3); // [object Array]
var c = [1, 2, 3]; // [object Array]

var d = Function(''); // [object Function]
var e = new Function(''); // [object Function]

When some operators are used, there are also some explicit and implicit conversions:
Copy code The code is as follows:

var a = 1;
var b = 2;

// Implicit
var c = a b; // 3, number
var d = a b '5' // "35", string

// explicit
var e = '10'; // "10", string
var f = e; // 10, number
var g = parseInt(e, 10); // 10, number

// Wait

Characteristics of attributes

All properties can have many attributes.

1.{ReadOnly} - Ignore the write operation of assigning a value to the property, but the read-only property can be changed by the behavior of the host environment - that is, it is not a "constant value";
2.{DontEnum}——Attributes cannot be enumerated by for..in loop
3.{DontDelete}——The behavior of the delete operator is ignored (that is, it cannot be deleted);
4. {Internal} - Internal attribute, no name (only used at the implementation level), such attributes cannot be accessed in ECMAScript.

Note that in ES5 {ReadOnly}, {DontEnum} and {DontDelete} are renamed to [[Writable]], [[Enumerable]] and [[Configurable]], which can be manually passed through Object.defineProperty or similar methods to manage these properties.

Copy code The code is as follows:

var foo = {};

Object.defineProperty(foo, "x", {
value: 10,
writable: true, // that is {ReadOnly} = false
enumerable: false, // that is {DontEnum} = true
configurable: true // i.e. {DontDelete} = false
});

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

// Get the feature set attributes
through descriptor var desc = Object.getOwnPropertyDescriptor(foo, "x");

console.log(desc.enumerable); // false
console.log(desc.writable); // true
// Wait

Internal properties and methods

Objects can also have internal properties (part of the implementation level) that are not directly accessible to ECMAScript programs (but as we will see below, some implementations allow access to some such properties). These properties are accessed through nested square brackets [[ ]]. Let's take a look at some of them. The description of these properties can be found in the specification.

Every object should implement the following internal properties and methods:

1.[[Prototype]] - the prototype of the object (will be introduced in detail below)
2.[[Class]] - a representation of a string object (for example, Object Array, Function Object, Function, etc.); used to distinguish objects
3.[[Get]]——Method to obtain attribute value
4.[[Put]]——Method to set attribute value
5.[[CanPut]]——Check whether the attribute is writable
6.[[HasProperty]]——Check whether the object already has this property
7.[[Delete]]——Delete the attribute from the object
8.[[DefaultValue]] returns the original value of the object (calling the valueOf method, some objects may throw a TypeError exception).
The value of the internal property [[Class]] can be obtained indirectly through the Object.prototype.toString() method, which should return the following string: "[object " [[Class]] "]" . For example:

Copy code The code is as follows:

var getClass = Object.prototype.toString;

getClass.call({}); // [object Object]
getClass.call([]); // [object Array]
getClass.call(new Number(1)); // [object Number]
// Wait

This function is usually used to check objects, but the specification says that the [[Class]] of the host object can be any value, including the value of the [[Class]] attribute of the built-in object, so theoretically it cannot be 100% guaranteed. Guaranteed to be accurate. For example, the [[Class]] attribute of the document.childNodes.item(...) method returns "String" in IE, but it does return "Function" in other implementations.
Copy code The code is as follows:

// in IE - "String", in other - "Function"
alert(getClass.call(document.childNodes.item));

Constructor

So, as we mentioned above, objects in ECMAScript are created through so-called constructors.

Constructor is a function that creates and initializes the newly created object.
A constructor is a function that creates and initializes a newly created object.
Object creation (memory allocation) is taken care of by the constructor's internal method [[Construct]]. The behavior of this internal method is well defined, and all constructors use this method to allocate memory for new objects.

The initialization is managed by calling this function up and down the new object, which is responsible for the internal method [[Call]] of the constructor.

Note that user code can only be accessed during the initialization phase, although during the initialization phase we can return a different object (ignoring the tihs object created in the first phase):

Copy code The code is as follows:

function A() {
// Update the newly created object
this.x = 10;
// But it returns a different object
return [1, 2, 3];
}

var a = new A();
console.log(a.x, a); undefined, [1, 2, 3]

Referring to Chapter 15 Function - Algorithm for Creating Functions, we can see that the function is a native object, including [[Construct]] ] and [[Call]] ] attributes as well as the displayed prototype prototype attribute - the future The prototype of the object (Note: NativeObject is a convention for native objects and is used in the pseudocode below).

Copy code The code is as follows:

F = new NativeObject();

F.[[Class]] = "Function"

.... // Other attributes

F.[[Call]] = // function itself

F.[[Construct]] = internalConstructor // Ordinary internal constructor

.... // Other attributes

// Object prototype created by F constructor
__objectPrototype = {};
__objectPrototype.constructor = F // {DontEnum}
F.prototype = __objectPrototype

[[Call]] ] is the main way to distinguish objects other than the [[Class]] attribute (here equivalent to "Function"), so the internal [[Call]] attribute of the object is called as a function. Using the typeof operator on such an object returns "function". However, it is mainly related to native objects. In some cases, the implementation of using typeof to obtain the value is different. For example: the effect of window.alert (...) in IE:

Copy code The code is as follows:

// In IE browser - "Object", "object", other browsers - "Function", "function"
alert(Object.prototype.toString.call(window.alert));
alert(typeof window.alert); // "Object"

The internal method [[Construct]] is activated by using the constructor with the new operator. As we said, this method is responsible for memory allocation and object creation. If there are no parameters, the parentheses for calling the constructor can also be omitted:

Copy code The code is as follows:

function A(x) { // constructor А
this.x = x || 10;
}

// If no parameters are passed, the brackets can be omitted
var a = new A; // or new A();
alert(a.x); // 10

//Explicitly pass in parameter x
var b = new A(20);
alert(b.x); // 20

We also know that shis in the constructor (initialization phase) is set to the newly created object.

Let’s study the object creation algorithm.

Algorithm for object creation

The behavior of the internal method [[Construct]] can be described as follows:

Copy code The code is as follows:

F.[[Construct]](initialParameters):

O = new NativeObject();

// Property [[Class]] is set to "Object"
O.[[Class]] = "Object"

// Get the object g
when referencing F.prototype var __objectPrototype = F.prototype;

// If __objectPrototype is an object, then:
O.[[Prototype]] = __objectPrototype
// Otherwise:
O.[[Prototype]] = Object.prototype;
// Here O.[[Prototype]] is the prototype of the Object object

// F.[[Call]]
is applied when initializing the newly created object. // Set this to the newly created object O
//The parameters are the same as the initialParameters in F
R = F.[[Call]](initialParameters); this === O;
// Here R is the return value of [[Call]]
// View it in JS, like this:
// R = F.apply(O, initialParameters);

// If R is an object
return R
// Otherwise
return O

Please note two main features:

1. First, the prototype of the newly created object is obtained from the prototype attribute of the function at the current moment (this means that the prototypes of two created objects created by the same constructor can be different because the prototype attribute of the function can also be different) .
2. Secondly, as we mentioned above, if [[Call]] returns an object when the object is initialized, this is exactly the result used for the entire new operator:

Copy code The code is as follows:

function A() {}
A.prototype.x = 10;

var a = new A();
alert(a.x); // 10 – get
from prototype
// Set the .prototype property to the new object
// Why explicitly declare the .constructor property is explained below
A.prototype = {
constructor: A,
y: 100
};

var b = new A();
// Object "b" has new properties
alert(b.x); // undefined
alert(b.y); // 100 – get
from prototype
// But the prototype of a object can still get the original result
alert(a.x); // 10 - get
from prototype
function B() {
this.x = 10;
return new Array();
}

// If the "B" constructor does not return (or returns this)
// Then this object can be used, but in the following case, array
is returned var b = new B();
alert(b.x); // undefined
alert(Object.prototype.toString.call(b)); // [object Array]

Let’s take a closer look at the prototype

Prototype

Every object has a prototype (except some system objects). Prototype communication is carried out through the internal, implicit, and not directly accessible [[Prototype]] prototype property. The prototype can be an object or a null value.

Property constructor

There are two important knowledge points in the above example. The first one is about the prototype attribute of the constructor attribute of the function. In the function creation algorithm, we know that the constructor attribute is set to the prototype attribute of the function during the function creation phase. , the value of the constructor attribute is an important reference to the function itself:

Copy code The code is as follows:

function A() {}
var a = new A();
alert(a.constructor); // function A() {}, by delegation
alert(a.constructor === A); // true

Usually in this case, there is a misunderstanding: the constructor constructor property is wrong as a property of the newly created object itself, but, as we can see, this property belongs to the prototype and is accessed through inheritance.

By inheriting the instance of the constructor attribute, you can indirectly obtain a reference to the prototype object:

Copy code The code is as follows:

function A() {}
A.prototype.x = new Number(10);

var a = new A();
alert(a.constructor.prototype); // [object Object]

alert(a.x); // 10, via prototype
// Same effect as a.[[Prototype]].x
alert(a.constructor.prototype.x); // 10

alert(a.constructor.prototype.x === a.x); // true

But please note that the constructor and prototype attributes of the function can be redefined after the object is created. In this case, the object loses the mechanism described above. If you edit the element's prototype through the function's prototype attribute (adding a new object or modifying an existing object), you will see the newly added attributes on the instance.

However, if we completely change the prototype property of the function (by allocating a new object), the reference to the original constructor is lost, because the object we create does not include the constructor property:

Copy code The code is as follows:

function A() {}
A.prototype = {
x: 10
};

var a = new A();
alert(a.x); // 10
alert(a.constructor === A); // false!

Therefore, the prototype reference to the function needs to be restored manually:
Copy code The code is as follows:

function A() {}
A.prototype = {
constructor: A,
x: 10
};

var a = new A();
alert(a.x); // 10
alert(a.constructor === A); // true

Note that although the constructor attribute has been restored manually, compared with the original lost prototype, the {DontEnum} feature is no longer available, which means that the for..in loop statement in A.prototype is not supported, but in the 5th edition of the specification , provides the ability to control the enumerable state enumerable through the [[Enumerable]] attribute.

Copy code The code is as follows:

var foo = {x: 10};

Object.defineProperty(foo, "y", {
value: 20,
enumerable: false // aka {DontEnum} = true
});

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

for (var k in foo) {
console.log(k); // only "x"
}

var xDesc = Object.getOwnPropertyDescriptor(foo, "x");
var yDesc = Object.getOwnPropertyDescriptor(foo, "y");

console.log(
xDesc.enumerable, // true
yDesc.enumerable // false
);

Explicit prototype and implicit [[Prototype]] attributes

Generally, it is incorrect to explicitly reference the prototype of an object through the function's prototype attribute. It refers to the same object, the object's [[Prototype]] attribute:

a.[[Prototype]] ----> Prototype <---- A.prototype

In addition, the [[Prototype]] value of the instance is indeed obtained from the prototype attribute of the constructor.

However, submitting the prototype attribute will not affect the prototype of the already created object (it will only be affected when the prototype attribute of the constructor changes). That is to say, only newly created objects will have new prototypes, and already created objects will still have new prototypes. Reference to the original old prototype (this prototype can no longer be modified).

Copy code The code is as follows:

// The situation before modifying the A.prototype prototype
a.[[Prototype]] ----> Prototype <---- A.prototype

// After modification
A.prototype ----> New prototype // New objects will have this prototype
a.[[Prototype]] ----> Prototype // Boot the original prototype

For example:

Copy code The code is as follows:

function A() {}
A.prototype.x = 10;

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

A.prototype = {
constructor: A,
x: 20
y: 30
};

// Object a is a value obtained from the prototype of crude oil through the implicit [[Prototype]] reference
alert(a.x); // 10
alert(a.y) // undefined

var b = new A();

// But the new object is the value obtained from the new prototype
alert(b.x); // 20
alert(b.y) // 30

Therefore, some articles say that "dynamically modifying the prototype will affect all objects and all objects will have new prototypes" which is wrong. The new prototype will only take effect on newly created objects after the prototype is modified.

The main rule here is: the prototype of an object is created when the object is created, and cannot be modified to a new object after that. If it still refers to the same object, it can be referenced through the explicit prototype of the constructor. After the object is created, only the properties of the prototype can be added or modified.

Non-standard __proto__ attribute

However, some implementations, such as SpiderMonkey, provide the non-standard __proto__ explicit attribute to reference the object's prototype:

Copy code The code is as follows:

function A() {}
A.prototype.x = 10;

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

var __newPrototype = {
constructor: A,
x: 20,
y: 30
};

//Reference to new object
A.prototype = __newPrototype;

var b = new A();
alert(b.x); // 20
alert(b.y); // 30

// The "a" object still uses the old prototype
alert(a.x); // 10
alert(a.y); // undefined

// Explicitly modify the prototype
a.__proto__ = __newPrototype;

// Now the "а" object refers to the new object
alert(a.x); // 20
alert(a.y); // 30

Note that ES5 provides the Object.getPrototypeOf(O) method, which directly returns the [[Prototype]] property of the object - the initial prototype of the instance. However, compared to __proto__, it is only a getter and does not allow set values.
Copy code The code is as follows:

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

Object independent of constructor
Because the prototype of the instance is independent of the constructor and the prototype attribute of the constructor, the constructor can be deleted after completing its main work (creating the object). Prototype objects continue to exist by referencing the [[Prototype]] attribute:

Copy code The code is as follows:

function A() {}
A.prototype.x = 10;

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

// Set A to null - show reference constructor
A = null;

// But if the .constructor property has not changed,
// You can still create objects through it
var b = new a.constructor();
alert(b.x); // 10

// Implicit references are also deleted
delete a.constructor.prototype.constructor;
delete b.constructor.prototype.constructor;

// Objects can no longer be created through the constructor of A
// But these two objects still have their own prototypes
alert(a.x); // 10
alert(b.x); // 10

Characteristics of instanceof operator
We display the reference prototype through the prototype attribute of the constructor, which is related to the instanceof operator. This operator works with the prototype chain, not the constructor. With this in mind, there are often misunderstandings when detecting objects:

Copy code The code is as follows:

if (foo instanceof Foo) {
...
}

This is not used to detect whether object foo was created using the Foo constructor. All instanceof operators require only one object property - foo.[[Prototype]], and check its existence starting from Foo.prototype in the prototype chain. The instanceof operator is activated through the internal method [[HasInstance]] in the constructor.

Let’s take a look at this example:

Copy code The code is as follows:

function A() {}
A.prototype.x = 10;

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

alert(a instanceof A); // true

// If the prototype is set to null
A.prototype = null;

// ..."a" can still access the prototype through a.[[Prototype]]
alert(a.x); // 10

// However, the instanceof operator can no longer be used normally
// Because it is implemented from the prototype attribute of the constructor
alert(a instanceof A); // Error, A.prototype is not an object

On the other hand, an object can be created by a constructor, but if the [[Prototype]] attribute of the object and the value of the prototype attribute of the constructor are set to the same value, instanceof will return true when checked:

Copy code The code is as follows:

function B() {}
var b = new B();

alert(b instanceof B); // true

function C() {}

var __proto = {
constructor: C
};

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

alert(b instanceof C); // true
alert(b instanceof B); // false

Prototypes can store methods and share properties
Prototypes are used in most programs to store object methods, default states, and shared object properties.

In fact, objects can have their own state, but the methods are usually the same. Therefore, methods are usually defined in prototypes for memory optimization. This means that all instances created by this constructor can share this method.

Copy code The code is as follows:

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

A.prototype = (function () {

// Initialize context
// Use additional objects

var _someSharedVar = 500;

function _someHelper() {
alert('internal helper: ' _someSharedVar);
}

function method1() {
alert('method1: ' this.x);
}

function method2() {
alert('method2: ' this.x);
_someHelper();
}

// Prototype itself
Return {
​ 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

// The two objects use the same method in the prototype
alert(a.method1 === b.method1); // true
alert(a.method2 === b.method2); // true

Read and write attributes

As we mentioned, reading and writing property values ​​is through the internal [[Get]] and [[Put]] methods. These internal methods are activated through property accessors: dot notation or index notation:

Copy code The code is as follows:

// write
foo.bar = 10; // Called [[Put]]

console.log(foo.bar); // 10, called [[Get]]
console.log(foo['bar']); // Same effect

Let’s see how these methods work in pseudocode:

[[Get]] method

[[Get]] will also query properties from the prototype chain, so properties in the prototype can also be accessed through the object.

O.[[Get]](P):

Copy code The code is as follows:

// If it is your own attribute, return
if (O.hasOwnProperty(P)) {
Return O.P;
}

// Otherwise, continue analyzing the prototype
var __proto = O.[[Prototype]];

// If the prototype is null, return undefined
// This is possible: the top-level Object.prototype.[[Prototype]] is null
if (__proto === null) {
Return undefined;
}

// Otherwise, call [[Get]] recursively on the prototype chain and search for attributes in the prototypes of each layer
// Until the prototype is null
return __proto.[[Get]](P)

Please note that [[Get]] will also return undefined in the following situations:
Copy code The code is as follows:

if (window.someObject) {
...
}

Here, if the someObject property is not found in the window, it will be searched in the prototype, then in the prototype of the prototype, and so on. If it cannot be found, undefined will be returned according to the definition.

Note: The in operator can also be responsible for looking up properties (it will also look up the prototype chain):

Copy code The code is as follows:

if ('someObject' in window) {
...
}

This helps avoid some special problems: for example, even if someObject exists, when someObject is equal to false, the first round of detection will fail.

[[Put]] method

The

[[Put]] method can create and update the properties of the object itself, and mask the properties of the same name in the prototype.

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

Copy code The code is as follows:

// If the value cannot be written to the attribute, exit
if (!O.[[CanPut]](P)) {
Return;
}

// If the object does not have its own properties, create it
// All attributes are false
if (!O.hasOwnProperty(P)) {
createNewProperty(O, P, attributes: {
ReadOnly: false,
DontEnum: false,
DontDelete: false,
Internal: false
});
}

// Set the value if the attribute exists, but do not change the attributes property
O.P = V

return;

For example:
Copy code The code is as follows:

Object.prototype.x = 100;

var foo = {};
console.log(foo.x); // 100, inherited properties

foo.x = 10; // [[Put]]
console.log(foo.x); // 10, own attributes

delete foo.x;
console.log(foo.x); //reset to 100, inherit properties
Please note that read-only properties in the prototype cannot be masked, and the assignment results will be ignored. This is controlled by the internal method [[CanPut]].

// For example, the attribute length is read-only, let’s try to mask the length

function SuperString() {
/* nothing */
}

SuperString.prototype = new String("abc");

var foo = new SuperString();

console.log(foo.length); // 3, the length of "abc"

// Attempt to mask
foo.length = 5;
console.log(foo.length); // Still 3


But in ES5's strict mode, if the read-only attribute is masked, a TypeError will be saved.

Property accessor

The internal methods [[Get]] and [[Put]] are activated through dot notation or indexing in ECMAScript. If the attribute identifier is a legal name, it can be accessed through ".", and indexing Party runs dynamically defined names.

Copy code The code is as follows:

var a = {testProperty: 10};

alert(a.testProperty); // 10, click
alert(a['testProperty']); // 10, index

var propertyName = 'Property';
alert(a['test' propertyName]); // 10, dynamic properties are indexed

There is a very important feature here - property accessors always use the ToObject specification to treat the value to the left of "." This implicit conversion is related to the saying "everything in JavaScript is an object" (however, as we already know, not all values ​​in JavaScript are objects).

If the attribute accessor is used to access the original value, the original value will be wrapped by the object (including the original value) before accessing, and then the attribute will be accessed through the wrapped object. After the attribute is accessed, the wrapped object will be deleted.

For example:

Copy code The code is as follows:

var a = 10; // Original value

// But methods can be accessed (just like objects)
alert(a.toString()); // "10"

// Additionally, we can create a heart attribute on a
a.test = 100; // It seems to be no problem

// However, the [[Get]] method does not return the value of the property, but returns undefined
alert(a.test); // undefined

So, why can the original value in the entire example have access to the toString method, but not the newly created test property?

The answer is simple:

First of all, as we said, after using the property accessor, it is no longer the original value, but a wrapped intermediate object (the entire example uses new Number(a)), and the toString method is passed at this time Found in the prototype chain:

Copy code The code is as follows:

//Principle of executing a.toString():

1. wrapper = new Number(a);
2. wrapper.toString(); // "10"
3. delete wrapper;

Next, when the [[Put]] method creates new attributes, it is also done through the packaged object:
Copy code The code is as follows:

//The principle of executing a.test = 100:

1. wrapper = new Number(a);
2. wrapper.test = 100;
3. delete wrapper;

We see that in step 3, the wrapped object is deleted, and the newly created property page is deleted - deleting the wrapping object itself.

When using [[Get]] to get the test value, the packaging object is created again, but this time the wrapped object no longer has the test attribute, so undefined is returned:

Copy code The code is as follows:

//Principle of executing a.test:

1. wrapper = new Number(a);
2. wrapper.test; // undefined

This method explains how the original value is read. In addition, if any original value is often used to access attributes, time efficiency considerations will directly replace it with an object; on the contrary, if it is not accessed frequently, or just used For calculation purposes, this form can be retained.

Inherit

We know that ECMAScript uses prototype-based delegated inheritance. Chains and prototypes have already been mentioned in the prototype chain. In fact, all the implementation of delegation and the search and analysis of the prototype chain are condensed into the [[Get]] method.

If you fully understand the [[Get]] method, then the question of inheritance in JavaScript will be self-explanatory.

When I often talk about inheritance in JavaScript on forums, I always use one line of code to show it. In fact, we don’t need to create any objects or functions because the language is already based on inheritance. The code is as follows:

Copy code The code is as follows:

alert(1..toString()); // "1"

We already know how the [[Get]] method and property accessors work, let’s see what happens:

1. First, create a packaging object from the original value 1 through new Number(1)
2. Then the toString method is inherited from this packaging object

Why is it inherited? Because objects in ECMAScript can have their own properties, the wrapper object in this case does not have a toString method. So it inherits from the principle, which is Number.prototype.

Note there is a subtlety, the two dots in the above example are not an error. The first point represents the decimal part, and the second point is an attribute accessor:

Copy code The code is as follows:

1.toString(); // Syntax error!

(1).toString(); // OK

1..toString(); // OK

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

Prototype Chain

Let's show how to create a prototype chain for a user-defined object, it's very simple:

Copy code The code is as follows:

function A() {
alert('A.[[Call]] activated');
this.x = 10;
}
A.prototype.y = 20;

var a = new A();
alert([a.x, a.y]); // 10 (self), 20 (inherited)

function B() {}

// The most recent prototype chain method is to set the prototype of the object to another new object
B.prototype = new A();

// Repair the constructor property of the prototype, otherwise it would be A
B.prototype.constructor = B;

var b = new B();
alert([b.x, b.y]); // 10, 20, 2 are inherited

// [[Get]] b.x:
// b.x (no) -->
// b.[[Prototype]].x (yes) - 10

// [[Get]] b.y
// b.y (no) -->
// b.[[Prototype]].y (no) -->
// b.[[Prototype]].[[Prototype]].y (yes) - 20

// where b.[[Prototype]] === B.prototype,
// and b.[[Prototype]].[[Prototype]] === A.prototype

This method has two characteristics:

First, B.prototype will contain the x attribute. At first glance this may not seem right, you might think that the x property is defined in A and that the B constructor expects it as well. Although prototypal inheritance is no problem under normal circumstances, the B constructor may sometimes not need the x attribute. Compared with class-based inheritance, all attributes are copied to descendant subclasses.

However, if there is a need (to simulate class-based inheritance) to assign the x attribute to the object created by the B constructor, there are some ways, one of which we will show later.

Secondly, this is not a feature but a disadvantage - when the subclass prototype is created, the code of the constructor is also executed, and we can see that the message "A.[[Call]] activated" is displayed twice - When using the A constructor to create an object and assign it to the B.prototype property, the other scene is when the a object creates itself!

The following example is more critical, the exception thrown by the constructor of the parent class: Maybe it needs to be checked when the actual object is created, but obviously, the same case, that is, when using these parent objects as prototypes Something will go wrong.

Copy code The code is as follows:

function A(param) {
if (!param) {
throw 'Param required';
}
this.param = param;
}
A.prototype.x = 10;

var a = new A(20);
alert([a.x, a.param]); // 10, 20

function B() {}
B.prototype = new A(); // Error

In addition, having too much code in the constructor of the parent class is also a disadvantage.

To solve these "functions" and problems, programmers use the standard pattern of prototype chains (shown below). The main purpose is to wrap the creation of constructors in the middle. The chains of these wrapping constructors contain the required prototypes.

Copy code The code is as follows:

function A() {
alert('A.[[Call]] activated');
this.x = 10;
}
A.prototype.y = 20;

var a = new A();
alert([a.x, a.y]); // 10 (self), 20 (integrated)

function B() {
// Or use A.apply(this, arguments)
B.superproto.constructor.apply(this, arguments);
}

// Inheritance: connect prototypes together through empty intermediate constructors
var F = function () {};
F.prot
Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn