ECMAScript and JavaScript are not discussed in detail in this paper. As ecMA-262 is used for interpretation, ECMAScript, ES for short is used in this paper. The latest standard document (2021-04-05) is used in this paper, and the link is as follows: TC39.es/ECMA262 /
There are a lot of good articles about object and prototype analysis, but most of them are mainly about explaining and analyzing phenomena, rather than analyzing the underlying principles or definitions. Therefore, after reading them, you may just learn the phenomena, but not understand the causes.
This paper attempts to start from the perspective of language standards and see how objects and prototypes are described in the standards, hoping to provide a different perspective for understanding. Of course, the skill is limited, where there are problems, please give us more advice.
Object-oriented programming language
Section 4 Overview of the standard states:
ECMAScript is an object-oriented programming language for performing computations and manipulating computational objects within a host environment.
It is also mentioned in section 4.3:
ECMAScript is object-based: basic language and host facilities are provided by objects, and an ECMAScript program is a cluster of communicating objects.
ECMAScript is an object-oriented programming language. It is based on objects, and its core capabilities are provided by objects.
So what exactly is an object? Section 4.4.6 defines an object as: member of the type object, i.e., a member of the type object.
An object is a collection of properties and has a single prototype object. The prototype may be the null value.
This makes sense: an object is a collection of attributes and has a unique prototype object (possibly NULL).
Note that the prototype object is not the same as the constructor’s prototype object, and [[prototype]] is used to represent the prototype properties of the object.
It is also necessary to note the description in section 4.3.1:
Even though ECMAScript includes syntax for class definitions, ECMAScript objects are not fundamentally class-based such as those in C++, Smalltalk, or Java.
That is, ES is not an object-oriented language based on classes (from which objects are created), but rather based on prototypes (as explained gradually later), unlike C++,Java, etc. This is what we call “class” in “ES”.
How objects are created
Continue with the description in section 4.3.1:
Instead objects may be created in various ways including via a literal notation or via constructors which create objects and then execute code that initializes all or part of them by assigning initial values to their properties.
Objects are created by using constructors in new expressions
As you can see, there are several ways to create objects: literal symbols and constructors. There are many different ways to create objects in the Little Red Book, but the underlying logic is those two.
Object literals
Literals are very simple, such as creating an object literal with the following code:
{
name: 'lml'
}
Copy the code
The constructor
Objects are created using the new operator using the constructor (hereinafter called constructor). So there are two questions, what is the constructor? How does the new operator create objects?
First, let’s understand the constructor definition (section 4.4.7) : function object that creates and initiobjects
The value of a constructor’s “prototype” property is a prototype object that is used to implement inheritance and shared properties.
In addition, there is the description in Section 4.3.1:
Each constructor is a function that has a property named “prototype” that is used to implement prototype-based inheritance and shared properties.
ES is an object-oriented language based on stereotypes. The constructor object is a function object whose stereotype attribute value is a stereotype object that implements inheritance and attribute sharing. (Note the distinction between [[prototype]] and [[prototype]] above)
Next, let’s look at how the new operator creates an object, which we are probably familiar with (because there are so many pre-abstractions involved in the specification, we refer directly to the specification in MDN) :
1.Creates a blank, plain JavaScript object.
2.Adds a property to the new object (__proto__) that links to the constructor function’s prototype object
3.Binds the newly created object instance as the this context (i.e. all references to this in the constructor function now refer to the object created in the first step).
4. Returns this if the function doesn’t return an object.
To summarize the result of this procedure: the returned object has all the properties assigned by this statement from Constructor, and the [[prototype]] property of the returned object points to the constructor prototype object.
Here’s a quick example:
function Animal(name) {
this.name = name;
}
Animal.prototype.getName = function() {
return this.name;
}
const animal = new Animal('miky');
console.log(name);
Copy the code
Take a look at the result:
Prototype and prototype chain
The prototype
We have already covered the concept of a prototype. Let’s first look at the standard definition (4.4.8) : Object that provides shared properties for other objects
When a constructor creates an object, that object implicitly references the constructor’s “prototype” property for the purpose of resolving property references.
The constructor’s “prototype” property can be referenced by the program expression constructor.prototype, and properties added to an object’s prototype are shared, through inheritance, by all objects sharing the prototype.
Alternatively, a new object may be created with an explicitly specified prototype by using the Object.create built-in function.
There are a few things to note about this description:
- When the constructor creates an object, the created object has an implicit reference to the constructor’s Prototype object, which resolves the object attribute reference. What is a property reference? This can be simply understood as the process of finding object properties, as described in the prototype chain section below.
- The prototype properties of the constructor can be obtained in the code using constructive.prototype, and any properties added to Prototype can be shared with objects created by the constructor.
- We can also explicitly specify [[Prototype]] to create objects using the object.create method, which was introduced in ES5
Let’s do a simple rewrite based on the above code:
function Animal(name) {
this.name = name;
}
Animal.prototype.getName = function () {
return this.name;
}
const animal = new Animal('miky');
console.log(animal.name, animal.category);
Animal.prototype.category = 'animal';
console.log(animal.name, animal.category);
const animalByObjectCreate = Object.create(Animal.prototype);
console.log(animalByObjectCreate.name, animalByObjectCreate.category);
Copy the code
Miky undefined miky animal undefined animal
Here we add two more points:
-
Constructor’s prototype can be obtained directly from constructor. Prototype, such as animal. prototype. How to get the [[prototype]] object? There are generally two methods:
- By object.getProtoTypeof (Object), a method introduced in ES5
- Object.__proto__ attribute, but this feature is non-standard and not recommended
Object.getprototypeof (animal) === Object.getProtoTypeof (animalByObjectCreate) === animal.prototype
-
Constructor of the prototype will have a “constructor” attribute points to the constructor function itself, such as in the above example: Animal. The prototype. The constructor = = = Animal
Prototype chain
Based on the Animal constructor and Animal object above, we’ll write console.log(animal.tostring ()), which will output [Object Object]. [[prototype]] : toString = animal; toString = animal; toString = animal; This is where prototype chains come in. Let’s first look at how prototype chains are described in the standard (4.3.1) :
Every object created by a constructor has an implicit reference (called the object’s prototype) to the value of its constructor’s “prototype” property.
Furthermore, a prototype may have a non-null implicit reference to its prototype, and so on; this is called the prototype chain.
Constructor creates an object that has an implicit reference to its Prototype object, and its Prototype object has an implicit non-null reference to its [[Prototype]] object. This leads to a traversable chain known as the prototype chain.
The purpose of the prototype chain is to resolve the properties of the object. Again, look at the description in the standard (4.3.1) :
When a reference is made to a property in an object, that reference is to the property of that name in the first object in the prototype chain that contains a property of that name.
In other words, first the object mentioned directly is examined for such a property; if that object contains the named property, that is the property to which the reference refers; if that object does not contain the named property, the prototype for that object is examined next; and so on.
When a reference to an attribute on an object is required, the object itself is checked to see if it contains the attribute of the same name. If the object does not contain the property, check the object’s [[prototype]] object and so on until either the property is found or [[prototype]] is null.
With this in mind, we can simply draw the search for the toString property method above. Of course, the actual traversal process ends when toString() is found.
Why toString is in the Object.prototype Object is covered in the next section.
The Object and the Function
[[prototype]] [Function] [[prototype]] [[prototype]] [[prototype]] [[prototype]] [[prototype]] [[prototype]]
In Chapter 20, the standard introduces Fundamental Objects, the Fundamental Objects, whose importance is self-evident, and which are even the ancestors of all other Objects. The first and second sections of this chapter introduce Object and Function respectively, which shows the importance of the two.
Object and Function are both objects and constructors, and it is important to distinguish between them as objects and constructors when understanding the topic of the prototype chain.
Object
Object is introduced in section 20.1
Object constructor property
Properties of the Object Constructor: has a [[Prototype]] internal slot whose value is %Function.prototype%. has a “length” property. has the following additional properties:…
- Function. Prototype: object.getProtoTypeof (Object) === Function. Prototype
- Object has a length attribute with a value of 1(Object.length === 1), which is not writable by default
- Object has many other properties. Among these properties, except
Object.prototype
In addition, the other are the static methods we are familiar with Object, which can be directly seen in MDN(link)
Object. Prototype (constructor) The properties on this Object are shared by all instances of Object
Object. The prototype of the attribute
{[[Writable]]: False, [[Enumerable]]: false, [[64x]]: {[[Writable]]: {[Writable]]: False }, so we can’t do anything to override or configure the prototype property of Object. Object. Prototype = {} is invalid and will report errors in strict mode. We select the most relevant part of this paper to introduce:
-
has a [[Prototype]] internal slot whose value is null. Object.getprototypeof (Object.prototype) === null
-
The initial value of Object.prototype.constructor is %Object%.
That is to say: the Object. The prototype. The constructor = = = the Object
- Other properties are also we are very familiar with the basic method of attributes, including hasOwnProperty, isPrototypeOf, toString, etc., specific can consult MDN (link)
Object Indicates the properties of the instance
Object instances have no special properties beyond those inherited from the Object prototype object.
Instances of Object have no special attributes other than those inherited from Object.prototype. In addition to creating an Object instance through the new Object(argument), an Object literal can also be treated as an instance of Object, whose [[prototype]] refers to Object.prototype.
Function
Function is introduced in section 20.2
Properties of the Function constructor
-
Has a [[Prototype]] internal slot whose value is %Function. Prototype %. Object.getPrototypeOf(Function) === Function.prototype
-
Function.length: The initial value is 1
-
Prototype: The prototype Object for Function has the same properties as the Object. Prototype property and therefore cannot be overwritten or configured
The Function prototype properties
-
Has a [[Prototype]] internal slot whose value is %Object. Prototype %. : Object.getPrototypeOf(Function.prototype) === Object.prototype
-
Has a “length” property whose value is +0ðœ. And has a “name” property whose value is the empty String. That means: the Function. The prototype. The length = = = 0; Function.prototype.name === ”;
-
The initial value of The Function. The prototype. The constructor is % % Function. : Function. The prototype. The constructor = = = Function
-
Other attributes include the apply,call, and bind methods. These are familiar methods, which is why our custom functions can call this method directly, because the [[prototype]] of our custom functions all point to function.prototype by default, There are several approaches that can be found along the prototype chain.
Function. Prototype does not have a “prototype” property.
The Function prototype object is specified to be a function object to ensure compatibility with ECMAScript code that was created prior to the ECMAScript 2015 specification.
Typeof function. prototype === ‘Function’ instead of ‘object’; typeof function. prototype === ‘Function’ instead of ‘object’; Nor as a constructor (using the new operator on it raises an error). This is a standard backward compatibility problem, just pay attention to this detail.
Function instance properties
First, we need to define the scope of the Function instance. We can briefly review several ways to create functions:
- Function declaration
- Functional expression
- Function constructor
- Function. The prototype. The bind method
As the standard has evolved, Function types have been added, including arrow functions, generator functions, async functions, etc. Functions created in these ways can be considered as instances of Function objects.
-
Length attribute: The value of the “length” property is an integral Number that indicates the typical number of arguments expected by the Function., can be understood as the number of parameters expected by the function, but the actual number of parameters passed in when the function is called may vary
-
Descriptive of The function. Name: The value of The “name” property is a String that is descriptive of The function
-
The prototype property:
{[[Writable]]: true, [[Enumerable]]: false, [[Writable]]: false} As you can see, the Prototype property is writable, but not enumerable or configurable, meaning we can redirect the prototype of a function instance.
Function instances that can be used as a constructor have a “prototype” property.
It’s also important to be clear: Only function instances that can be used as constructors have the Prototype attribute. The standard also states that async functions, generator functions, arrow functions, and functions created through bind do not have the Prototype attribute, so they cannot be used as constructors.
Function objects created using Function.prototype.bind, or by evaluating a MethodDefinition (that is not a GeneratorMethod or AsyncGeneratorMethod) or an ArrowFunction do not have a “prototype” property.
Continuing with the standard description of the prototype attribute of a function instance:
Whenever such a Function instance is created another ordinary object is also created and is the initial value of the function’s “prototype” property. Unless otherwise specified, the value of the “prototype” property is used to initialize the [[Prototype]] internal slot of the object created when that function is invoked as a constructor.
It is also a very important description, we can draw the following conclusions:
-
The prototype initial value of the function instance is an “ordinary Object”. Object that has the default behaviour for the essential internal methods that must be supported by all objects. This definition may not be very well understood, but a real implementation would typically be an Object instance Object with an initial constructor attribute pointing to the constructor function.
-
If not specified, when called as a constructor, this initial value is the [[prototype]] property value of the instance object it creates, as mentioned several times in the previous article. Of course we can also rewrite the prototype reference of a Functino instance, which is the underlying principle for implementing inheritance.
Based on the above introduction, we can draw the prototype chain diagram related to Function and Object. In fact, it is relatively easy to draw as long as we know the content specified in the standard:
In addition to Object and Function, the standard also introduces the built-in objects such as Boolean,String,Array, etc., and defines the prototype and [[prototype]] properties of these built-in objects. Topics related to prototypes and prototype chains can be better understood and remembered.