4
Objects and Prototypes

JavaScript distinguishes itself from most usual “object-oriented programming” (OOP) languages, which are “class based”: JavaScript is “prototype based”. This originality is confusing for people accustomed to other OOP languages, and sometimes perceived as a weakness vis-à-vis better “controlled” languages such as Java, of which it “inherited” the name based on a misunderstanding.

4.1. Introduction

This chapter is organized as follows:

  • – an introduction about the notions of “concept” versus “named entities” should help in understanding the difference between “class-based” and “prototype-based” OOP;
  • section 4.2: This section details the syntax and use of the “object literal” in JavaScript, popular through the JSON format, which is widely used to exchange data over the Internet;
  • section 4.3: This section deals with the methods of the built-in objects “Object” and “Object.prototype”, how they provide the notion of inheritance by delegation and how objects can be related;
  • section 4.4: After analyzing the role of prototypes, we detail the three approaches for creating objects:
    • - literal: this includes the operator {..}, the role of the functions Object and Function, to enlighten the role of the “prototypes” in the inheritance mechanism in JavaScript,
    • - prototypal: this includes the innovative methods Object.create and Object.assign (ES6),
    • - classical: this consists of the operator “new” and the “constructor” functions.

We can hardly talk about objects without talking about functions, and even arrays. Therefore, Chapters 57 are very intertwined. But “object” is probably the most basic notion, and we start with it, even if several “forward references” must be used in the text to point out that some tools will be defined later on.

The prototypal approach may seem puzzling, but it provides flexibility and ease in programming (maybe too much?). We do hope that this chapter will convince you that it is better to stand with the very nature of JavaScript, and use the prototypal approach instead of the classical one, as often as possible, and especially for the kind of “data-oriented” applications targeted by this book.

4.2. The objects: concepts versus named entities

An object is something that we can grasp (a glass on a table), distinguish at a glance (a cloud in the sky) or which we can describe by some characteristics (an appointment with a friend). In any language, an object is either a particular object (this glass on this table), or the generic concept that we attach to a set of similar particular objects (“a” glass).

These two viewpoints may be as old as any human language: is the generic concept different from the set of the named entities it represents? (see Platoʼs “Cave allegory”, the “problem of universals”). The computer languages face the same issue:

  • – should we create a generic representation (a “class”) from which to create particular “instances”?
  • – should we use a particular object to represent all objects similar to it?

A metaphor may help identify the issue: let us dare to quote the field of law: exclusive use of Jurisprudence (any case may be used a posteriori) versus exclusive use of the Civil Code (any case reduces to an article of the Code).

We propose to rephrase the distinction between:

Generic concept, an a priori representation of similar objects, made of:

  • – an (abstract) structure, probably a tree-structure, of named containers;
  • – an operator to create instances;
  • – the ability to modify, augment, the objects after their creation.

Named entities, represented by an explicit notation:

  • – an (explicit) structure of named containers with a defined content;
  • – a link to the reference of the set of similar objects.

Let us first study how JavaScript answers the named entity notion.

4.3. Object literal notation in JavaScript

A JavaScript object is merely the container of a collection of named values. Here is the syntax of the notation representing such a collection.

4.3.1. Syntax for “object literal”:

The operator typeof answers "object" to every notation complying with the following syntax (object notation):

  • – the container is delimited by curly braces { … }:
        {} is the empty object
    
  • – the inside collection is made of comma separated “properties”;
  • – a property is a couple “property_name”: “value”:
        { "key_1" : "value_1", "key_2" : "value_2", … }
    
  • – a key can be used with or without “quotes”;
  • – a value must comply with the requirements of any variable value: primitive value, literal: number, string, another object, including array and function, or more complex expression. Object literals can be nested (see the following).

This notation is simple, but may present some traps: here are several warnings to help you to avoid writings that will mislead the interpreter.

4.3.2. Important warnings about writing JavaScript object notation

4.3.2.1 The name of the properties

Any Unicode character can be used, and any word, including “reserved word”. However, we recommend to respect these best practices:

  • – start with a letter (or possibly a dollar sign $ or underscore _);
  • – preferably do not use reserved words;
  • – preferably use quotes, for they are mandatory in the JSON format.

