diff --git a/Slang/slang.js b/Slang/slang.js index 968edcd..e846b16 100755 --- a/Slang/slang.js +++ b/Slang/slang.js @@ -128,7 +128,7 @@ var PRE_NAMESPACE = { // XXX should we look ahead and count the explicitly closed // via ']' and ']]' blocks??? // ...at this point this seems a bit complex... - // ...of there are more than one ']]' in a structure + // ...if there are more than one ']]' in a structure // this might stop being deterministic... code.splice(0, 0, cur) } diff --git a/js-oop.js b/js-oop.js index 60ccef5..10f4a49 100755 --- a/js-oop.js +++ b/js-oop.js @@ -5,124 +5,405 @@ * **********************************************************************/ // -// The value of 'this' +// The basic prototype inheritance +// ------------------------------- +// +// First we'll create a basic object a + + var a = { + x: 1, + y: 2, + } + +// Then we will create a new object using a as a "base" + + var b = Object.create(a) + b.z = 3 + +// The object b now has both access to it's own attributes ('z') and +// attributes of a ('x' and 'y') + + b.x // -> 1 + b.z // -> 3 + +// 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 +// chians can of any length. +// +// NOTE: there is also a second mechanism available but we'll discuss +// it a bit later in +// +// A couple of easy ways to see these sets of attributes: + + Object.keys(b) // -> z + + for(var k in b){ console.log(k) } + // -> x, y, z + +// Another way to test if the attribute is "local" ("own"): + + b.isOwnProperty('z') // -> true + b.isOwnProperty('x') // -> false + + +// What happens under the hood is very simple: + + b.__proto__ === a // -> true + + +// Thus, we could define our own create function like this: + + function clone(from){ + var o = {} + o.__proto__ = from + return o + } + + var c = clone(b) + +// Out of curiosity let's see if .__proto__ is defined on a basic object + + var x = {} + + x.__proto__ // -> {} + +// Turns out it is, and it points to Object's prototype + + x.__proto__ === Object.prototye + // -> true + +// We will discuss what this means and how we can use this in the next +// sections... +// +// +// +// The Constructor Mechanism +// ------------------------- +// +// JavaScript provides a second, complementary mechanism to inherit +// attributes, it resembles the class/object relationship in languages +// like C++ but this resemblance is on the surface only as it still +// uses the same prototype mechanism as the above. +// +// We will start by creating a constructor: + + function A(){ + this.x = 1 + this.y = 2 + } + +// Technically a constructor is just a function, what makes it a +// "constructor" is how we use it... + + var a = new A() + + +// what 'new' does here is: +// 1) creates an empty object +// 2) sets a bunch of attributes on it +// 3) passes it to the constructor via 'this' +// 4) after the constructor returns, this object is returned +// +// We could write an equivalent (simplified) function: + + function construct(func){ + var obj = {} + + // set some special attributes on obj... + + return func.apply(obj) + } + + var b = construct(A) + +// But what makes this interesting? At this point this all looks like +// all we did is moved attribute from a literal object notation to a +// constructor function, effectively adding complexity. What are we +// getting back from this? +// +// Let's look at a number of attributes: + + a.__proto__ // -> {} + + a.constructor // -> [Function A] + + +// The answer lies in the attributes that are set on the object, lets +// write a more complete reference implementation: + + function construct(func, args){ + var obj = {} + + obj.constructor = func + obj.__proto__ = func.prototype + + var res = func.apply(obj, args) + if(res instanceof Object){ + return res + } + + return obj + } + + var b = construct(A) + +// Notice that we return the resulting object in a more complicated +// way, this will come in handy later. +// +// Also notice that 'prototype' from the end of the previous section. +// +// First let us cover the default. Each time a function is created in +// 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 used +// when we use the function as a constructor. +// +// As we can see from the code above, the resulting object's .__proto__ +// points to the constructor's .prototype, from the previous section +// this means that attributes accessed via that object are resolved to +// the prototype. +// In the default case this is true. +// +// So if we add stuff to the constructor's .prototype they should get +// resolved from the object + + A.prototype.x = 123 + a.constructor.prototype.y = 321 + a.__proto__.z = 333 + + // for illustration, some object own attributes + a.x = 'a!' + b.x = 'b!' + + a.x // -> 'a!' + a.y // -> 321 + a.z // -> 333 + +// These values are accessible from all objects constructed by A since +// all of them point to A with both the .constructor and .__proto__ +// attributes + + b.x // -> 'b!' + b.y // -> 321 + b.z // -> 333 + + + +// Double inheritance: // ------------------- // +// NOTE: this might be implementation specific. Tested and works in IE11, V8 +// +// There are actually three sources where JavaScript looks for attributes: +// 1) the actual object +// 2) .__proto__ +// as coverd in the first section +// 3) .constructor.prototype +// as explained in the previous section + + var O = { + o: 0 + } + + function A(){} + A.prototype.a = 1 + + var a = new A() + a.__proto__ = o + +// Now we can access both attributes inherited from 'O' and 'A'... + + a.o // -> 0 + a.a // -> 1 + + +// The check is done specifically in this order, thus attributes can +// "shadow" other attributes defined later in the chain. +// +// To show this let us define an attribute with the same name on both +// 'O' and 'A': + + O.x = 'came from O' + A.prototype.x = 'came from A' + + a.x // -> 'came from O' + + +// In both inheritance mechanisms, each step is checked via the same +// rules recursively, this enables inheritance chains. +// +// We will create a cahin: +// +// c -> b -> a +// + + var a = {x: 1} + var b = Object.create(a) + b.y = 2 + var c = Object.create(b) + + c.x // -> 1 + c.y // -> 2 + + +// Creating an inheritance chain via the constructor mechanism is a bit +// more involved, and there are multiple ways to do this... +// +// Here we will create a similar chian: +// +// C -> B -> A +// + + function A(){} + A.prototype.x = 1 + + function B(){} + // NOTE: if this is done after an instance is created, that instances' + // .__proto__ will keep referencing the old prototype object. + // see the next constructor for a way around this... + B.prototype = Object.create(A.prototype) + B.prototype.y = 2 + + function C(){} + // NOTE: this is safer than Object.create as it does not overwrite + // the original object and thus will affect all existing + // instances of C, if any were created before this point... + C.prototype.__proto__ = B.prototype + + var c = new C() + + c.x // -> 1 + c.y // -> 2 + + + +// Checking inheritance (instanceof) +// --------------------------------- +// +// An object is considered an instance of its' constructor and all other +// constructors in the inheritance chain. + + c instanceof C // -> true + c instanceof B // -> true + c instanceof A // -> true + c instanceof Object + // -> true + + +// This also works for manually created objects + + var cc = construct(C) + + cc instanceof C + + +// But this will not work outside the constructor model, i.e. if the right +// parameter is not a function. + + var x = {} + var y = Object.create(x) + + try{ + // this will fail as x is not a function... + y instanceof x + } catch(e){ + console.log('error') + } + + +// Again to make this simpler to understand we will implement our own +// equivalent to instanceof: + + function isInstanceOf(obj, proto){ + return proto instanceof Function + && (obj.__proto__ === proto.prototype ? true + // NOTE: the last in this chain is Object.prototype.__proto__ + // and it is null + : obj.__proto__ == null ? false + // go down the chian... + : isInstanceOf(obj.__proto__, proto)) + } + + isInstanceOf(c, C) // -> true + isInstanceOf(c, B) // -> true + isInstanceOf(c, A) // -> true + isInstanceOf(c, Object) + // -> true + isInstanceOf(c, function X(){}) + // -> false + + +// Checking type (typeof) +// ---------------------- +// +// What typeof returns in JavaScript is not too useful and sometimes +// even odd... + + typeof c // -> 'object' + +// This might differ from implementation to implementation but +// essentially the main thing typeof is useful for is distinguishing +// between objects and non-objects (numbers, strings, ...etc.) + + // non-objects + typeof 1 // -> 'number' + typeof Infinity // -> 'number' + typeof 'a' // -> 'string' + typeof undefined // -> 'undefined' + + // objects + typeof {} // -> 'object' + typeof [] // -> 'object' + + // the odd stuff... + typeof NaN // -> 'number' + typeof null // -> 'object' + typeof function(){} // -> 'function' + + + +// Methods and the value of 'this' +// ------------------------------- +// +// A method is simply an attribute that references a function. + function f(){ console.log(this) this.a = 1 } + var o = { f: f } +// Thus we call the attribute .f of object o a method. +// +// +// '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 +// on how that function is called... // -// 'this' is always the "context" of the function call, in JS a context -// can be: +// a simple way to think about is that 'this' always points to the +// "context" of the function call. +// +// This context can be: // - implicit // - root context - f() // 'window' or 'module' is implied, this is - // equivalent to: window.f() + f() + // 'window', 'global' or 'module' is implied, + // in strict mode this is null. + // the same as: + // window.f() // - 'new' context new f() // here a context will be created and passed to - // 'f's 'this', for more details on what 'new' - // does see the next section. + // 'f's 'this', for more details on what 'new' + // does see: "The Constructor Mechanism" section. // - explicit: -// - the object on the left side of "." or the [ ] attribute access: +// - the object on the left side of "." or the [ ] operators: o.f() // o is the context o['f']() // the same as the above // - the object explicitly passed to .call(..) or .apply(..) methods // as first argument: f.call(o) // o is the context -// -// -// -// What's 'new'? -// ------------- -// - function O(){ - this.attr = 123 - } - var o = new O() - -// -// 'new' creates a new context object and passes it to the function -// being called (the constructor), this new object has several special -// attributes set: -// o.constructor -- references the object (constructor) used to -// construct the object o (instance) -// o.__proto__ -- references the constructor prototype, same as: - o.constructor.prototype === o.__proto__ -// this is used to identify the object via: - o instanceof O -// -// The 'new' expression returns the context object after it has been -// populated by the constructor function. -// -// NOTE: when using 'new', the function/constructor return value is -// ignored. -// -// -// The values set on 'this' by the constructor are instance attributes, -// i.e. they are stored in the specific instance being created. -// -// NOTE: calling the constructor is not required, but not calling it -// will not run the initialization code that would otherwise -// populate the object. i.e. no instance values will be created. -// -// -// The instance has another type of attribute accessible through it, an -// attribute that's not stored in the object, but rather in it's -// prototype (o.__proto__), or rather the constructor's prototype -// (o.constructor.prototype). For more details see the next section. -// -// -// -// The object's 'prototype' -// ------------------------ -// - function f(){} - console.log(f.prototype) // f {} - -// -// The unique prototype object is created on every function definition. -// The prototype is instance-like of the function to which it is assigned. -// -// NOTE: since the prototype has .__proto__ set to Object, technically -// it's not a strict instance of the type it defines, so: - f.prototype instanceof f -// will return false. -// -// Each unresolved attribute access on the "instance" will resolve to its -// prototype (o.__proto__ or o.constructor.prototype). -// - O.prototype.x = 321 - console.log(o.x) // 321 - -// -// Since the prototype is a JS object that adheres to the same rules as -// any other object, if the attr is not resolved in it directly, it will -// be searched in its prototype, and so on. -// This principle enables us to implement inheritance. -// - var OO = function(){ - // call the base constructor... - O.call(this) - } - // chain the two prototypes... - OO.prototype = new O() - // this is to make things coherent for oo below... - OO.prototype.constructor = OO - - var oo = new OO() - - console.log(oo.x) // 321 - console.log(oo.constructor.name) // 'OO' - -// -// So in the example above local attributes (in 'oo') will be searched -// first, then oo.__proto__ (instance of O), then in O.prototype, and -// then in Object.prototype. -// -// oo -> oo.__proto__ -> (oo.__proto__).__proto__ -> ... + f.apply(o) // o is the context // // //