We're not talking about ES6 classes today. Just ES5 JavaScript object-oriented programming. ## Prototypal inheritance People say there's at least these **3 big features of OOP** that distinguishes OOP from other styles of programming: 1. Encapsulation 2. Polymorphism 3. Inheritance We're going to focus on the last item today. OOP in JavaScript isn't intrinsically class-based in the style of Java. I assume you already know a thing or two about OOP in terms of Java or Python, or similar though. Instead, **JavaScript OOP is prototype based**. That means you'd just make an object `c` as you like it, no class definition required --- but if you must think in classes, imagine making a class `C` and using it to create exactly the one object `c`. Then when you want another object `s` to inherit from object `c`, you'd just do it, *no class required* --- again if you must, imagine making a class `S` that inherits from `C`, and using `S` to create exactly the one object `s`. Conceptually, it's simple, but trying to use it productively, with constructors and everything else, and it can get confusing. Rather than learning how to do class-based OOP in JavaScript (for that, see MDN's [Introduction to Object-Oriented JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript) instead), let's try to really understand what JavaScript is doing --- from the bottom up. ## Diving in, bottom up In JavaScript, everything are objects, even functions. OK, some things are primitives... forget that, let's just see some code. By the way, I'm running all the code in the Firefox Console. Explanations and diagrams are interwoven into the code as comments. ## Cosmic Super Object Let's start with the "cosmic super" object that all objects usually inherit from, namely the object stored at `Object.prototype`. ```JavaScript typeof(Object.prototype); // is an object Object.prototype.prototype === undefined; // true Object.prototype.__proto__ === null; // true ``` So `Object.prototype` doesn't have a `prototype`, and it doesn't "`__proto__`-inherit" from anything. The buck stops here. In JavaScript, each object `o` has a `__proto__` property that points to the object that `o` **prototypely-inherits** from (as opposed to "inherit" in the class-based sense). So from here on, if `s.__proto__ === k` then we say `s` **proto-inherits** from `k`. I use the term "proto-inherit" to distinguish from the Java-style, class-based, concept of inheritance. Yes, `__proto__` is deprecated, but it works (for now) and is easier to think with. We're going to start diagram out the relationship between the various objects we're exploring in JavaScript. ![Object.prototype UML-like diagram](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJZIMM9zCjENTA9a-3J3tm3NwQV-KrRXFQzB5zyxwDC7Q7qPSXXrXncOHmTktqCMqdcAsw6xRAMbrKfj2FGI56gDbAwoBmqe6-ovF_ZS1tprzxwaSeHfUnqfBQ7sOSXRXbrHCA89AGQFA/s1600/d1.txt.png) As described before, if you must think in classes, then making an object in JavaScript is like writing a class and instantiating exactly one object from it, so we'll draw objects in the style of UML classes. i.e. ![UML-like class diagram example schema](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjEss9UraMrJC7cwso34SN0A_SnETwAmxgnmLoYHQLanN68PI-Sd5Xr6KBPA1q-1HFp8H9dbkBgAUI7o6Expfhn5y4ePFGneu1sRJCoYwih4dP_NuyNeiGvysk2FqMc4lUszCaotE7dj4/s1600/d2.txt.png) ## Object function But if `Object.prototype` is an object, what is `Object`? ```JavaScript typeof(Object); // is a function. ``` Huh, but it had a `prototype` property before... I guess functions are like objects and can have properties. In fact, there is a circular relationship between `Object` and `Object.prototype`: ```JavaScript Object.prototype.constructor === Object; // true ``` Since functions can have properties, we'll draw them like objects but tag them with "fn:" ![Circular relationship between fn Object and Object.prototype](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgw2LI308pvxETX61mR7VYvqZH3mFJS71sn_ZLgoFPTygfm_jwi_LmzPXh-plk-1ZZVtPY18SIehfFFvLPtNoxFi2Ikfpuz6pET94i_1whyphenhyphenkjGLicaJFCLPw_YtxGg-BRImBw1aHKxdUak/s1600/d3.txt.png) There's a general circular relationship between functions and prototype objects as we'll soon see. ## Function function Since `Object` is a function, what is `Function`? ```JavaScript typeof(Function); // is also a function ``` Actually, `Function` is very much like `Object` with a circular relationship: ```JavaScript Function.prototype.constructor === Function; // true ``` But what is `Function.prototype`? ```JavaScript typeof(Function.prototype); // is a function ``` So there's a general circular relationship between functions and prototype objects as well: ![Circular relationship between fn Function and Function.prototype](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuPF29bpW2vp-gRCzPr42gT1c4CLaGu6uJCdv_FjqK6nbOUC2JqeVCeU7pxNOiUr8NnFBNGtNuJ8C8yHXvM3JnvzIC8hHsPjjU1M77zAjvML5AehBC0IQnsq_tNkpwrf8yAo445j8rOgQ/s1600/d4.txt.png) ## How do Function and Object relate? Well we already know `Object` and `Function` are both functions. Now observe where they proto-inherit from: ```JavaScript Object.__proto__ === Function.prototype; // true Function.__proto__ === Function.prototype; // true ``` Wasn't `Function.prototype` a function as well? Yes and no... ```JavaScript typeof(Function.prototype); // recall that's a function Function.prototype.__proto__ === Object.prototype; // true ``` ![Circular relationship between fn Object and Object.prototype, and between fn Function and Function.prototype](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJhd17gDojdt-labL0wFRS4dTscyh6miYnTm0Ah8QFjNVEXmX-sB6pR-s5D40GI461_K9LvvIfZLbvWWyZDagKJrEfXwgAICl60XxcNyfXuMQU3iBux1BmkV49shIPFfd2f32QByp1DB4/s1600/d5.txt.png) So the buck stops at `Object.prototype`, and `Function.prototype` is a function but proto-inherits from `Object.prototype`. `Object` and `Function` are both more straightforward: both are functions and proto-inherits from `Function.prototype`. ## Literal objects and functions Consider the following literal object and function: ```JavaScript var x = {}; typeof(x); // is an object var f = function(){}; typeof(f); // is a function ``` So what do they proto-inherit from? ```JavaScript x.__proto__ === Object.prototype; // true f.__proto__ === Function.prototype; // true ``` ...they proto-inherit not from `Object` or `Function`, but from `Object.prototype` or `Function.prototype`. Remember the general circular relationship between functions and prototype objects? Well `x` is an object, so I suspect we won't find it related circularly with its prototype object (if it has one)... ```JavaScript x.prototype === undefined; // true ``` But `f` is a function, so let's see if it has a circular relationship with its prototype object... ```JavaScript typeof(f.prototype); // is an object f.prototype.__proto__ === Object.prototype; // true f.prototype.prototype === undefined; // true f.prototype.constructor === f; // true, truly circular! ``` Thus we see the following relationship `x`, `f`, and the existing circular relationships between `Object`, `Function`, and their prototypes: ![Relationship between Object, Function, their prototypes, and object x and function f](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOr45lN9l-LPz9AMjmRLVkvxpfMP54xceqL9mpX7iQwbF-K-haYIV9IZVl5gLH1SjTIr1_iFyhLa39IsuCj0bjOGSVNOa_5DGRf5pNso4fjARMw1TVSCBsHcZ-Q2H_xiggqf3r9fD6Tcw/s1600/d6.txt.png) ## Let's start writing some "classes" The goal here is to end up with a "super class" called `SuperKlass`, which will have as a "subclass" the class `Klass`. ```JavaScript var SuperKlass = function(){}; // a constructor for our super "class". typeof(SuperKlass.prototype); // is an object. SuperKlass.__proto__ === Function.prototype; // true SuperKlass.prototype.__proto__ === Object.prototype; // true SuperKlass.prototype.constructor === SuperKlass; // true ``` So merely writing a function creates a circular relationship between a function and an object: ![Relationship between Function.prototype and SuperKlass, and Object.prototype and SuperKlass.prototype.](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgT-GnQ0tXAQtfJOJ02r0Frb7m4lDKOOERu4z07wB4yUbkKL_-yizcD7uQmiPA5tGKiiC6MqJyfn3RawR2hkcFBEgKBRO_PnnjsyGXTo2Orw9V8x3I9VB_ywZ18BRr05pBxX01uA3RmUA/s1600/d7.txt.png) We'll abbreviate our diagram of the `Function.prototype` and `Object.prototype` objects. ```JavaScript var Klass = function(){}; // a constructor for another "class" ``` As with the `SuperKlass` case above, we get this circular relationship: ![Relationship between Function.prototype and Klass, and Object.prototype and Klass.prototype.](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiaQWqkD5vVpxmcuwgb_doNxXVcMqTpgnXapMgzO_jGH23z9klsJGGJt1l2sbeJNSeA_SPuDf1BR-EOFuyaaUfcHdoVjatBa9J-8yA6IEiYOFdyWfm-NyF1_93Op3q_eOyQ0kWtkqejt0M/s1600/d8.txt.png) `Klass` and `SuperKlass` don't look like classes, but they would if we started writing some methods for them. But we'll hold of on that until we get some class-based inheritance going on first as those two are currently unrelated to each other. ## Proto-inherit our way to writing "subclasses" The above don't look like classes yet, but they will once we make `Klass` inherit from `SuperKlass`. ```JavaScript var oldKlassProto = Klass.prototype; // for later testing Klass.prototype = Object.create(SuperKlass.prototype); Klass.prototype === oldKlassProto; // false. It's really a new object! Klass.prototype.__proto__ === SuperKlass.prototype; // true Klass.prototype.constructor === SuperKlass; // true ``` The `Object.create` line of code basically did the following: create a new object `o` that proto-inherits from `SuperKlass.prototype`, then set `o.constructor` to the `constructor` of `SuperKlass.prototype`, which is of course just `SuperKlass` because of the circular relationship we saw above! Then we set `Klass.prototype` to `o` resulting in this relationship: ![Relationship between Klass, Klass.prototype, SuperKlass, SuperKlass.prototype, and Object and Function prototypes, after using Object.create.](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqAQynhPfRyI9oNlAydARZg9KKtMl8YHVsoShd7rg0xgmW-1BltlZFFlFd80PHe7MWcQzV6DcuT6RD_W_3UwANabRmeBX2clW-6MUeOc-GPi9Z5wGO2euu0F9ZuCV5a6-qMzhhR95aTMo/s1600/d9.txt.png) Notice the `Klass.prototype.constructor` no longer points back to `Klass`, breaking the circular relationship between constructor functions and prototypes we kept seeing above. Let's repair that... ```JavaScript Klass.prototype.constructor = Klass; ``` ![Relationship after repairing Klass.prototype.constructor to point to Klass](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwx_vHpDKSwHatiEbKbPKT2ZDJQfz640hWDn9YX1Yb5BndPDqGa2W958oH5CA4rbcoB6cOtzImFMChc704YmcaqqORmBPlnU6sK0EiRi0B00Mpjh36KjCcOdPywCZ2tM3RbzrfXdrftkQ/s1600/d10.txt.png) Now `Klass` is a subclass inheriting from `SuperKlass` --- inheriting, not proto-inheriting. ## Instance method for our Klass and some new objects Let's dynamically create a `SuperKlass` instance method: ```JavaScript SuperKlass.prototype.hi = function(){return "super!"}; ``` Notice where the method is created in the diagram and how it relates to `SuperKlass`, and `Klass`: ![Relationship after dynamically patching in a "hi" method in SuperKlass.prototype](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHAC3o2Katyy0MTOTeJXbp6JgtpON3wX3TJVYOc4MJ4Y3aEyau-7NghFiMpNy4nLQoPIXovxvPEZdBwsZUeJi8iW2fuhxduqNX4WFNa1msdLPVwMd3EgukzouQs4kCsphAW7RFHdeqZeM/s1600/d11.txt.png) We keep making classes but never instance objects constructed from those classes, so let's do that now: ```JavaScript var sk = new SuperKlass(); var k = new Klass(); sk.prototype === undefined; // true k.prototype === undefined; // true. They really are just objects sk.__proto__ === SuperKlass.prototype; // true k.__proto__ === Klass.prototype; // true ``` So `new F()` just makes a fresh object that proto-inherits from `F.prototype`. In fact, it also runs the `F()` constructor function on that fresh object so to initialize it by doing whatever `F` does. ![Relationship between k and Klass, and sk and SuperKlass.](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieQZxagZ6zrIBovw4Sg5SE99IrwehtpIKv-M9g4A8Yl4FNXmo3UJV-xRLtweAOCy42N0jgRDycjGULR8bv1G_aKc9EI_GFJcfj5WQPdwd7UnA0Y-_0sKy4TcZZ_u4iEGJzGq0Gzg5oyGM/s1600/d12.txt.png) ## Polymorphism "Polymorphism" here just means, if an object `k` has it, use it, otherwise repeat looking for it with the object that `k` proto-inherits from (done recursively, again and again...). ```JavaScript sk.hi(); // "super!" ``` ...note that `sk` itself doesn't have a method `hi()`, but `sk.__proto__` (a.k.a. `SuperKlass.prototype`) has it! ```JavaScript k.hi(); // "super!" - polymorphic call to inherited method ``` `k` itself doesn't have `hi()`, but in this case, `k.__proto__` (a.k.a. `Klass.prototype`) doesn't have it either! But `k.__proto__.__proto__` (a.k.a. `SuperKlass.prototype`) does have it! In effect we've got, in Java terms, an instance method `hi()` defined in the `SuperKlass` class, and `Klass` inherits it being a subclass. So `k` is like an instance of the class `Klass`, and `sk` is like an instance of the class `SuperKlass`... even though JavaScript has no native concept of class or class-based inheritance (not until ES6 anyway). Let's write more methods, this time dynamically adding the `hi` method to `Klass.prototype`: ```JavaScript Klass.prototype.hi = function(){return "klass!"}; ``` Notice how this `hi` method relates to the `Klass.prototype` object, "overriding" the one proto-inherited from `SuperKlass.prototype`: ![Relationship after dynamically patching in a "hi" method in Klass.prototype](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiy61cgwO1v0tFIozlgQBaIW_ybFwdD-8kbDrerlsiCoTW5qgnyufyGaA7MMP2Cy6iWO6VthZlGtATpBe8dh1suG8jBsDf28I0lLnkyxb1f2YQnjbG9krKX8JrcvBQ2TscU21-bXugfpkg/s1600/d13.txt.png) ```JavaScript sk.hi(); // "super!" k.hi(); // "klass!" - polymorphic call to overriding method ``` ...of course `k` itself doesn't have a method `hi()`, but `k.__proto__` (a.k.a. `Klass.prototype`) this time does! Although JavaScript has no native concept of class or class-based inheritance (not until ES6), it kind of half-heartedly does since ES3 (JavaScript 1.4) via the `instanceof` operator which basically just checks to see if a function's `prototype` is proto-inherited by, or is proto-inherited proto-inherited by, or is proto-inherited proto-inherited proto-inherited by, ..., the given object. ```JavaScript sk instanceof Klass; // false sk instanceof SuperKlass; // true ``` The last line evaluates to `true` because `sk` proto-inherits from `SuperKlass.prototype` ```JavaScript k instanceof Klass; // true k instanceof SuperKlass; // also true! ``` The last line *also* evaluates to `true` because `k` proto-inherits proto-inherits `SuperKlass.prototype` Just follow the proto-inheritance hierarchy! ## this and super Let's write another subclass. ```JavaScript var Klass2 = function(nm){ // super(); in Java terms SuperKlass.call(this); // call superclass's constructor // with `this` object this.name = nm; }; Klass2.prototype = Object.create(SuperKlass.prototype); Klass2.prototype.constructor = Klass2; // Klass2 inherits from SuperKlass via usual proto-inheritance hierarchy: Klass2.prototype.__proto__ === SuperKlass.prototype; // true // make a `Klass2` instance var k2 = new Klass2("Pat"); k2.hi(); // "super!" - polymorphic call to inherited method // define a `Klass2` instance method Klass2.prototype.hi = function(){return this.name}; sk.hi(); // "super!" k.hi(); // "klass!" k2.hi(); // "Pat" ``` More or less, `this` refers to the object the method is called on, but technically it gets tricky as JavaScript's concept of a method is really in terms of functions that are stored as properties on objects. For details, see [MDN's JavaScript reference of this](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this). Notice the dynamicism of JavaScript, where we added a new instance method to an existing class, and all existing instance objects of that class got to use the newly defined instance method. No recompilation needed, unlike e.g. Java. ## References It was noted that `__proto__` is deprecated. It was a way of exposing to us the internal linkages between JavaScript's internal representation of objects. The "proper" way now is through using the `getPrototypeOf` method. See [MDN's reference on Object.prototype.\_\_proto\_\_](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto). A more in-depth explanation of JavaScript OOP can be found at MDN's reference on [Inheritance and the prototype chain](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain) or at [Introduction to Object-Oriented JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript). A hugely more in-depth explanation with beautiful diagrams is available at [JavaScript: the core](http://dmitrysoshnikov.com/ecmascript/javascript-the-core). Incidentally, had I found that article first, I might never have written this one... ...because I wrote this to help myself understand JavaScript's prototypal OOP system by drawing diagrams and playing around with JavaScript in the Firefox Console. Conceptually, it was so simple, but then when you actually program with it, or when you try to use it in a more class-based manner, it can get confusing. All diagrams originally drawn with [ASCII Flow](http://asciiflow.com). Diagrams turned into pretty PNG images using [Shaape](https://github.com/christiangoltz/shaape).
on software development, computing science, software technologies, learning, etc.
2016-07-11
JavaScript OOP inheritance explained with diagrams
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment