more work on the OOP doc...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2014-09-29 19:29:43 +04:00
parent ad14667008
commit 351ec981c7

171
js-oop.js
View File

@ -31,8 +31,11 @@
b.z // -> 3 b.z // -> 3
// What we see is that if the attribute is not found in the current // What we see is that if the attribute is not found in the current
// object it resolves to the object's "prototype" and so on, these // object it resolves to the next object, and so on, this next object is
// chians can of any length. // called "prototype".
// These prototype chains can be of any length.
// Cycles in prototype chains are not allowed, see note further down for
// an example.
// //
// Note that this works for reading, when writing or deleting we are // Note that this works for reading, when writing or deleting we are
// affecting ONLY the local object and attributes explicitly defined in // affecting ONLY the local object and attributes explicitly defined in
@ -58,8 +61,8 @@
// Now back to the mechanism that makes all of this work... // Now back to the mechanism that makes all of this work...
// //
// A couple of easy ways to see the local and non-local sets of // First we'll try couple of easy ways to see the local and non-local
// attributes: // sets of attributes:
// show local or "own" only attribute names (keys)... // show local or "own" only attribute names (keys)...
Object.keys(b) // -> z Object.keys(b) // -> z
@ -74,14 +77,23 @@
b.hasOwnProperty('x') // -> false b.hasOwnProperty('x') // -> false
// What happens under the hood is very simple: // What happens under the hood is very simple: b references it's
// "prototype" via the .__proto__ attribute:
b.__proto__ === a // -> true b.__proto__ === a // -> true
// We can read/set this special attribute just like any other attribute
// on most systems.
//
// NOTE: we did not see .__proto__ in the list of accessible attributes // NOTE: we did not see .__proto__ in the list of accessible attributes
// because it is a special attributes, it is implemented internally // because it is a special attribute, it is implemented internally
// and is not enumerable. // and is not enumerable.
// NOTE: cyclic prototype chains are actively not allowed, e.g. creating
// a chain like the following will fail:
// var a = {}
// var b = Object.creating(a)
// a.__proto__ = b
// //
// Thus, we could define our own create function like this: // Thus, we could define our own create function like this:
@ -102,11 +114,18 @@
// Turns out it is, and it points to Object's prototype // Turns out it is, and it points to Object's prototype
x.__proto__ === Object.prototye x.__proto__ === Object.prototype
// -> true // -> true
// We will discuss what this means and how we can use this in the next // We will discuss what this means and how we can use this in the next
// sections... // sections...
//
// As a side note, Object.prototype is the "root" most object in
// JavaScript and usually is "terminated" with null, i.e.:
Object.prototype.__proto__ === null
// We'll also need this a bit later...
@ -116,7 +135,7 @@
// JavaScript provides a second, complementary mechanism to inherit // JavaScript provides a second, complementary mechanism to inherit
// attributes, it resembles the class/object relationship in languages // attributes, it resembles the class/object relationship in languages
// like C++ but this resemblance is on the surface only, as it still // like C++ but this resemblance is on the surface only, as it still
// uses the same prototype mechanism as the above. // uses the same prototype mechanism as basis as described above.
// //
// We will start by creating a "constructor": // We will start by creating a "constructor":
@ -131,7 +150,17 @@
var a = new A() var a = new A()
// what 'new' does here is: // Some terminology:
// - in the above use-case A is called a constructor,
// - the object returned by new is called an "instance" (in this case
// assigned to a),
// - the attributes set by the constructor (x and y) are called
// "instance attributes" and are not shared (obviously) between
// different instances, rather they are "constructed" for each
// instance independently.
//
//
// Let's look in more detail at what 'new' does here:
// 1) creates an empty object // 1) creates an empty object
// 2) sets a bunch of attributes on it, we'll skim this part for now // 2) sets a bunch of attributes on it, we'll skim this part for now
// 3) passes the new object to the constructor via 'this' // 3) passes the new object to the constructor via 'this'
@ -146,63 +175,74 @@
var b = construct(A) var b = construct(A)
// But what does make this interesting? At this point this all looks like // But at this point this all looks like all we did is move the attribute
// all we did is move attribute definition from a literal object notation // definition from a literal object notation into a constructor function,
// into a constructor function, effectively adding complexity. What are we // effectively adding complexity.
// getting back from this? // And now instead of "inheriting" attributes we make a new set for each
// individual instance.
// So hat are we getting back from this?
// //
// Let's look at a number of attributes that new sets: // To answer this question we will need to look deeper under the hood,
// specifically at a couple of special attributes:
// we saw this one before...
a.__proto__ // -> {} a.__proto__ // -> {}
// this points back to the constructor...
a.constructor // -> [Function A] a.constructor // -> [Function A]
// These are what makes this fun, lets write a more complete new // These are what makes this fun, lets write a more complete new
// implementation: // re-implementation:
function construct(func, args){ function construct(func, args){
var obj = {} var obj = {}
// set some special attributes...
obj.constructor = func obj.constructor = func
obj.__proto__ = func.prototype obj.__proto__ = func.prototype
// call the constructor...
var res = func.apply(obj, args) var res = func.apply(obj, args)
// handle the return value of the constructor...
if(res instanceof Object){ if(res instanceof Object){
return res return res
} }
return obj return obj
} }
var b = construct(A) var b = construct(A)
// Notice that we return the resulting object in a more complicated // There are two important things we added here:
// way, this will come in handy later. // 1) we now explicitly use the .prototype attribute that we saw earlier
// 2) we return the resulting object in a more complicated way
// //
// Also notice that 'prototype' from the end of the previous section. // Each time a function is created in JavaScript it will get a new empty
// // object assigned to it's .prototype attribute.
// First let us cover the default. Each time a function is created in // On the function level, this is rarely used, but this object is very
// JavaScript it will get a new empty object assigned to it's .prototype
// attribute.
// On the function level, in general, this is not used, but this is very
// useful when the function is used as a constructor. // useful when the function is used as a constructor.
// //
// As we can see from the code above, the resulting object's .__proto__ // As we can see from the code above, the resulting object's .__proto__
// points to the constructor's .prototype, from the previous section // points to the constructor's .prototype, this means not-own the
// this means that attributes accessed via that object are resolved to // attributes accessed via that object are resolved to the prototype.
// the prototype. // In the default case this is true, but in general it's a bit more
// In the default case this is true. // flexible, we'll see this in the next section.
// //
// So if we add stuff to the constructor's .prototype they should get // And the way we handle the return value makes it possible for the
// resolved from the object // constructor to return a custom object rather than use the one
// provided in its "this" by new.
//
//
// So if we add stuff to the constructor's .prototype they should be
// accessible from the object
A.prototype.x = 123 A.prototype.x = 123
a.constructor.prototype.y = 321 a.constructor.prototype.y = 321
a.__proto__.z = 333 a.__proto__.z = 333
// for illustration, some object own attributes // for illustration, we'll set some object own attributes
a.x = 'a!' a.x = 'a!'
b.x = 'b!' b.x = 'b!'
@ -220,18 +260,43 @@
b.z // -> 333 b.z // -> 333
// This works for any constructor, including built-in constructors and
// since name resolution happens in runtime all instances will get the
// new functionality live, as it is defined:
// a "class method", like .keys(..) but return all available keys...
Object.allKeys = function(o){
var res = []
for(var k in o){
res.push(k)
}
return res
}
// now make these into real methods we can use from any object...
Object.prototype.keys = function(){ return Object.keys(this) }
Object.prototype.allKeys = function(){ return Object.allKeys(this) }
b.keys() // -> ['x']
b.allKeys() // -> ['x', 'y', 'z']
// "Double" inheritance // "Double" inheritance
// -------------------- // --------------------
// //
// There are actually three sources where JavaScript looks for attributes: // There are actually three sources where JavaScript looks for attributes:
// 1) the actual object // 1) own attributes (local object)
// 2) .__proto__ // 2) .__proto__
// as coverd in the first section // as coverd in the first section
// 3) .constructor.prototype // 3) .constructor.prototype
// as explained in the previous section // as explained in the previous section
// //
// Here is a basic inheritance structure (tree): // Though in the general case both .__proto__ and .constructor.prototype
// point to the same object and are redundant, the two are independent
// and can be used in parallel, thus the title.
//
// Here is a basic inheritance structure (tree) with .__proto__ and
// .constructor.prototype split to separate objects:
// //
// O A // O A
// \ / // \ /
@ -242,7 +307,9 @@
o: 0, o: 0,
} }
function A(){} function A(){
//...
}
A.prototype.a = 1 A.prototype.a = 1
var a = new A() var a = new A()
@ -276,10 +343,13 @@
// //
var a = {x: 1} var a = {x: 1}
var b = Object.create(a) var b = Object.create(a)
b.y = 2 b.y = 2
var c = Object.create(b) var c = Object.create(b)
c.x // -> 1 c.x // -> 1
c.y // -> 2 c.y // -> 2
@ -287,7 +357,7 @@
// Creating an inheritance chain via the constructor mechanism is a bit // Creating an inheritance chain via the constructor mechanism is a bit
// more involved, and there are multiple ways to do this... // more involved, and there are multiple ways to do this...
// //
// Here we will create a similar chian: // Here we will create a similar chain to the above for comparison:
// //
// C -> B -> A // C -> B -> A
// //
@ -326,6 +396,9 @@
c instanceof A // -> true c instanceof A // -> true
c instanceof Object // -> true c instanceof Object // -> true
c instanceof function X(){}
// -> false
// This also works for manually created objects // This also works for manually created objects
@ -366,6 +439,7 @@
isInstanceOf(c, A) // -> true isInstanceOf(c, A) // -> true
isInstanceOf(c, Object) isInstanceOf(c, Object)
// -> true // -> true
isInstanceOf(c, function X(){}) isInstanceOf(c, function X(){})
// -> false // -> false
@ -374,6 +448,9 @@
// Checking type (typeof) // Checking type (typeof)
// ---------------------- // ----------------------
// //
// This section is mainly here for completeness and to address several
// gotcha's.
//
// What typeof returns in JavaScript is not too useful and sometimes // What typeof returns in JavaScript is not too useful and sometimes
// even odd... // even odd...
@ -408,7 +485,7 @@
// Methods and the value of 'this' // Methods and the value of 'this'
// ------------------------------- // -------------------------------
// //
// A method is simply an attribute that references a function. // A "method" is simply an attribute that references a function.
function f(){ function f(){
return this return this
@ -422,9 +499,9 @@
// 'this' is a reserved word and is available in the context of a function // 'this' is a reserved word and is available in the context of a function
// execution, not just in methods, but what value it references depends // execution, not just in methods, but what value it references depends
// on how that function is called... // on how that function is called...
// This is mostly useful and used in methods. // 'this' is mostly useful and used in methods.
// //
// A simple way to think about this is that 'this' always points to the // A simple way to think about it is that 'this' always points to the
// "context" of the function call. // "context" of the function call.
// //
// There are three distinct cases here: // There are three distinct cases here:
@ -435,11 +512,12 @@
// //
// 1) function call (implicit) // 1) function call (implicit)
// In the first case the context is either global/window/module which // In the first case the context is either global/window/module which
// ever is the root context in a given implementation or null in ES5 // ever is the root context in a given implementation or undefined in
// strict mode // ES5 strict mode
f() // -> window/global/module f() // -> window/global/module
// Strict mode example: // Strict mode example:
// //
function strict_f(){ function strict_f(){
@ -451,8 +529,8 @@
// 2) new call (implicit) // 2) new call (implicit)
// Here as we have discussed before, this is assigned a new object with // Here as we have discussed before, 'this' is assigned a new object
// some attributes set. // with some special attributes set.
new f() // -> {} new f() // -> {}
@ -466,13 +544,14 @@
o['f']() // -> o o['f']() // -> o
// ...or an explicitly passed to .call(..) / .apply(..) object // ...or an explicitly passed to .call(..) / .apply(..) function methods
f.call(o) // -> o f.call(o) // -> o
f.apply(o) // -> o f.apply(o) // -> o
// ES5 also defines a third way to make method calls: Object.bind which
// creates a new function where 'there' is bound to the supplied object // ES5 also defines a third way to make method calls: Function.bind which
// creates a new function where 'this' is bound to the supplied object
var ff = f.bind(o) var ff = f.bind(o)
ff() // -> o ff() // -> o
@ -480,7 +559,7 @@
// NOTE: all of the above 5 calls are the same. // NOTE: all of the above 5 calls are the same.
// NOTE: the resulting from .bind(..) function will ignore subsequent // NOTE: the resulting from .bind(..) function will ignore subsequent
// .bind(..), .call(..) and .apply(..) method calls and this will // .bind(..), .call(..) and .apply(..) method calls and 'this' will
// always be the original bound object. // always be the original bound object.
// NOTE: the difference between strict and "quirks" modes is in the // NOTE: the difference between strict and "quirks" modes is in the
// following: // following: