Lecture
Prototype-based programming is a style of object-oriented programming in which there is no notion of a class, and inheritance is achieved by cloning an existing object instance — the prototype.
The canonical example of a prototype-oriented language is Self. This programming style later began to gain popularity and was made the foundation of such programming languages as JavaScript, Lua, Io, REBOL and others.

In class-oriented languages a new instance is created by calling the class constructor (possibly with a set of parameters). The resulting instance has the structure and behaviour rigidly defined by its class.
Prototype-oriented systems provide two ways of creating a new object: cloning an existing object, or creating an object "from scratch". To create an object from scratch, the programmer is given syntactic means for adding properties and methods to an object. A complete copy — a clone — can then be obtained from the resulting object. During cloning the copy inherits all the characteristics of its prototype, but from that moment on it becomes independent and can be modified. In some implementations copies keep references to their prototype objects, delegating part of their functionality to them; in that case changing the prototype may affect all of its copies. In other implementations new objects are completely independent of their prototypes. Both of these cases are examined below.
//An example of inheritance in prototype-based programming
//using JavaScript as the example language
//Creating a new object
let foo = {name: "foo", one: 1, two: 2};
//Creating one more new object
let bar = {two: "two", three: 3};
bar.__proto__ = foo; // foo is now the prototype for bar
//If we now try to access foo's fields from bar
//it will work
bar.one // Equals 1
//Its own fields are available too
bar.three // Equals 3
//Own fields take priority over prototype fields
bar.two; // Equals "two"
In prototype-oriented languages that use delegation, the runtime is able to dispatch method calls (or look up the required data) simply by following the chain of delegating pointers (from an object to its prototype) until a match is found. Unlike the "class — instance" relationship, the "prototype — descendants" relationship does not require descendant objects to retain structural similarity with their prototype. Over time they can adapt and improve, and there is no need to rework the prototype. Importantly, it is not only data that can be added/removed/modified, but functions as well, and functions are themselves first-class objects. Because of this, most prototype-oriented languages call an object's data and methods "slots".
In "pure" prototyping — also called cascading and represented in Kevo — cloned objects keep no references to their prototypes. The prototype is copied one-to-one, with all its methods and attributes, and the copy is given a new name (reference). This resembles the mitosis of biological cells.
Among the advantages of this approach is the fact that the creator of a copy can change it without fear of side effects among the other descendants of its ancestor. Dispatch costs are also radically reduced, since there is no need to walk the entire chain of possible delegates in search of a suitable slot (method or attribute).
Among the drawbacks one can include the difficulty of propagating changes through the system: modifying a prototype does not entail an immediate and automatic change in all of its descendants. Nevertheless, Kevo provides additional means for publishing changes across a set of objects, and does so on the basis of their similarity ("family resemblance") rather than the presence of a common ancestor, which is typical of delegation-based models.
Another drawback is that the simplest implementations of this model lead to increased memory consumption (compared with the delegation model), since every clone, until it is modified, will contain a copy of its prototype's data. However, this problem can be solved by optimally sharing unmodified data and applying "lazy copying" — which is exactly what was used in Kevo.
In languages based on the notion of a "class", all objects are divided into two main types — classes and instances. A class defines the structure and functionality (behaviour) that is the same for all instances of that class. An instance is the carrier of data — that is, it has state that changes in accordance with the behaviour defined by the class.
Advocates of prototype-based programming often argue that class-based languages lead to excessive focus on class taxonomy and on the relationships between classes. In contrast, prototyping concentrates attention on the behaviour of some (small) number of "samples", which are then classified as "base" objects and used to create other objects. Many prototype-oriented systems support changing prototypes while the program is running, whereas only a small proportion of class-oriented systems (for example, Smalltalk, Ruby) allow classes to be changed dynamically.
Although the vast majority of prototype-oriented systems are based on interpreted languages with dynamic typing, it is technically possible to add prototyping to languages with static type checking as well. The Omega language is one example of such a system.
__proto__ and prototypal inheritanceEvery object in JS has a hidden reference to another object — its prototype (__proto__).
If an object does not have the required property, the engine looks for it in the prototype, then in the prototype's prototype, and so on (the prototype chain).
This is called prototypal inheritance.
const parent = { greet: () => console.log("Hello!") };
const child = {};
child.__proto__ = parent;
child.greet(); // "Hello!" — taken from the prototype
parentIn PHP inheritance is implemented through classes.
The keyword parent is used to access the methods or constructors of the parent class.
class ParentClass {
public function greet() {
echo "Hello!";
}
}
class ChildClass extends ParentClass {
public function greet() {
parent::greet(); // calling the parent's method
echo " And hi from child!";
}
}
__proto__ and inheritance in PHP via parent:| Criterion | JavaScript (__proto__) |
PHP (parent) |
|---|---|---|
| Inheritance model | Prototypal: objects inherit properties and methods through the prototype chain | Classical: classes inherit methods and properties from parent classes |
| Keyword / mechanism | __proto__ (or Object.setPrototypeOf, class extends) |
parent:: |
| Flexibility | Very dynamic: prototypes can be changed on the fly | Rigidly defined when classes are declared |
| Usage example | js\nconst parent = { greet: () => console.log("Hello!") };\nconst child = {};\nchild.__proto__ = parent;\nchild.greet(); // Hello!\n |
php\nclass ParentClass {\n public function greet() { echo "Hello!"; }\n}\nclass ChildClass extends ParentClass {\n public function greet() {\n parent::greet();\n echo " And hi from child!";\n }\n}\n |
| Property/method lookup | Automatic lookup along the prototype chain | Explicit call via parent::method() |
| Main area of use | Objects and functions | Classes and OOP |
Conclusion:
In JS inheritance goes through a chain of objects.
In PHP inheritance goes through a hierarchy of classes.
In both cases we are talking about inheriting behaviour from a "parent".
JS does this through the prototype chain of objects, while PHP does it through a hierarchy of classes.
JS: a more flexible and dynamic model (prototypes can be changed on the fly).
PHP: a strict object-oriented model (classes are fixed, inheritance is rigidly defined).
Advocates of class-oriented object models who criticise the prototype-based approach are often troubled by the same problems that concern advocates of static typing with regard to dynamically typed languages. In particular, discussions revolve around such topics as program correctness, safety, predictability and efficiency.
As for the first three points, classes are often regarded as types (and indeed, in most statically typed object-oriented languages that is exactly what they are), and classes are assumed to provide certain contracts and to guarantee that instances will behave in a well-defined way.
As for efficiency, declaring classes considerably simplifies the compiler's optimisation task, making both methods and attribute lookup in instances more efficient. In the case of the Self language, a great deal of time was spent developing compilation and interpretation techniques that would bring the performance of prototype-oriented systems closer to that of their class-oriented competitors. Further work in this direction, together with progress in the theory of JIT compilers, has led to a situation where the difference between class- and prototype-oriented approaches now has little effect on the efficiency of the resulting code. In particular, the prototype-oriented Lua is one of the fastest interpreted languages and competes directly with many compiled ones, while the Lisaac translator generates ANSI C code that is practically on a par with native code.
Finally, perhaps the most common criticism levelled at prototype-based programming is that the software development community is not sufficiently familiar with it, despite the popularity and prevalence of JavaScript. In addition, since prototype-oriented systems are relatively new and are still few and far between, development techniques that use them have still not become widespread.
Comments