JavaScript. The core.

时间:2023-03-08 21:31:16
JavaScript. The core.

Read this article in: Japanese, German (version 2), Arabic, Russian, French, Chinese.

This note is an overview and summary of the “ECMA-262-3 in detail” series. Every section contains references to the appropriate matching chapters so you can read them to get a deeper understanding.

Intended audience: experienced programmers, professionals.

We start out by considering the concept of an object, which is fundamental to ECMAScript.

An object

ECMAScript, being a highly-abstracted object-oriented language, deals with objects. There are also primitives, but they, when needed, are also converted to objects.

An object is a collection of properties and has a single prototype object. The prototype may be either an object or the null value

Let’s take a basic example of an object. A prototype of an object is referenced by the internal [[Prototype]] property. However, in figures we will use __<internal-property>__ underscore notation instead of the double brackets, particularly for the prototype object: __proto__.

For the code:

var foo = {
x: 10,
y: 20
};

we have the structure with two explicit own properties and one implicit __proto__ property, which is the reference to the prototype of foo:

JavaScript. The core.

Figure 1. A basic object with a prototype.

What for these prototypes are needed? Let’s consider a prototype chain concept to answer this question.

A prototype chain

Prototype objects are also just simple objects and may have their own prototypes. If a prototype has a non-null reference to its prototype, and so on, this is called the prototype chain.

A prototype chain is a finite chain of objects which is used to implement inheritance and shared properties.

Consider the case when we have two objects which differ only in some small part and all the other part is the same for both objects. Obviously, for a good designed system, we would like to reuse that similar functionality/code without repeating it in every single object. In class-based systems, this code reuse stylistics is called the class-based inheritance — you put similar functionality into the class A, and provide classes B and C which inherit from A and have their own small additional changes.

ECMAScript has no concept of a class. However, a code reuse stylistics does not differ much (though, in some aspects it’s even more flexible than class-based) and achieved via the prototype chain. This kind of inheritance is called a delegation based inheritance (or, closer to ECMAScript, a prototype based inheritance).

Similarly like in the example with classes A, B and C, in ECMAScript you create objects: a, b, and c. Thus, object a stores this common part of both b and c objects. And b and c store just their own additional properties or methods.

var a = {
x: 10,
calculate: function (z) {
return this.x + this.y + z;
}
}; var b = {
y: 20,
__proto__: a
}; var c = {
y: 30,
__proto__: a
}; // call the inherited method
b.calculate(30); //
c.calculate(40); //

Easy enough, isn’t it? We see that b and c have access to the calculate method which is defined in a object. And this is achieved exactly via this prototype chain.

The rule is simple: if a property or a method is not found in the object itself (i.e. the object has no such an own property), then there is an attempt to find this property/method in the prototype chain. If the property is not found in the prototype, then a prototype of the prototype is considered, and so on, i.e. the whole prototype chain (absolutely the same is made in class-based inheritance, when resolving an inherited method — there we go through the class chain). The first found property/method with the same name is used. Thus, a found property is called inherited property. If the property is not found after the whole prototype chain lookup, then undefined value is returned.

Notice, that this value in using an inherited method is set to the original object, but not to the (prototype) object in which the method is found. I.e. in the example above this.y is taken from b and c, but not from a. However, this.x is taken from a, and again via the prototype chain mechanism.

If a prototype is not specified for an object explicitly, then the default value for __proto__ is taken — Object.prototype. Object Object.prototype itself also has a __proto__, which is the final link of a chain and is set to null.

The next figure shows the inheritance hierarchy of our a, b and c objects:

JavaScript. The core.

Figure 2. A prototype chain.

Notice: ES5 standardized an alternative way for prototype-based inheritance using Object.create function:

  var b = Object.create(a, {y: {value: 20}});
var c = Object.create(a, {y: {value: 30}}); You can get more info on new ES5 APIs in the appropriate chapter. ES6 though standardizes the __proto__, and it can be used at initialization of objects.

Often it is needed to have objects with the same or similar state structure (i.e. the same set of properties), and with different state values. In this case we may use a constructor function which produces objects by specified pattern.

Constructor

Besides creation of objects by specified pattern, a constructor function does another useful thing — it automatically sets a prototype object for newly created objects. This prototype object is stored in the ConstructorFunction.prototype property.

E.g., we may rewrite previous example with b and c objects using a constructor function. Thus, the role of the object a (a prototype) Foo.prototype plays:

// a constructor function
function Foo(y) {
// which may create objects
// by specified pattern: they have after
// creation own "y" property
this.y = y;
} // also "Foo.prototype" stores reference
// to the prototype of newly created objects,
// so we may use it to define shared/inherited
// properties or methods, so the same as in
// previous example we have: // inherited property "x"
Foo.prototype.x = 10; // and inherited method "calculate"
Foo.prototype.calculate = function (z) {
return this.x + this.y + z;
}; // now create our "b" and "c"
// objects using "pattern" Foo
var b = new Foo(20);
var c = new Foo(30); // call the inherited method
b.calculate(30); //
c.calculate(40); // // let's show that we reference
// properties we expect console.log( b.__proto__ === Foo.prototype, // true
c.__proto__ === Foo.prototype, // true // also "Foo.prototype" automatically creates
// a special property "constructor", which is a
// reference to the constructor function itself;
// instances "b" and "c" may found it via
// delegation and use to check their constructor b.constructor === Foo, // true
c.constructor === Foo, // true
Foo.prototype.constructor === Foo, // true b.calculate === b.__proto__.calculate, // true
b.__proto__.calculate === Foo.prototype.calculate // true );

This code may be presented as the following relationship:

JavaScript. The core.

Figure 3. A constructor and objects relationship.

This figure again shows that every object has a prototype. Constructor function Foo also has its own __proto__ which is Function.prototype, and which in turn also references via its __proto__ property again to the Object.prototype. Thus, repeat, Foo.prototype is just an explicit property of Foo which refers to the prototype of b and c objects.

Formally, if to consider a concept of a classification (and we’ve exactly just now classified the new separated thing — Foo), a combination of the constructor function and the prototype object may be called as a “class”. Actually, e.g. Python’s first-class dynamic classes have absolutely the same implementation of properties/methods resolution. From this viewpoint, classes of Python are just a syntactic sugar for delegation based inheritance used in ECMAScript.

Notice: in ES6 the concept of a “class” is standardized, and is implemented as exactly a syntactic sugar on top of the constructor functions 
as described above. From this viewpoint prototype chains become as an implementation detail of the class-based inheritance: // ES6
class Foo {
constructor(name) {
this._name = name;
} getName() {
return this._name;
}
} class Bar extends Foo {
getName() {
return super.getName() + ' Doe';
}
} var bar = new Bar('John');
console.log(bar.getName()); // John Doe

The complete and detailed explanation of this topic may be found in the Chapter 7 of ES3 series. There are two parts: Chapter 7.1. OOP. The general theory, where you will find description of various OOP paradigms and stylistics and also their comparison with ECMAScript, and Chapter 7.2. OOP. ECMAScript implementation, devoted exactly to OOP in ECMAScript.

Now, when we know basic object aspects, let’s see on how the runtime program execution is implemented in ECMAScript. This is what is called an execution context stack, every element of which is abstractly may be represented as also an object. Yes, ECMAScript almost everywhere operates with concept of an object