mirror of
https://github.com/flynx/Slang.git
synced 2025-10-29 02:30:08 +00:00
rewritted the OOP intro, still needs some cleanup...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
159a80d737
commit
0553ec9845
@ -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)
|
||||
}
|
||||
|
||||
479
js-oop.js
479
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
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user