var someObject = function(arg) {
this.property = function() {
// do something with the argument
return arg;
}();
};
var obj = new someObject(some argument);
// object.property instanceof "someObject" should be true
When property of someObject is used, a new instance of newObject should be created. For example, when I use the native DOM Element's nextSibling property, a new DOM Element object instance is returned. I wonder if it is possible to create a similar structure. Or would such cause infinite recursion?
Strictly speaking, this is possible in ES5 (all latest browsers, yes that includes IE).
ES5 specifies getters and setters via the get and set keyword or the Object.defineProperty function so you can make functions behave like properties (think innerHTML). Here's how you can do it:
function Mother () {
this.name = '';
Object.defineproperty(this,'child',{
get: function(){
return new Mother();
}
});
}
So the object can now create new instances of itself simply by reading the child property:
var a = new Mother();
a.name = 'Alice';
b = a.child;
b.name = 'Susan';
alert(a.name) // Alice
alert(b.name) // Susan
a instanceof Mother; // true
b instanceof Mother; // true
Having said that, your observation about DOM elements is wrong. The DOM is simply a tree structure. You can create a similar structure yourself using old-school javascript:
function MyObject () {}
var a = new MyObject();
var b = new MyObject();
var c = new MyObject();
a.children = [b,c];
b.nextSibling = c;
c.prevSibling = b;
// now it works like the DOM:
b.nextSibling; // returns c
a.children[1]; // returns c
b.nextSibling.prevSibling instanceof MyObject; // true
No, that's not possible. You could set function to the property, but anyway, you will need to invoke function somehow (with property() notation or with call/apply), because function it's an object itself, and only () or call/apply say to interpreter that you want to execute code, but not only get access to function's object data.
Your understanding of the nextSibling property in the DOM is incorrect. It does not create a new DOMElement, it simply references an existing DOM Node.
When you create a sibling of an element to which you have a reference (e.g., via jQuery or document.createElement), the browser knows to update sibling and parent/child references.
So, the behavior you're trying to emulate doesn't even exist.
As others have intimated, simply accessing a property on an object is not sufficient to get the Javascript interpreter to "do" anything (other than deference the name you're looking up). You'll need property to be a function.
nextSibling doesn't return a new element, it returns an existing element which is the next sibling of the target element.
You can store an object reference as a property of another object just like you can store primitive values.
function SomeObject(obj) {
this.obj = obj;
}
var someObject = new SomeObject(new SomeObject());
someObject.obj instanceof SomeObject //true
However if you want to create a new instance of SomeObject dynamically when accessing someObject.obj or you want to return an existing object based on conditions that shoul be re-evaluated every time the property is accessed, you will need to use a function or an accessor.
function SomeObject(obj) {
this.obj = obj;
}
SomeObject.prototype.clone = function () {
//using this.constructor is a DRY way of accessing the current object constructor
//instead of writing new SomeObject(...)
return new this.constructor(this.obj);
};
var someObject = new SomeObject(new SomeObject());
var someObjectClone = someObject.clone();
Finally with accessors (be aware that they aren't cross-browser and cannot be shimmed)
function SequentialObj(num) {
this.num = num;
}
Object.defineProperty(SequentialObj.prototype, 'next', {
get: function () {
return new this.constructor(this.num + 1);
},
configurable: false
});
var seq = new SequentialObj(0);
console.log(seq.next); //SequentialObj {num: 1}
console.log(seq.next.next.next); //SequentialObj {num: 3}
If you want this.property() to return a new someObject you can write the class as follows:
var someObject = function(arg) {
this.arg = arg;
};
someObject.prototype.property = function(arg) {
// do something with the argument
return new someObject(arg||this.arg);
}();
var obj = new someObject(/*some argument*/);
// object.property instanceof "someObject" should be true
If you want it to return some already instantiated version you can write the code as follows:
var someObject = (function() {
var previous;
function(arg) {
this.arg = arg;
this.propertyBefore = previous;//refers to the someObject created before this one
if(previous) previous.property = this; //before.property now references this class
//this.property will be undefined until another instance of someObject is created
previous = this;
};
})()
var obj = new someObject(/*some argument*/);// returns someObject already created earlier (similar to nextSibling)
One small note - its best practice in javascript to declare class names with a capitalized name (SomeObject rather than someObject)
Related
I have just seen a google tech talk presented by John Resig where he said jQuery operates as an array. Following that advice I have been playing with a subclassed array and it works great but I have been looking through the jQuery source and can't see that they have used the same method of
jQuery.prototype = new Array();
And I can't see it even taking the native Array.prototype.methods with call/apply or in the prototype chain in the window.$ object, so I am wondering how does the jQuery object return an array of the selected elements.
I have tried using an ordinary object, but if returning an array it stops the ability to chain commands
If it is possible to take some methods from Array.prototype what is essential to return an array?
This is what I was playing with.
;(function(window, document, undefined){
function MyLib(){};
// prototype constructor functions
function Core(){};
function Methods(){};
// create new instance of the MyLib object and call the construct method
function myLib(selector){
return new MyLib().construct(selector);
}
// allow adding new methods to the prototype from the window.
// eg $.extend('module', 'name', 'function')
myLib.extend = function(module, name, fn){
if(typeof fn === 'function'){
if(!MyLib.prototype[module][name]){
MyLib.prototype[module][name] = fn;
}
} else if(typeof fn === 'object'){
for(var key in fn){
if(!MyLib.prototype[module][key]){
MyLib.prototype[module][key] = fn[key];
}
}
} else {
throw new Error("invalid type, function or objects are required");
}
}
MyLib.prototype = new Array();
MyLib.prototype.core = new Core();
MyLib.prototype.methods = new Methods();
MyLib.prototype.construct = function(selector){
var elems = document.getElementsByTagName(selector);
Array.prototype.push.apply(this, Array.prototype.slice.call(elems, 0, elems.length));
return this;
};
// access to prototype objects with $.core && $.methods
myLib.core = MyLib.prototype.core;
myLib.methods = MyLib.prototype.methods;
// give the window access to the constructor function
window.$ = myLib;
// give the window access to the prototype object for debugging
window.$$ = MyLib;
})(window, document);
// this adds a new method to the methods object within the prototype
$.extend('methods', 'test', function(){alert('method successfully added')});
// the added method can be accessed with
$.methods.test();
// or
$('tagName').test();
thanks for any answers
To work "as an array" (we usually speak of "array-like" object) you don't inherit from Array, you simply have to
have a relevant property named length
have properties "0", "1"... length-1 (you might skip some)
Example :
var a = {
length: 2,
"0": 'first',
"1": 'second'
}
var b = [].slice.call(a); // yes, Array functions work !
console.log(b); // logs ["first", "second"]
Of course you can make all this easier for the lib users by defining a prototype based class and the relevant prototype functions (just as jQuery does) :
var A = function(){
this.length = 2;
this['0'] = 'first';
this['1'] = 'second';
}
A.prototype.slice = [].slice;
A.prototype.splice = [].splice;
var a = new A, b = a.slice();
console.log(a); // logs ["first", "second"] because of the splice function
console.log(Array.isArray(a));
console.log(b); // logs ["first", "second"] because it's really an array
console.log(Array.isArray(b)); // true
If you want your objects to be logged as arrays, you need to have the splice function.
I have a JavaScript object defined like so:
var Object = (function () {
function Object() {
this.id = RandomNumber();
}
// Custom Object.prototype / Object impementations here...
return Object;
})();
The problem is that once this has been constructed, it loses original functionality like Object.defineProperty etc.
The idea is that I want to extend the basic functionality of Object, not re-write or overwrite the existing prototype.
How can this be achieved?
EDIT: Just to be clear, I know I can do this without affecting the original functionality:
Object.prototype.foo = function() { }
but I need to specifically add functionality to Object's constructor, i.e.
function Object() { this.id = 0; }
The new functionality must not overwrite the original Functionality.
Use the .prototype to add a property:
Object.prototype.specialMethod = function () {
// Your method's code
};
And you'd use it like:
var a = {};
a.specialMethod();
Although I would discourage adding a property to the Object's prototype, because it is enumerable and will mess up looping, and will be inherited by all objects, and objects that inherit from Object, which is basically everything.
You could actually use the Object.defineProperty method you mention:
Object.defineProperty(Object.prototype, "specialMethod", {
enumerable: false, // The important one, to avoid looping problems
configurable: false,
writable: false,
value: function () {
// Your method's code
}
});
Do as Ian wrote. If you also want to check it the method already exists use
if (Object.prototype.specialMethod == null) Object.prototype.specialMethod = function() { ... };
In order to extend this object you should create another object that has its prototype assigned a new instance of Object.
var Object = (function () {
function Object() {
this.id = 5;
}
Object.prototype.speak = function(prop){
alert(this[prop]);
}
return Object;
})();
function ExtendsObject(prop){
this.someProperty = prop;
}
ExtendsObject.prototype = new Object();
var xObj = new ExtendsObject("derived");
xObj.speak("id");
xObj.speak("someProperty");
Working Example: http://jsfiddle.net/RbCcA/
If you want to stick with the self executing functions here is the example rewrote:
var Object = (function () {
function Object() {
this.id = 5;
}
Object.prototype.speak = function(prop){
alert(this[prop]);
}
return Object;
})();
var ExtendsObject = (function(){
function ExtendsObject(prop){
this.someProperty = prop;
}
ExtendsObject.prototype = new Object();
return ExtendsObject;
})();
var xObj = new ExtendsObject("derived");
xObj.speak("id");
xObj.speak("someProperty");
Working Example: http://jsfiddle.net/RbCcA/1/
I do question the use of self executing functions in this situation. They are usually used to encapsulate and shield internals, however in the code example they are being exposed by returning the object from the SEF. Returning the object and storing it in a global variable just re-exposes the object, allowing its prototype and properties to be manipulated. Maybe there are private variables you have not mentioned, but as stated I find the SEFs unnecessary.
I find this is most recommended way to do inheritance in javascript.
function extend(Child, Parent) {
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
}
what if I already have methods in child's prototype, aren't they will overwrite, shouldn't we preserve them.
function extend(Child, Parent) {
var c = child.prototype;
var oldProto = new C();
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
for(var i in oldProto ){
Child.prototype[i] = oldProto[i]
}
}
I'm not sure if this is any good to you, but it's well important to remember: prototypes are not the same things as classes. What you're doing is trying to make JS behave like a traditional OO language, which is trying to teach a dolphin to dance ballet, or forcing a tiger to become vegan: Admirable, but destined to end in tears.
I can't really see why you'd want to use the extend function to do whatever it is you're trying to do. Why not simply use this:
function Parent()
{};
function Child()
{};
//augment parent proto
Parent.prototype.parentMethod1 = function()
{};
//set Child's proto to Parent
Child.prototype = new Parent();
Child.prototype.constructor = Child;
//Then augment the Child's prototype
Child.prototype.childMethod1 = function()
{};
var foo = new Child();
foo.parentMethod1();//works
foo.childMethod1();//works, too
IMO, this solves the problem entirely. Sure, it's a tad more verbose, but OOP always is.
The pattern you're trying to achieve is called multiple inheritance. And it's highly not recommended for the use because of the issue you're experiencing, called diamond problem. Just use mixin pattern instead.
The code below is the one of the best I have seen for doing inheritance in JavaScript.
Object.create(proto [, propertiesObject ]) is discussed on MDN here.
Below, Jon defines a base empty object called ExtendBase then adds a function property called extend which is not enumerable which takes as its argument a single new object.
That object should contain enumerable properties such as methods and data that will be added to the base object.
He gets all the enumerable properties from the passed object, then creates an array of the necessary descriptors to pass into Object.create using those properties' names. He then uses the parent object as the prototype and resultant descriptors as new properties to be added to the child object directly in the Object.create() call.
As you can see, you can use an object argument with properties, including methods, to extend a parent without losing that passed object's properties with the result being a child object with the parent as the prototype and the enumerable objects of the passed object added directly to the child.
However, this maintains a clean prototype chain while intending to extend parent objects using other objects which are created sanely to extend the parent into a new child in a way that makes sense:
Live sample here (Press F12 in Chrome for console output, or use FireBug in FireFox, etc.)
JavaScript:
// Original Author: FireFly - Jonas Höglund - ##javascript channel
// on irc.freenode.net - see THANKS File. Updated to private data
// members and passable initial parameters by Scott Sanbar
///////////////
// Library code
///////////////
var ExtendBase = {};
Object.defineProperty(ExtendBase, 'extend', {
enumerable:false, value:function (obj) {
'use strict';
var descs = {};
Object.getOwnPropertyNames(obj).forEach(function (key) {
descs[key] = Object.getOwnPropertyDescriptor(obj, key)
});
return Object.create(this, descs);
}
});
///////////////
// Sample Usage
///////////////
function PersonObj(nam) {
return {
name:new function () {
var name = nam;
this.set = function (value) {
name = value;
};
this.get = function () {
return name;
}
},
// A person can tell you its name.
talk:function () {
return "Hello, I'm " + this.name.get();
}
}
}
;
function WorkingPersonObj(occ) {
return {
occupation:new function () {
var occupation = occ;
this.set = function (value) {
occupation = value;
};
this.get = function () {
return occupation;
}
},
// A working person also tells you their occupation when they talk.
talk:function () {
return Person.talk.call(this) + " and I am a " + this.occupation.get();
}
}
}
;
var hush = {
hush:function () {
return "I am supposed to be quiet";
}
};
var Person = ExtendBase.extend(new PersonObj('Harry'));
var WorkingPerson = Person.extend(new WorkingPersonObj('wizard'));
var wp1 = WorkingPerson.extend(hush);
console.log(wp1.talk()); // "Hello, I'm Harry and I am a wizard"
console.log(wp1.hush()); // "I am supposed to be quiet"
wp1.name.set("Elijah");
wp1.occupation.set("prophet");
console.log(wp1.talk()); // "Hello, I'm Elijah and I am a prophet"
console.log(wp1.name.get());
console.log(wp1.occupation.get());
I'm using Resig's Simple JavaScript Inheritance to create my classes. The only thing I don't like about it so far is that when I log an object created with this library to the console, it's name is simply "Class". My question is whether there is a way to modify his code so that I get the actual class name in the console instead. Here's an example from Chrome's console:
I would really like that name "Class" to be the actual name of the class I've created, in the way it would if you did the following:
I believe I know the reason why this happen's with Resig's library: the actual constructor function is simply named "Class". Here's the code for his library:
(function(){
var initializing = false,
// Determine if functions can be serialized
fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
// Create a new Class that inherits from this class
Object.subClass = function(prop) {
var _super = this.prototype;
// Instantiate a base class (but only create the instance,
// don't run the init constructor)
initializing = true;
var proto = new this();
initializing = false;
// Copy the properties over onto the new prototype
for (var name in prop) {
// Check if we're overwriting an existing function
proto[name] = typeof prop[name] == "function" &&
typeof _super[name] == "function" && fnTest.test(prop[name]) ?
(function(name, fn){
return function() {
var tmp = this._super;
// Add a new ._super() method that is the same method
// but on the super-class
this._super = _super[name];
// The method only need to be bound temporarily, so we
// remove it when we're done executing
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
})(name, prop[name]) :
prop[name];
}
// The dummy class constructor
function Class() {
// All construction is actually done in the init method
if ( !initializing && this.init )
this.init.apply(this, arguments);
}
// Populate our constructed prototype object
Class.prototype = proto;
// Enforce the constructor to be what we expect
Class.constructor = Class;
// And make this class extendable
Class.subClass = arguments.callee;
return Class;
};
})();
You'll find the Class() function about 2/3 of the way down. Does anyone know how to modify this code so that you get the actual name of the class in the console?
Toss in a change to Person.prototype.constructor when you're creating Person:
var Person = (function() {
var myConstructor = Class.extend({
init: function(isDancing){
this.dancing = isDancing;
},
dance: function(){
return this.dancing;
}
});
myConstructor.prototype.constructor = function Person(){};
return myConstructor;
}());
I don't think you could do it from within the Simple JavaScript Inheritance. You'd need the Person.prototype.constructor to be a named function, and I don't think you can name a function without eval... and you have too much rep for me to explain why you shouldn't do that ;)
No promises this doesn't screw something up elsewhere though :P
Scenario 1 - everything works:
var AwesomeObject = function()
{
var self = this;
self.whatstuff = 'really awesome';
}
AwesomeObject.prototype.doStuff = function()
{
var self = this;
console.log('i did '+self.whatstuff+' stuff');
return self;
}
var awesome = new AwesomeObject(); //returns a new AwesomeObject
awesome.doStuff(); // prints 'i did really awesome stuff' on the console
Now i want it even awesomer:
var AwesomeObject = function()
{
var f = function() { console.log('i am awesome'); }
var self = f;
self.whatstuff = 'really awesome';
return self;
}
AwesomeObject.prototype.doStuff = function()
{
var self = this;
console.log('i did '+self.whatstuff+' stuff');
return self;
}
var awesome = new AwesomeObject(); //returns the interal f object
awesome(); // prints 'i am awesome'
awesome.doStuff(); // throws an error
new AwesomeObject should return an executable function itself, so that i can say 'awesome();'
but i want it to inherit the AwesomeObject.prototype, too.
adding self.prototype = AwesomeObject.prototype; does not help.
var AwesomeObject = function()
{
var f = function() { console.log('i am awesome'); }
var self = f;
self.whatstuff = 'really awesome';
self.prototype = AwesomeObject.prototype;
return self;
}
ok i can copy the AwesomeObject.prototype functions - one after the other - into the scope of f
var AwesomeObject = function()
{
var f = function() { console.log('i am awesome'); }
var self = f;
self.whatstuff = 'really awesome';
self.doStuff = function() { AwesomeObject.prototype.doStuff.apply(self,arguments); }
return self;
}
but i think there must be a better way, a better pattern, what is it?
this issue drives me crazy, help would be really appreciated.
in general: how to create a function object that
can be created with new
returns a function object that can be executed
inherits all properties and methods of a given prototype
?
is there a way?
thx
Franz
A very simple pattern is a factory.
var AwesomeObject = (function() {
var AwesomeObject = function() {
this.whatstuff = 'really awesome';
};
AwesomeObject.prototype.doStuff = function() {
console.log('i did ' + this.whatstuff + ' stuff');
return this;
};
return function() {
var o = new AwesomeObject();
var f = function() { console.log("I am awesome"); };
for (var k in o) {
f[k] = o[k];
}
return f;
};
})();
var foo = AwesomeObject();
foo();
foo.doStuff();
Live Example.
The idea is that you seperate your function and your object into two things. Your object exists in the local scope of your function and the function can use the object.
The object itself inherits completely through the prototype.
The key is do forward all properties/methods of the object onto the function.
This is the cleanest solution.
When a property is resolved the prototype chain is traversed as you probably know.
But if you have an object awesome and try to evaluate awesome.doStuff, then awesome.prototype will never be queried for the property. You can verify this in your example, "doStuff" in awesome => false but "doStuff" in awesome.prototype => true.
So what you're doing is not changing the implicit properties of awesome, you are changing its prototype, meaning any objects created by doing new awesome will have that property. Verification: "doStuff" in new awesome() => true. And this makes sense, since there is no way to distinguish between a constructor or a regular function when using f/awesome.
The procedure when resolving a property p on an object o is roughly as follows:
Check whether p is defined on o
Check whether p is defined on o.__proto__ (usage of __proto__ is non-standard but widely implemented, except for jscript last time i checked and it has now been deprecated in SpiderMonkey)
Check whether p is defined on o.constructor.prototype
Check whether p is defined on o.constructor.prototype.prototype
etc
So one solution would be to simply set o.__proto__ = AwesomeClass.prototype. Think of __proto__ as a hidden intermediary object between an object and its prototype. Each instance receives its own unique __proto__ object. But this is deprecated and non-standard like I said.
We could also set the values in Function.prototype but that would override other Function properties and affect all Function instances. We don't want that.
So what's left? Not much it turns out. There is no way to set the complete prototype of an object while retaining it's inherited prototype. You will need to iterate through your prototype and copy all properties. Fortunately this will allow instanceof to behave as expected when working with chains of constructors, as well as allowing inheritance/overriding of properties properly.
The problem is really that there is no built-in way to copy the properties of an object into another one, and that there is no standard way to change an object's prototype chain ad-hoc (__proto__).
So use __proto__, or iterate through the prototype.
I don't think there is a good way to do this. I would redesign your program to avoid it.
However, here is a bad, platform-dependent solution (works on V8 using non-standard __proto__ property):
var PrototypeToBeInherited = {'inheritedProperty': 'inheritedPropertyValue'};
f = function() {
return "result";
};
f.__proto__ = PrototypeToBeInherited;
f()
=> "result";
f.inheritedProperty
=> "inheritedPropertyValue"
For your requirement that it must be created with "new", just wrap it in function:
F = function() {
return f;
}
var instance = new F();