KnightNiwrem
Senior Member
- Joined
- Jun 1, 2014
- Messages
- 1,057
- Reaction score
- 0
Another epic pair of posts. You and David are like human man pages
I've never seen that sort of shim/polyfill used before, probably due to my inexperience. An interesting solution I'll definitely be messing around with for a bit.
Extending the native Function object has only 1 purpose here - that is to make Class Inheritance more intuitive when writing code. It is not required at all. If you take a look at the extending method's code, it is simply a one-liner.
The issue with Inheritance in Javascript, for some, is the flexibility of the Object.create method, coupled with the fact that all functions are actually objects, instances are objects, constructor.prototype are objects too. It just makes things all too confusing.
The above implementation attempts to make things intuitive again, by clearly defining constructors, and using the intuitive idea that only classes(which are also constructors, in this case) should extend classes.
That being said, I decided to use Object.create precisely to get around 'pretending' that JS has classes at all. I am trying to shift the mental model in my brain toward thinking about JS objects as a set of horizontally-related bundles of functionality and data, linked with [[prototype]] instead of vertical, parent-child inheritance like in Java and many other OO languages (I can read Java fine, but I can't programme with it very well).
So instead of instanceOf, which confuses me, I just used isPrototypeOf, since that will immediately tell me whether or not any particular object at run time has the functionality that it should in the prototype chain, ignoring altogether where or not it exists directly on the object itself on its .prototype property.
If I do need to extend an object, then I can always just add any functions I need directly onto the object, just declaring something like
Code:TestObject.prototype.getter = function () { return this.key; }
And then, if I need to use this new function in another object, I just delegate to TestObject by doing something like
Code:var newObject = Object.create(TestObject);
So that newObject's [[Prototype]] will contain functionality from TestObject and the other objects TestObject is delegating to through its own [[Prototype]]. Now I could well be wrong, so again, please rip my little hypothesis to shreds.
First, using isPrototypeOf to replace instanceof is perfectly fine - at least, in my humble opinion. In most cases, isPrototypeOf is more general than instanceof.
I'm not entirely sure how you are doing a horizontal hierachy thing for objects and stuff here... If you are extending, it kind of looks like it would fit in a vertical hierachy just fine.
That aside, if you want to think of Objects as a bundle of functionality and data, you could look at the idea of Object Composition. Object Composition usually complements Inheritance, but it works independently from Inheritance.
Object composition attempts to describe a "has a" relationship, rather than a "is a" relationship that Inheritance does. We make this work by extracting and factorizing shared functionalities.
Constructors and classes are entirely unnecessary for Object composition because strict Object type checking doesn't seem very fruitful - the main advantage is programming speed.
Anyway, let us demonstrate some Object composition without the use of classes/constructors. For this example, I'll use Object literals and Object.create only.
Suppose I want to create a cat and a bird. Both the cat and bird can move, but only the bird can fly. Normally, we'd say that cat and bird are subclass of Animal, and then we'd override the move method for cat and bird, and extend a fly method for bird.
For Object composition, instead, we will have no "animal" class. Cat and bird are unrelated. However, cat and bird will "delegate" the move functionality to a moveComponent, while a bird will "delegate" the fly functionality to a flyComponent. A cat will not have a flyComponent.
Code:
var moveComponent = { isMoving : false,
move : function() {
this.isMoving = true;
},
stop : function() {
this.isMoving = false;
}
};
var cat =
{ name : "White cat",
moveComponent : Object.create(moveComponent)
};
var flyComponent = { isFlying : false,
fly : function() {
this.isFlying = true;
},
land : function() {
this.isFlying = false;
}
};
var bird =
{ name : "Blue bird",
moveComponent : Object.create(moveComponent),
flyComponent : Object.create(flyComponent)
};
Now, we tell ourselves that if we would like to check if an object is moving, we will always use the object's moveComponent - because it needs a moveComponent to be moving.
Code:
cat.moveComponent.isMoving; // false
cat.moveComponent.move();
cat.moveComponent.isMoving; // true
bird.moveComponent.isMoving; // false
cat.moveComponent.stop();
cat.moveComponent.isMoving; // false
cat.flyComponent; // undefined
cat.flyComponent.fly(); // throws error
bird.flyComponent.isFlying; // false
Note that every object has its own moveComponent object that describes the states that are related to moving, and functionality that are related to moving. Hence, they maintain some sort of independence of state between 2 different objects, while sharing functionalities.
The usefulness of this, over Inheritance, is that moveComponent and flyComponent can be applied to anything that moves or flies. Previously, if we extended bird and cat from Animal, then a car that moves too, would have to copy and paste code from Animal, or it might even extend Animal for its functionality - which hardly makes any sense.
Suppose we want to create a plane. A plane has no name, unlike animals, so extending animal would have caused problems, if we used Inheritance. Instead, a plane has a pilot.
Code:
var plane =
{ pilot: "Natnai",
moveComponent: Object.create(moveComponent),
flyComponent: Object.create(flyComponent)
};
And we are done. Now, our plane has no name parameter. It has a pilot parameter. And it can move and fly without having any relationship with bird or cat.
In summary, the main advantage of Object composition is the ability to borrow functionality and reuse code without keeping track of messy hierachy and relationship between objects.