Examples of valid property names, complying best practice:

    { "firstName": "Jean", "name2": "O", "_salaire_€": 20e3, …

4.3.2.2 The type and value of properties

The type of the property is the type of its value (idem: variables):

  • string: (idem variables) the engine store, the string and the address (reference) becomes the value of the property.
           "first": "Jean", // address of the string "Jean"
    
  • number or boolean: the primitive values must be written without quotes, unless they are strings and a conversion may be necessary:
           "age": 22,     // type number
           "age": "22",   // type string: to convert if required
    
  • object (including array): any value whose typeof is "object" is valid:
            circonscription": { "dept": "Gironde", "numero": 2 }
            affiliation": [ "party_1", "party_2" ]

    These literals are stored and their references become the values.

  • function: any function expression, then called a “method”:
      fullName: function(){return this.first +" "+ this.last;}

    The methods are not accepted in JSON format, only as strings:

       fullName: "function(){return this.first +' '+ this.last;}"

4.3.3. The object literal first use: to define an object type variable

An object literal on the right-hand side of a variable definition statement is stored, and the address is assigned to the (left-hand) variable. Example:

  const candidatN = {
          "first": "Jean", "last": "Dupont",
          "age": 22,
          "circonscription": {
                "dept": "Gironde", "numero": 2
          },
          "fullName": function(){return this.first+" "+this.last;}
 };

The object is created when the engine meets the curly braces {.}: the built-in function Object is implicitly invoked. The keyword this will be explained later.

4.3.4. The object literal second use: data notation in JSON format

The object literal is a mere character string that we can archive in a plain text file. The further reading of the file will provide a string that could be directly used as the literal for an object in the target code, written in JavaScript or possibly some other programming language: the chapter on AJAX details this importation mechanism.

This idea of a plain text file for the object notation has been specified by Douglas Crockford in 2001, precisely “Java Script Object Notation” (JSON). Since then, it has been massively used as an exchange format over the Internet. To facilitate the interface with those “JSON files”, an object JSON has been added to JavaScript, with two static methods:

  • – JSON.parse() interprets the string as a literal and returns the object.
  • – JSON.stringify() converts an object into a string, keeping only the “enumerable properties”, and making some editing (see below).
    const candidatN = {
           "first": "Jean", "last": "Dupont", "age": undefined,
           "circ": {
                   dept: "Gironde", num: 2
           },
           "fullN" : function(){return this.first +" "+ this.last;}
    };
    JSON.stringify(candidatN);
    //prints:
    {"first":"Jean","last":"Dupont","circ":{"dept":"Gironde","num":2}}

NOTE.– White spaces or line feeds are removed, quotes are added (dept, num), and the method is ignored, as well as the property whose value is undefined.

4.3.5. Accessing the individual properties of an object

There are two notations to denote one particular property of an object:

  • – dot notation: candidatN.first; NB: property name without quotes;
  • – bracket notation: candidat [“first”]; NB: property name with quotes.
    let dN = candidatN.circo.dept;
        dN = candidatN["circo"]["dept"];

The dot notation is simpler, but cannot be used in every case, in particular if the name of the property is given through a variable. For example:

function f(prop){
      candidatN[prop]; // ok, if prop exists in candidatN
      candidatN.prop; // undefined : "prop" isn't a property
}
f("first"); // gives prop, in f, the value "first"

4.3.5.1 Using references in the object literal

For instance, with the nested object “circo”, we can create the object once, outside of any “candidat” object, then the reference to that “circo” object can be mutualized between several “candidat-like” objects. To mutualize avoids creating multiple instances of the “Gironde” string, and makes it possible to “inherit” further additions, modifications brought to the mutualized reference:

const circo_3302 = { // (Gironde INSEE number is 33)
                 "dept": "Gironde", "num": 2
       };
const candidatN = {
       "first": "Jean", "last": "Dupont", "circo": circo_3302
};
circo_3302.population = 25600;
candidatN.circo.population // 25600 new property is "concatenated"

4.3.6. Notation syntax evolution with ES6

Table 4.1. Shorthand syntax for object literal notation (ES6)

Regular notation ES6 shorthand
let a = [[value]];
const obj = {
  a: a,
  foo: function() {…}
};
let a = [[value]];
const obj = {
  a,
  foo() {…}
};
  A declared and defined variable can be used directly as a property. Methods are named directly.

Using JSON.stringify(obj) restores the regular JSON notation. For example:

let a = "A", b = 1;
JSON.stringify({a, b});        // {"a":"A","b":1}
JSON.stringify({a:a, b:b});    // {"a":"A","b":1} : identical

4.4. The built-in methods of Object and Object.prototype

4.4.1. The methods of Object, Object.prototype, and JSON

Table 4.2 lists the “static methods” of the built-in object Object, which must be invoked directly from Object: Object.method().

Table 4.2. The most usual static methods of Object

Method Description Return
Object.assign Copies the enumerable own properties of (o1,o2 … Object
Object.create Creates an object whose prototype is (proto) Object
Object.defineProperties
Object.defineProperty
Creates or modifies the properties of (o)
Idem. only one property
Object
Object.freeze
Object.isFrozen
Prevent against modifications of (o)
Test status
Object
Boolean
Object.getPrototypeOf Returns the [[prototype]] of (o) Object
Object.setPrototypeOf [to avoid : instead use Object.create()]  
Object.keys Object.values
Object.entries
lists names of enumerable own properties of (o),
idem for values,
idem for [key, value]
Array

Table 4.3 lists the methods of the object Object.prototype, which are delegated to all objects. They are invoked from any object: obj.method().

Table 4.3. The methods of Object.prototype, delegated to any object

Method Description Return
obj.hasOwnProperty Tests if property (p) is “own” Boolean
obj.isPrototypeOf Tests if prototype chain contains (o1) Boolean
obj.propertylsEnumerable Tests if property (p) is “enumerable” Boolean
obj.toString
obj.toLocaleString
Provides a stringified version of the object (idem with locale rules) String

And, it is worth remembering here the two (static) methods of the object JSON.

Table 4.4. Static methods of JSON

Method Description Return
JSCN.parse () Transforms a valid JSON notation into an object Object
JSCN.stringify () Gives the string JSON notation from an object String

4.4.2. Create an object and specify its properties

Here is the syntax of the two most useful methods:

Object.assign( target, source [, source2, …] ) makes it possible to “augment” the object “target” with the properties of another object “source” (or several). Returns the augmented “target”.

Object.create( proto [, descriptor1, descriptor2, …] ) creates an object from the object “proto” used as the prototype of the new object. The (optional) descriptors make it possible to add individual properties with specific control attributes.

The two methods Object.defineProperty( obj, prop, descriptor ), and Object.defineProperties( obj, objectWithDescibedProps ) also utilize the “descriptors” properties, as described below.

4.4.3. Syntax and usage of the “descriptor” property

In the methods described in the following, Descriptors have been added to JavaScript by ES5, to better control the rights (e.g. read/write) of every property. All the descriptors possess:

  • configurable: the descriptor itself can be modified and the property can be deleted “delete” (default: false);
  • enumerable: the property is enumerable (default: false).

    Then two cases (exclusives): the “data descriptors” with:

    • value: the value of the property (default: undefined);
    • writable: the property can be modified (default: false);

and the “accessor descriptors” instead have:

  • get: function returning the value of the property (def.: undefined);
  • set: function whose argument becomes the value (def.: undefined).

Using a “data descriptor”:

{ "property name" : {     "value": value,
                          "enumerable": boolean,
                          "configurable": boolean,
                          "writable": boolean }
}

By default, all three booleans are initialized to false.

WARNING.– For an object created by a literal, only the couple (key, value) is considered, and by default, all three booleans are initialized to true.

The Object.freeze( obj ) method prevents any modification of the object, which constant does not do.

The methods Object.getPrototypeOf(obj),

obj.isPrototypeOf(obj2),obj.hasOwnProperty(p),
obj.propertyIsEnumerable(p),and
Object.getOwnPropertyNames(obj) are obvious.

Object.getOwnPropertyNames and similar methods can be added which make it possible to list otherwise invisible properties (not of use in this work).

4.4.4. Listing the properties of an object, analyzing a literal

4.4.4.1 Using the instruction for…in

There exists a property enumeration instruction in JavaScript: for..i n

const candidatN = {"first": "Jean", "last": "Dupont", … …};
for (let prop in candidatN) {console.log(prop);}
for (let prop in candidatN) {console.log(candidatN[prop]);}
// these instruction lines successively display:
    "first", "last", "age", "circonscrition"
    "Jean", "Dupont", 22, [Object()]

NOTE 1.– for..in looks like a loop, but it is an enumerator: it lists the enumerable properties of the object, either own properties or delegated by the prototype. To break down between own/delegated properties, use obj.hasOwnProperty(prop).

NOTE 2.–for..in is not recursive, for a nested object, you must code recursively.

4.4.4.2 Using the static methods of Object

Object.keys(obj)/.values/.entries returns an array of the enumerable properties of obj in the same order than for..in but limited to each property: keys returns the names, values the values and entries the couples. Let us compare:

for (let p in candidatN) {
if(candidatN.hasOwnProperty(p)){console.log(candidatN[p]);}
}
// equivalent to:
Object.values(candidatN).forEach(function(v){console.log( v );};

4.4.4.3 Methods of JSON object (with option function)

The JSON methods accept a second, optional, argument: a function with two arguments (key, value).

For example, with ‘JSON.stringifyʼ we cannot directly “stringify” a method, but we can get its code as plain text::

function stringifyTheMethods(k, v) {
return (typeof v === "function")? v.toString(): v;
});
JSON.stringify(candidatN, stringifyTheMethods);
// yields:
{"first":"Jean","last":"Dupont","circo":{"dept":"Gironde","num":2},"fullName ":"function() {return this.first +" "+ this.last;}"}

NOTE.– The quotes used in the code are escaped.

For example, with ‘JSON.parseʼ, to avoid warning messages (“JSON badly formed”), we can add escapements for tabulations, line breaks, etc., with regular expressions::

function escapeSpecialChars(k, v) {
      return v.replace(/"/g, "\"")   // quotes
              .replace(/
/g, "\n")   // line feed
              .replace(/
/g, "\r")   // carriage return
              .replace(//g, "\b")   // back space
              .replace(/	/g, "\t")   // horizontal tab
              .replace(/f/g, "\f");  // new page
}
JSON.parse( jsonText, escapeSpecialChars );

4.5. Basics of the “prototypal approach” in JavaScript

JavaScript takes into account the notion of “named entity”:

  • – the object literal provides the means to create individual objects;
  • – the JSON notation makes it possible to archive and to reuse such objects, including with different languages, for instance Python, or usual speadsheets.

Question:

  • Does JavaScript take into account the notion of “generic concept”?
  • And by which means?

Many OOP languages use a software feature, named a “class”, to represent the generic concept, plus a mechanism to build “instances” of that class, which are the named entities. In JavaScript, there is no such thing as a class, neither an instance: So, what do we do?

NOTE.– The ambition of Netscape was to have a scripting language similar to Java. Brendan Eich, in order to complete his contract during the very short allotted time, chose the “prototypal approach”, which is to say: the mere addition, in every object, of a link to another object, named its “prototype”. This simple choice has since fueled many misunderstandings, especially because of the operator: “new”. Despite the introduction of the keyword “class” in ES6, nothing has changed: classes and instances still do not exist in JavaScript.

Does the “prototypal approach” answer the three earlier mentioned criteria for generic concepts ? Does it provide:

  • – a tree structure of named containers?
  • – a means to build named entities of a given model?
  • – the ability to modify, or to augment the objects, once having been created?

4.5.1. JavaScript object’s fundamental relation: “has prototype”

Instead of being member of a “class”, every JavaScript object owns a link to a particular object: its “prototype”. Though it is a property of the object, this link is not directly accessible (not part of the norm). Let us name this link: [[prototype]].

Figure 4.1 demonstrates the fundamental power and limits of this relation.

image

Figure 4.1. The maths behind the prototype: two dual relations and a tree structure

So far, we know how to build objects from literals. Now, the question is:

How can we build an object with a given object as its prototype?

Since ES6, there is a simple answer: thanks to the method Object.create, we can pick an object and use it as the prototype for the new object:

const proto = {/* any object, e.g. a literal */};
   const newob = Object.create(proto); // newob.[[prototype]] = proto

The strict partial orderimage and the associated tree structure determine the “prototype chain” of every object. To keep it simple, let us say that any JavaScript object is built:

  • – either from a literal, and its [[prototype]] is Object.prototype:
    { … } ¬ Object.prototype ¬ null
  • – or from Object. create (proto), and its [[prototype]] is proto:
    x ¬ proto ¬ … ¬ Object.prototype ? null

The overall tree structure is made up of nodes that are prototypes, except the terminal leaves, and its root is Object.prototype (we can ignore null). All nodes, which are neither root, nor leaves, can be labeled by the prototype that is characteristic of the related “equivalence class”.

We can say that these equivalence classes are the “generic concepts” we were looking for in the previous section, and, at the same time, any such generic concept is also a “named entity”, for the prototype is an object (remember the similarity with “jurisprudence”).

4.5.2. Role of the prototypes and inheritance mechanism

Given an object (p), it is by itself a named entity, and can play the role of a generic concept for other objects (x): x ¬ p

The method Object.create(p) allows us to create one x whose prototype is p, and Object.getPrototypeOf(x) allows us to check if the prototype is p:

const p = { oprint(){return 'I am p the prototype';} };
const x = Object.create(p);
if(Object.getPrototypeOf(x) === p){console.log(x.oprint());}
      // " I am p the prototype " (the test is positive)

Let us create a new object using x as prototype:

const y = Object.create(x);
if(Object.getPrototypeOf(y) === x){console.log(y.oprint());}
     // " I am p the prototype " (the test is also positive)

The objects x and y successively inherit the method oprint from p. The relation is transitive and the chain of prototypes is:

y x ¬ p ¬ Object.prototype

And inheritance is transitive as well: the object that plays the role of prototype automatically delegates its methods and properties to the members of the equivalence class, which it labels.

This “inheritance by delegation” works as follows:

When a property is “called” on an object x, for instance through the dot notation x.oprint, then the property is searched among xʼs own properties. If not found, the same search is performed for each member of the prototype chain down to Object.prototype. The first occurrence found becomes the value, else undefined is the value.

4.5.2.1. Conclusion

With the object notation {…} we can create objects whose prototype is Object.prototype.

With the Object.create(p) method, we can create objects whose prototype is the object p, which therefore becomes a prototype unbeknownst to itself.

The prototypes chain is also an inheritance chain: the properties of an object playing the role of a prototype are delegated to all the objects upwards in the chain.

The inheritance by delegation is dynamic: any modification of a method of a certain prototype object automatically modifies the method used by any object that has the prototype in its chain.

NOTE.– By contrast, a method is “static” if that method is owned by the object itself.

JavaScript is very permissive; it is sometimes unwise to use this flexibility: some recommendations are as follows:

RECOMMENDATION 1.– Objects are containers, the contents can be modified, but do not modify the box: use const x = Object.create(p); or const x = {..}; to declare and create an object.

RECOMMENDATION 2.– Its prototypes chain is the marker of an object; do not modify that chain once created and do not use Object.setPrototypeOf(x).

In the following, three object construction approaches are described: literal with the operator {..}, prototypal with Object.create and classical with the operator new.

4.5.3. Object construction: the “literal approach”

To better understand object construction, we need some additional background.

4.5.3.1. The object–function “Function”

The built-in object Function is a function, and as such (see chapter 5) it owns a special property named prototype, which is not to be confused with the [[prototype]] property. Hence Function owns a Function.prototype and a [[prototype]].

4.5.3.2. The object–function “Object”

The built-in object Object is a function, therefore it owns an Object.prototype, which points to the root to any prototypes chain, and whose [[prototype]] is null.

4.5.3.3. The operator {..}

The literal approach for creating an object is: const x = {..}; it:

  • – creates a new object x: implicitly with function Object;
  • – sets the prototype: x[[prototype]] = Object .prototype;
  • – sets constructorʼs name: x.constructor.name = "Object".
image

Figure 4.2. The diagram of the literal object

Once created, the object may receive new properties, and others can be modified:

const x = {last: "Dupont", first:"J"};
x.first = "Jean";
x.full = function(){return this.first+" "+this.last}

NOTE.– These properties and methods are named “static” and we must use the object itself to modify them. By contrast, properties or methods delegated from the prototype can be modified independently.

4.5.4. Object construction: the “prototypal approach”

Any object, created with the literal approach, can be used to create a new object whose [[prototype]] is that first object:

const protox = {last = "Dupont", first = "Jean";
      full(){return this.first+" "+this.last}};
const x = Object.create(protox);
console.log(x.full());                    // Jean Dupont
if(Object.getPrototypeOf(x) === protox)   // true
  console.log(x.constructor.name);       // Object

The method 'Object.create' acts as follows:

  • – creates a new object x;
  • – set: x. [[prototype]] = protox;
  • – the property x.constructor inherits from protox.constructor.
image

Figure 4.3. The diagram of the object created from a prototype

4.5.5. The pattern “assign/create”

function Candidat(){} // provides Candidat.prototype
Candidat.prototype.full = function(){return this.first+ … };
const x = Object.assign(        // line 3
            Object.create(Candidat.prototype),
            {last = "Dupont", first = "Jean"} );
console.log(x.full());                      //-> Jean Dupont
if(Object.getPrctctypeOf(x) === Candidat.prototype) //true
       console.log(x.constructor.name);      //-> Candidat

The combination Object.assign/Object.create (lines 3–5) underlines the distinction between the “generic concept” (the argument of 'create') and the “named entity” (arguments 2+ of 'assign'). We call it the assign/create pattern (see “Design patterns” in Chapter 7 for more details).

The method Object.assign adds new properties, or modifies existing ones according to its arguments 2 and next. To remove an existing property, we need to apply the operator 'delete' on that property. The property constructor has been valuated to Candidat by Object.create.

Further adding of methods to Candidat.prototype will automatically trigger those methods for every object created through the assign/create combo.

In Part 3, this pattern is put into practice with several “data-oriented” applications.

4.5.6. Object construction: the “classical approach”

4.5.6.1. What does the JavaScript operator new do?

It invokes a function, which is named a “constructor”. Good practice is to capitalize its name with an uppercase letter:

function Candidat(last){this.last = last;}       // line 1
const x = new Candidat("Dupont");                // line 2

Line 1: Two objects are created: Candidat and Candidat.prototype.

WARNING.– The property Candidat.[[prototype]] is Function.prototype, for Candidat is a function, which inherits function’s methods: e.g. call.

Line 2, new Candidat, acts as follows:

  • – creates a new object x, which is assigned to the pronoun this, inside the function. Otherwise, this = window (or the global object);
  • – returns implicitly this (unless an explicit return is coded: to avoid!);
  • – updates x.[[prototype]] = Candidat.prototype, which makes x inherit by delegation;
  • – updates x.constructor = Candidat.
image

Figure 4.4. The diagram of the object created by a constructor

The role of the constructor is very limited: the object Candidat.prototype is the one that really triggers the inheritance by delegation. The overall classical operation1 is complex, even if it is transparent for the programmer.

4.6. Comparing “prototypal” and “classical” approaches

Let’s explore the arguments of Douglas Crockford2, who advocated the introduction of Object.create

He was simulating that method3 using the operator new:

Object.create = function (proto) {
   function F() {}                // creates a prototype property
   F.prototype = proto;           // makes proto that prototype
   return new F();         // creates an object with proto as prototype
};

4.6.1. Simulating a class hierarchy in JavaScript

A class hierarchy is based upon the “Is-a” relation, for example, “a candidate is-a person” (plus some specific properties). Let us compare the two approaches.

4.6.1.1. Classical simulation: class “Person” and subclass “Candidate”

function Person (last, first) {                        // line 1
    this.last = last || "?";
    this.first = first || "";
}
function Candidate (last, first, dN) {                 // line 2
     Person.call(this, last, first); // uses code of Person with this
     this.dN = dN || "somewhere";
}
Candidate.prototype = new Person();                    // line 3
Candidate.prototype.constructor = Candidate;           // line 4
Person.prototype.fullName = function(){                // line 5
        return this.first+" "+this.last;
};
Candidate.prototype.fullName = function(){            // line 6
       return Person.prototype.fullName.call(this)+", at "+this.dN;
};
const c1 = new Person("D","Jean", "Creuse");
const c2 = new Candidate("D","Jean", "Creuse");

Let us comment on the logics of that code:

  • – lines 1 and 2, the functions Person and Candidate are creating prototypes, and make some initializations;
  • – line 2: Candidate “recycles” the code of Person with itself (this), i.e. the object that will be created when invoking new Candidate(). This is the job of Person.call (see Chapter 6).
  • – line 3: links Candidate to the prototypes chain of Person:
    c2 ¬ Candidate.prototype ¬ Person.prototype ¬ Object.prototype
  • – line 4: updates property constructor, which line 3 initialized to Person;
  • – line 5: adds method fullName to Person.prototype;
  • – line 6: recycles the code Person.fullName in Candidate.prototype.

The inheritance is controlled in lines 3-4. Finally, for c1, c2, let’s print: 'c.constructor.name', JSON.stringify(c) and c.fullName():

     name                            JSON                   fullName()
Perscn        {"last":"D","first":"Jean"}               Jean D
Candidate     {"last":"D","first":"Jean","dN":"Creuse"} Jean D, en Creuse

4.6.1.2. Prototypal simulation: class “Person” and subclass “Candidate”

function Person(){}                                 // line 1
function Candidate(){}                              // line 2
Person.prototype.fullName = function(){             // line 3
    return this.first+" "+this.last;
};
Candidate.prototype = Object.assign(                // line 4
                 Object.create(Person.prototype),
                 { fullName(){
    return Person.prototype.fullName.call(this)+", at "+this.dN;
}});
Candidate.prototype.constructor = Candidate;         // line 5
const p1 = Object.assign(                            // line 6
                 Object.create(Person.prototype),
                 { last:"D", first:"Jean", dN:"Creuse"});
const p2 = Object.assign(                            // line 7
                  Object.create(Candidate.prototype),
                  { last:"D", first:"Jean", dN:"Creuse"});

Let us comment and compare:

  • – lines 1 and 2, functions Person and Candidate merely create prototypes;
  • – line 3: adds fullName to Person.prototype (= line 5 classical);
  • – line 4: the assign/create pattern modifies Candidate.prototype to inherit from Person.prototype, and updates method fullName (= line 6 classical);
  • – line 5: updates Candidate.prototype.constructor (= line 4 classical).

Figures 4.5 and 4.6 demonstrate the similarity of the two approaches.

image

Figure 4.5. Subclassing in the classical approach

image

Figure 4.6. Subclassing in the prototypal approach

In Figures 4.5 and 4.6, the numbered dotted lines represent the prototype chains of the following objects:

c2/p2 ¬ Candidate.prototype ¬ Person.prototype ¬ Object.prototype ¬ null.

In the prototypal approach, 'pattern' replaces 'new', and there is no initialization for Person and Candidate.

4.6.2. Summing up what we learned so far

4.6.2.1. Properties

  • – Every object owns two types of properties: “own”, or “inherited” by delegation from one prototype of its prototypes chain;
  • – among own properties, some are not enumerable: the property [[prototype]] is hidden, which protects it. Preserving the link, preserves the mechanism of delegation;
  • – the property constructor refers to the function f that provides its f.prototype to the object (if a literal f = Object). The constructor.name property is shared by all the objects of an equivalence class.

4.6.2.2. Values

  • – The value of a property is a reference: after deleting a property, the garbage collector will check if the referred value must be flushed or not.

4.6.2.3. Methods

  • – Methods are generally meant to be shared by the objects linked to the same prototype: you must add them to that prototype.

Methods defined in the constructor are “static” and methods defined in its prototype are “delegated”. For example:

function Candidate(last, first){    // method in the constructor
   this.last = last; this.first = first;
   this.full = function(){return this.first+" "+this.last};
}
const c1 = new Candidate("Dupont"), c2 = new Candidate("Durand");
(cl.full === c2.full);             // false: not shared

The full method is different for each new instruction. You may get hundreds!

  function Candidate(last, first){
     this.last = last; this.first = first;
}                        // method in the prototype
Candidate.prototype.full = function(){return this.first+"…};
(cl.full === c2.full);   // true: shared

A single method 'full' is delegated: not found among the own properties, the method is taken from the prototype. One unique version for hundreds!

4.6.2.4. Inheritance kinds (delegation, concatenation)

There are two ways to provide properties to an object:

  • delegation: if the referred property is present in the prototypes chain, inheritance is dynamic. (see: 'Object.create');
  • concatenation: if the referred property is copied from another object, it becomes static to be updated whenever necessary (see: 'Object.assign').

4.6.2.5. Multiple inheritance in JavaScript

An object inherits from only one prototype (delegation). The concatenation makes it possible to multiply static inheritances under the condition that the referred objects (from which copies are made) remain unchanged. This can be the case when we concatenate methods or specific values, which are meant to be constant. One solution can be to “freeze” those referred objects (Object.freeze), hence preserving them from later modifications.

4.6.2.6. Conclusion about the notion of “class” in JavaScript

There is no “class” nor “instance” in JavaScript, however we have learnt that a generic concept is provided by the "equivalence class” induced by the relation “has prototype”, and we can name it (constructor.name). Any member of that class can be named an “instance”, if you are pleased with it.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.14.151.45