How to write chainable functions but do not pollute $.fn ? Write functions only for using inside my plugin. Is it possible?
$('.myclass').makeSomething().andOneMoreFunction().andLast();
It is correct approach?
UPD.
The best solution in my case is extension method:
String.prototype.getMyLength = function(){return this.length;}
And now I can apply this function to any string like this:
var mystring = "test";
mystring.getMyLength();
Or
"teststring".getMyLength()
And make it chainable:
String.prototype.getMe = function(){return this;}
"string".getMe().getMe().getMe().getMe().getMe();
Thanks for answers!
You can chain all you want. If you define a $.fn yourself it is important that you return this at the end of you function.
If you want to write some javascript yourself you can also chain! It just depends on what you return. So if you return some other object, you can chain on from that object. The return value is used for this.
Example
var obj = {
test : function(){
alert("Y");
return this;
},
test2 : function(){
alert("2");
return this;
}
}
obj.test().test2(); // And so on since it returns this
jQuery Plugin API
$.fn.test = function(){
var methods = {
method0 : function(){
alert("method0");
return this;
}
};
return methods;
}
var api = $("obj").test(); // Returns methods
api.method0(); // Calling a function from the returned methods.
// OR
$("obj").test().method0();
Above function is not jQuery chainable anymore. So you can't use the $("obj").test().addClass("test") because you return your own API!
You can avoid pollution by using the first parameter of your plugin's function to specify the method of choice; for instance
(function () {
var o = { // object holding your methods
'bar': function () {console.log('bar', this); return this;},
'foobar': function () {console.log('foobar', this); return this;}
};
$.fn.foo = function (method /*, args*/) {
return o[method].apply(
this,
Array.prototype.slice.call(arguments, 1) // pass your args
);
};
}());
and then
$('something').foo('bar').foo('foobar');
/*
bar, thisobj
foobar, thisobj
*/
This way you keep access to the jQuery object as normal, too.
When you call a.foo(), the function foo is invoked with this set to a. You can use this to your advantage.
Also recall that the expression a.foo() evaluates to whatever you returnd from within the function.
So, just return this.
Then a.foo() evaluates back to a, and (a.foo()).bar() becomes equivalent to calling a.foo() then calling a.bar()... i.e. chained operations on a!
$.fn is not particularly magical — it simply uses the above logic in the same way that you are about to.
Related
I was wondering how does JQuery use "$" both as a function to return a new instance and an instance itself.
I guess that it's not exactly the case but I mean, we can use $(element).method and for exemple $.ajax without brackets (.ajax will still be a method).
EDIT :
I think that I misspoke. I know how objects work in JavaScript and my question was not about that.
JQuery allows us to use $ without the key word new. It has a function that returns a new instance automatically. So my question was about how it can use $ both as a function to instanciate a new object and an object itself.
Let's say we have
(function() {
var jQ = function (arg){
this.own = arg;
};
jQ.prototype = {
foo : function (){
alert("Foo");
},
bar : function (){
alert("Bar");
}
};
window.jQ = window.$ = jQ;
return jQ;
}());
In this exemple, i have to go througth the key word new if I want to use my object.
So how does JQuery do to avoid us this step ?
Function is an object in javascript: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function You can check this code:
var f = function () { alert(1); };
f.func1 = function () { alert(2); };
f.func2 = function () { alert(3); };
and you can call f(), f.func1() and so on...
It's not jQuery. In JavaScript functions are objects.
In the case of $(element).method you are passing a parameter element into the jQuery function, where with the $.ajaxcall you are calling the ajax function inside of the $ jQuery object. In both cases we are talking about the same piece of code, but we are using it two different ways.
Have a look at the raw source code for jQuery and that should help to visualize this a little: https://code.jquery.com/jquery-2.1.1.js
Note: the jQuery function that is used repeatedly is aliased at the bottom of the page.
Remember that in JavaScript, functions are objects. So, using the specific functions you called out in your question, you could create them like this:
var $ = function(selector) {
...
};
$.ajax = function(url) {
...
};
EDIT: To respond to your edited/clarified question, you don't have to use prototyping to make constructor functions in javascript. Remember, all a constructor is doing is returning an object - here's the equivalent of your prototyping code, but without having to use the new operator to instantiate the object:
(function() {
var jQ = function (arg){
return {
own: arg,
foo: function (){
alert("Foo");
},
bar: function (){
alert("Bar");
}
}
};
window.jQ = window.$ = jQ;
return jQ;
}());
I believe this style is actually preferred by Douglas Crockford because forgetting to use the new keyword won't throw an error but you'll get some very unexpected behavior.
JQuery allows us to use $ without the key word new. It has a function that returns a new instance automatically.
Nothing magical here. The jQuery function simply returns an instance of another constructor (source):
// Define a local copy of jQuery
jQuery = function( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
// Need init if jQuery is called (just allow error to be thrown if not included)
return new jQuery.fn.init( selector, context );
},
The only magic going on in the code (not shown in the example) is that jQuery.fn.init.prototype = jQuery.prototype. But jQuery.fn.init is a different function than jQuery.
Applied to your example:
var jQ = function (arg){
return new jQ.prototype.init(arg);
};
jQ.prototype = {
init: function(arg) {
this.own = arg;
},
// ...
};
jQ.prototype.init.prototype = jQ.prototype;
I can't seem to find an example of what I'm trying to achieve, although I'm sure it has been done many times before...
I want to create an object which will have a set of properties and member functions but that I can also call directly. In the same way the jQuery object allows you to call $("selector") or $.method(...)
Here's a slimmed down version of what I'm trying to achieve :
var foobar = function(param) {
return "FOO : " + this.lookup(param);
}
foobar.vals = {
1: "one",
2: "two"
};
foobar.lookup = function (param) {
return "BAR : " + this.vals[param];
}
foobar.lookup("1")
// returns "BAR : one"
foobar("1")
// returns error since 'this' points to global scope
// I'd want it to return "FOO : BAR : one"
I've also tried various approaches with function prototype but can't seem to find a method which gives me everything I want...
var foobar = function(param) {
return "FOO : " + foobar.lookup(param);
}
will return you what you want
To understand this, maybe you should take a look at the basics of JavaScript. What are functions how to instanciate an object and what are objects...
To get something like JQuery this is not very difficult, the JQuery main object is simply a function which also has "static" functions.
to declare a variable as function you do
var myFunc = function(){};
to use the variable and extend it with static stuff you simply assign it via
myFunc.staticFunc = function(){};
this doesn't mean that myFunc.staticFunc can be accessed with this in any instance of myFucn because you didn't add the function to the prototype...
To define a class like object which can be instanciated you also define it as function and then extend the prototype. Prototype is your class definition which is used to construct the object's instance:
myFunc = function(){
// ctor
this.lala = "blub";
} ;
myFunc.prototype.objectFunc = function() {
return this.lala;
}
now the object myFunc has a function objectFunc. I have to initialize it with new...
alert(new myFunc().objectFunc());
instances can access itself with this...
To do something like jquery you'll have to do some tricks. Your global variable must be a function which returns an instance of your "real" object, which can implement whatever...
Then you can call your variable as if it is a function, e.g. myFunc()...
Hope the following example makes it more clear how this works: (can be found on jsfiddle)
(function ($) {
var myRealObj = function (outId, printText) {
this.text = printText;
$("#" + outId).append("<li>" + this.text + "</li>");
};
myRealObj.prototype.objectFunc = function () {
return this.lala
};
var myFunc = function (out, txt) {
return new myRealObj(out, txt);
};
myFunc.someFunc = function () {
myFunc("out", "myFunc.someFunc got called");
};
myFunc.static = {};
myFunc.static.someFunc = function () {
myFunc("out", "myFunc.static.someFunc got called");
};
window.$$ = myFunc;
})($);
$$("out", "test obj function");
$$.someFunc();
$$.static.someFunc();
You could add:
foobar = foobar.bind(foobar);
to make the variable refer to a bound version of the function. Any call to the (updated) "foobar" would have this bound to the original function object. You'd also have to mirror the properties if you wanted to get at them directly, which is sort-of a mess.
In the jQuery implementation, there's a separate internal function that handles the basic routing of the master $() function.
Note also that the "global" $.whatever() functions don't really have much to do with the set of methods supported by jQuery instances (objects returned from the $() function). The $ object just serves as a namespace so that those global utilities don't pollute the global (window) namespace.
you declare var foobar = function(param) {... in the global scope so this will always be a window
OK! First of all this question comes from a man who digs too deep (and posibly get lost) in the jQuery universe.
In my reserch I discovered the jquery's main pattern is something like this (If needed correction is wellcomed):
(function (window, undefined) {
jQuery = function (arg) {
// The jQuery object is actually just the init constructor 'enhanced'
return new jQuery.fn.init(arg);
},
jQuery.fn = jQuery.prototype = {
constructor: jQuery,
init: function (selector, context, rootjQuery) {
// get the selected DOM el.
// and returns an array
},
method: function () {
doSomeThing();
return this;
},
method2: function () {
doSomeThing();
return this;,
method3: function () {
doSomeThing();
return this;
};
jQuery.fn.init.prototype = jQuery.fn;
jQuery.extend = jQuery.fn.extend = function () {
//defines the extend method
};
// extends the jQuery function and adds some static methods
jQuery.extend({
method: function () {}
})
})
When $ initiates the jQuery.prototype.init initiates and returns an array of elements. But i could not understand how it adds the jQuery method like .css or .hide ,etc. to this array.
I get the static methods. But could not get how it returns and array of elements with all those methods.
I don't like that pattern either. They have an init function, which is the constructor of all jQuery instances - the jQuery function itself is just a wrapper around that object creation with new:
function jQuery(…) { return new init(…); }
Then, they add the methods of those instances to the init.prototype object. This object is exposed as an interface at jQuery.fn. Also, they set the prototype property of the jQuery function to that object - for those who don't use the fn property. Now you have
jQuery.prototype = jQuery.fn = […]init.prototype
But they also do two [weird] things:
overwriting the constructor property of the prototype object, setting it to the jQuery function
exposing the init function on jQuery.fn - its own prototype. This might allow Extending $.fn.init function, but is very confusing
I think they need/want to do all this to be fool-proof, but their code is a mess - starting with that object literal and assigning the init prototype things afterwards.
It’s easier to digest if you think of the API as an external collection of methods, and the jQuery function as the wrapper.
It’s basically constructed like this:
function a() { return new b();}
a.prototype.method = function() { return this; }
function b() {}
b.prototype = a.prototype;
Except that a is jQuery and b is jQuery.prototype.init.
I’m sure Resig had his reasons for placing the api constructor in the init prototype, but I can’t see them. A couple of more strangeness besides the ones Bergi mentioned:
1) The patterns requires a reference copy from jQuery.fn.init.prototype to jQuery.prototype, wich allows a weird endless loop:
var $body = new $.fn.init.prototype.init.prototype.init.prototype.init('body');
2) Every jQuery collection is actually an instance of jQuery.fn.init, but since they reference the same prototype object, it tricks us to "think" that the collection is an instance of jQuery. You can do the same sorcery like this:
function a(){}
function b(){}
a.prototype = b.prototype;
console.log( new b instanceof a); // true
console.log( new a instanceof b); // true
Sidenote: I have personally used the following constructor pattern with similar results without the weirdness:
var a = function(arg) {
if (!(this instanceof a)) {
return new a(arg);
}
};
a.prototype.method = function(){ return this; };
Let's say I've created this plugin:
$.fn.my_namespace = function() {}
with level 1 sub functions:
$.fn.my_namespace.category_func = function() {}
and level 2 sub functions (actual application):
$.fn.my_namespace.category_func.app_func() {
alert(this);
alert(this.selector);
}
Execution:
$('div').my_namespace.category_func.app_func();
how can I now in my app_func retrieve the actual selector? In this case, 'this' seems to be the parent function (category_func) and not the jQuery object (selector).
How come? And how do I access the selector from app_func() ?
jQuerys .fn namespace is intended to hold functions which return a jQuery object / array of objects.
You can't just throw a new object in there and expect everything to work just like that.
I swear I've answered this before, but I can't seem to find it. this always refers to the object you are calling the method on. In this case you are using category_func as that object, and calling app_func().
The pattern that jQuery UI uses is one possible way to work around this issue. They allow you to call methods on a UI object by doing something like $elem.draggable('destroy');
Imagine for a moment:
$.fn.my_namespace = function(submethod, method) {
var args = [].slice.call(arguments, 1);
var func = $.fn.my_namespace[submethod];
if (func && method) {
if ($.isFunction(func[method])) {
args.shift(); // remove the method
func = func[method];
}
}
if ($.isFunction(func)) {
// using .apply() allows us to pass `this` along to our "method functions"
return func.apply(this, args);
} else {
// didn't find the method, return... or do something else...
console.log('my_namespace', this, arguments);
return this; // jQuery chaining default
}
}
$.fn.my_namespace.category_func = function() {
console.log('category_func', this, arguments);
return this;
}
$.fn.my_namespace.category_func.method_func = function() {
console.log('method_func', this, arguments);
return this;
}
$("body").my_namespace('category_func', 'method_func', 10);
//method_func jQuery(body) [10]
$("body").my_namespace('category_func', 10);
//category_func jQuery(body) [10]
$("body").my_namespace(10, 'slow');
//my_namespace jQuery(body) [10, "slow"]
var Dog = function() {
var _instance = 'hello world';
return function() {
console.log(this._instance);
}
} (); //note that it is self invoking function
var l = new Dog(); //#> undefined
In the above case I was expecting an output of:
'hello world'
Why is this._instance not accessing the the variable which should be accessible by virtue of closure? I tested this in FF and am getting undefined.
You don't assign _instance to the object, it's just a closure variable, and should be accessed without using this:
var Dog = function() {
var _instance = 'hello world';
return function() {
console.log(_instance);
}
} (); //note that it is self invoking function
var l = new Dog();
I'd probably write it like so instead:
var Dog = (function() {
var defaults = {
name: 'Rags'
};
var Dog = function (options) {
// Take options as a constructor argument, this
// allows you to merge it with defaults to override
// options on specific instances
this.setOptions(options);
};
Dog.prototype = {
// Common methods for Dogs here
setOptions: function (options) {
// Declare all variables in the beginning of the method because
// JavaScript hoists variable declarations
var key = null;
// First assign all the defaults to this object
for ( key in defaults) {
this[key] = defaults[key];
}
// Now override with the values in options:
if (options && options.hasOwnProperty) {
for ( key in options ) {
this[key] = options[key];
}
}
}
};
return Dog; // Return the constructor method
} ()); // wrap the self-invoked function in paranthesis to visualize that
// it creates a closure
var buster = new Dog({name: 'Buster'}),
unnamed = new Dog();
alert(buster.name); // Alerts 'Buster'
alert(unnamed.name); // Alerts 'Rags'
Note that I have not tried to compile the above code, so it might contain a few mistakes. Nothing JsLint can't handle though!
You might want to consider adding filtering to the setOptions method so that it doesn't assign properties you don't want, or filter out methods etc declared in the options-parameter.
Additionally, if you use JQuery, or similar library, there are (often) utility functions for merging objects, making it trivial to write the setOptions-method:
function setOptions (options) {
// I assume JQuery here
// true as the first argument gives us a recursive merge
var mergedOptions = $.extend(true, defaults, options);
for (var key in mergedOptions ) {
if(this.checkAllowedProperty(key, typeof(mergedOptions[key])) {
this[key] = mergedOptions[key];
}
}
}
/**
* This method checks if propertyName is an allowed property on this object.
* If dataType is supplied it also checks if propertyName is allowed for
* dataType
* #return true if propertyName, with type dataType, is allowed on this object,
* else false
*/
function checkAllowedProperty (propertyName, dataType);
Your problem is this.
Change this._instance to _instance. You may also want to wrap your self-invoking function in parentheses like (function() { ... })(); for maximum browser compatibility.
As the others have said, you need to remove "this." from your function.
The reason for the problem is down to the binding of the "this" keyword in the two function contexts. Inside the closure, "this" refers to the function that is being returned, and not to the outer function. You could resolve this by doing the following:
var Dog = function() {
var _instance = 'hello world';
var that = this; //Assign "this" to "that"
return function() {
console.log(that._instance); //Use reference to "that"
}
} ();
var l = new Dog();
You could also probably do something closer with the function.apply() method, but I'll leave that to you.
I hope that helps.
Perhaps you are satisfied by removing "this.", but you may be interested to learn that "this" doesn't refer to what you wanted it to anyway. What it refers to really depends on how the function is called. It does not necessarily refer to an instance of an object constructed by the function you returned, or its container function, or to any other object. By default, if you merely call the function as a normal function, "this" will refer to the global window context.
What you must do to have "this" be bound to any specific object is to call the function as a method of that object, or of its prototype. e.g. foo.myMethod(). Another way is that you can use the apply or call method, passing in the object you want it to apply to. e.g. anyFunction.apply(foo).