Background: My latest project cannot use a large library, which saddens me. There are a few things that I would like to have from any library such as the missing functions addClass
, hasClass
, removeClass
, compatible addEventListener
, etc. So I created a little object which I'd like some opinions on some other time, but I'm having a little bit of trouble setting it up how I'd like.
For convenience of use, I want an object to return a new instance of itself on creation.
Given:
$ = function() {
this.name = "levi";
return this;
};
console.log($());
We get DOMWindow instead of $
because of the quirky nature of this
in JavaScript. What's more strange to me is that console.log(new $().name)
properly returns "levi". If this
is bound to window, why did the object properly get the value?. We could just add new console.log(new $())
and it works. However, I don't want to write new everytime. So I tried:
$ = function() {
var obj = function() {
this.name = "levi";
};
return new obj();
};
console.log($());
Which gives me what I want, but it seems a bit unnecessary to wrap the object inside of a function which creates it. Further more, the returned object is obj
and not $
. Comparison tests would fail.
What are some other ways this can be done? Is there a more elegant solution? I have no qualms about rethinking my entire process. I consider myself pretty good at using JavaScript, but creating new JavaScript is something I am very new to.
Does anyone see anything wrong with the following solution?
$a = function() {};
$ = function() {
if (!(this instanceof $)) {
return new $();
}
this.name = "levi";
return this;
};
//helper function
var log = function(message) {
document.write((message ? message : '') + "<br/>");
};
log("$().name == window.name: " + ($().name == window.name)); //false
log("$().name: " + $().name); //levi
log("window.name: " + window.name); //result
log();
log("$a instanceof $: " + ($a instanceof $)); //false
log("typeof $a: " + (typeof $a)); //function
log("typeof $: " + (typeof $)); //function
It appears to be working in all my tests.
The most simple way to do what you want would be (I think):
$ = function(){
if (!(this instanceof $)){
return new $;
}
this.name = 'levi';
return this;
}
The fact that just returning this
doesn't create an instance of $ is because of the way this
is created executing $
as a regular function: in that case the value of this
points to the global object (within a browser: window
, actually calling executing $()
is the same as window.$()
). It's a fact of javascript life so to speak. The fact that console.log(new $().name)
shows the right value is because you call the function as a constructor, which returns an instance of that constructor (i.e. an new instance of $
). But console.log($().name)
will also print 'levi', because it returns the global object with property name
, i.e. window.name
. try $(); console.log(name)
and you'll see name
is a global variable now. So if you don't want to use the new
keyword every time, check if your function is called as a regular function, or as a constructor for an instance (=== instanceof $
) within the constructor function. With the above method an instances constructor, no matter if it's instantiated with or without new
will allways be $
Maybe you should rephrase the title of your question to: 'An Object [constructor] that returns an instance of itself'
Maybe this blog entry can shed extra light.
The way jQuery does it is first tests if this
is window
(called as a function), and if so, returns a new instance of itself. For example:
var $ = function() {
if(this === window) {
return new $();
}
this.name = "levi";
return this;
};
console.log($());
This works because when you call a function normally (func()
), this
will be the same as the caller's this
. (Related but not irrelevant: obj.method()
will have this
be obj
) Since the default scope is window
, this
inside $
will be window
when you call it as $()
.
When you call a function using new
, what happens is JavaScript creates a new object and then calls your function with this
set to that object.
This solution works because it first tests if this
is window
, and therefore, was called like $()
. If it was called like $()
, it will return new $()
. Otherwise, it was called with new $()
, and will work as expected.
> $ = function() {
> this.name = "levi";
>
> return this; };
>
> console.log($());
We get DOMWindow instead of $
When you call $ as a funciton, then its this keyword is set to the global object, as it would be for any function called like that.
because of the quirky nature of this in JavaScript
Javascript's this keyword* works as it is specified to work. It is different to other languages, but that is how it works.
What's more strange to me is that console.log($().name) properly returns "levi".
When you call $ as a function, its this keyword is the global object, so:
this.name = 'levi';
creates a property of the global object called name with a value of 'levi'. Not strange when you know what's happening. :-)
We could just add new console.log(new $()) and it works.
That is how constructors are supposed to be called in javascript. When a function is called called with new, its this keyword is set to a newly constructed object, so this.name
will create a new property of that object. Incidentally, return this
is redundant, constructors return this by default.
> $ = function() {
> var obj = function() {
> this.name = "levi";
> };
>
> return new obj(); };
console.log($()); Which gives me what I want, but it seems a bit unnecessary to wrap the object inside of a function which creates it. Further more, it's of type obj and not type $
Presumably you are using typeof, which can only return one of the values specified by ECMA-262. That short list (which includes object, number, string an so on) does not include $.
What are some other ways this can be done?
You can use the approach you already have discovered, Lasse Reichstein Nielsen's clone (also popularised by Doublas Crockford as "beget") and Richard Cornford's module pattern. Use Google, there are many, many posts about all of the above.
Try something like this:
function $(name){
if( !(this instanceof arguments.callee) ){
return new arguments.callee(name);
}
this.name = name;
return this;
}
Update:
@Levi Morrison
Edit: Does anyone see anything wrong with the following solution:
Since you asked, I'm not exactly in love with document.write.
Try this instead:
var log = function(message){
if(message){
document.body.appendChild(document.createTextNode(message));
}
document.body.appendChild(document.createElement('br'));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With