Using e.g. Chrome Developer Tools, we can easily inspect JavaScript / jQuery objects. I am trying to understand the following:
$.ajax // defined as 'function ...'
$.fn.ajax // undefined
$.removeClass // undefined
$.fn.removeClass // defined
Since $.removeClass is not defined, how come we can invoke e.g. $('body').removeClass('some-class')? And, this leads to an error $('body').fn.removeClass('some-class')?
You are asking about two different types of objects.
$ is the same as jQuery and is a function that has properties on it. $.ajax is one of those properties.
An actual jQuery object as in what $('body') creates is actually an object that is an instance of jQuery.fn.init not of jQuery.
So, that's the first reason that you see different methods on $ and $('body') because they are different types of objects and thus can have different types of methods.
To understand further, the methods on $ (which is a synonym for jQuery) are the methods that are added directly to the jQuery function itself. In the jQuery code, this is mostly done with jQuery.extend() right on the jQuery object. $.ajax is one of those.
The methods on the jQuery object created by the jQuery function are the methods that are assigned to jQuery.fn.init.prototype which due to some trickery by jQuery are the methods assigned to jQuery.fn. As it turns out jQuery.fn.init.prototype is set to be the same object as jQuery.fn so when anything is assigned to jQuery.fn, it automatically goes to jQuery.fn.init.prototype and anything on that prototype automatically becomes a method of a jQuery.fn.init object which is what is created by the jQuery function such as jQuery('body') or $('body').
You can see this in action in the jQuery code. If you look at the jQuery function, it looks like this (it creates an object of jQuery.fn.init and thus will have the methods from jQuery.fn.init.prototype:
// Define a local copy of jQuery
var jQuery = function( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
return new jQuery.fn.init( selector, context, rootjQuery );
},
And, then later the .fn is like this:
// Give the init function the jQuery prototype for later instantiation
jQuery.fn.init.prototype = jQuery.fn;
Which is how any method assigned to jQuery.fn is also on the jQuery.fn.init.prototype and becomes a method on a jQuery object.
When you do something like var el = $("#content");, you're dealing with a few different types of objects:
Strings. "#content". 'Nuff said.
Functions. $ (here, a synonym for jQuery) is a function. You're calling it with the string "#content", as we established above.
jQuery objects. The jQuery function creates jQuery objects. el is one of those objects.
The jQuery function and its direct attached properties are mostly things that don't apply to any particular element; they're things you might want to do on their own. Take, for example, jQuery.ajax; that doesn't apply to an element, so it was put directly on the jQuery function.
Other functionality only makes sense when you've got the context of what elements to apply the operation to. Say, for example, removing a class. If I say jQuery.removeClass("selected");, what do I want to remove the class from? You never specified, so you can't do that. Instead, assuming we assigned el as above, el.removeClass("selected"); does indeed make some sense; it removes a class from whatever elements el represents (here, the element with an ID of content).
Whenever we have one of these function names after the dot, it's a property of something before the dot (either directly or indirectly). In the case of functions like ajax where no elements are involved, it's put directly on $/jQuery. In the case of methods like removeClass, it's put on all jQuery objects.
Of course, if you ever want to add a new method that can be used on a set of elements like removeClass, it would be rather tedious to add that property to every single one, let alone the issue of getting a reference to every jQuery object, past, present, and future! For that reason, jQuery.fn is an object that acts as the prototype of all jQuery objects. That means that whenever you create a new jQuery object, it will act minimally like the prototype it was based on, jQuery.fn. If you add a property to jQuery.fn, it will appear on all jQuery objects. In fact, the concept of a prototype is deeply embedded into JavaScript, and modifying jQuery.fn will affect all jQuery objects, whether they're newly created, created in the past, or created in the future.
I'm very new to jQuery, but my understanding is that $.fn is a property that contains all methods that can be invoked on jQuery objects.
That's why when you write a plugin, you extend $.fn by adding your own function definitions.
When you declare a function in JavaScript, it is an object:
function foo() {...}
foo.bar = 'baz'; //set the `bar` property on `foo`
...as well as being a constructor:
var f = new foo();
f instanceof foo; //`true`
f.bar; //undefined
When you construct an object from a function, the instance will inherit properties from the prototype of its constructor function:
function Bar() {...}
Bar.prototype = {
baz: function () {
console.log('baz');
}
}
var b = new Bar();
b.baz(); //logs 'baz'
Bar.baz(); //error
Additionally, objects in JavaScript are passed by reference*.
var fizz,
buzz;
fizz = {};
buzz = fizz;
buzz.foo = 'bar';
console.log(fizz.foo); //logs 'bar'
the jQuery factory function (jQuery or $) is essentially** defined in the following way:
function jQuery(...args...) {
//the map returned is a jQuery init object
return new jQuery.init(...args...);
}
//jQuery.init is a function used as a constructor
jQuery.init = function () {...do stuff...};
//jQuery.init.prototype is an object containing the methods that can be called on
//jQuery.init objects
jQuery.init.prototype = {
addClass: function () {...},
on: function () {...},
removeClass: function () {...}
};
//The jQuery.init.prototype object is exposed via jQuery.fn
jQuery.fn = jQuery.init.prototype;
//functions available on the jQuery namespace are added to the jQuery function object.
jQuery.ajax = function () {};
jQuery.extend = function () {};
* sorta
** gross oversimiplification
Related
I've been using JavaScript for a few years now in web design/development, and everything I know has been self-taught (I was a design major and hobbyist front-ender turned full web developer in pursuit of my career). With that background, I discovered something that I want to learn more about and have no idea what it is called, how it works, or that it may even be something extremely simple.
I've tried to search for more information about this (to prevent myself from needing to ask this), but it's difficult when I'm not sure what it's called that I'm even looking for...
I noticed in a few JavaScript libraries that I use that a variable name can be both a function and an object. For example, the jQuery library uses the name "jQuery". When logged using typeof jQuery it is a function, and typeof jQuery() is an object. What is interesting to me is that initial thought would suggest that jQuery() would be a function but it's actually an object.
//jQuery:
ƒ (a,b){return new r.fn.init(a,b)}
//jQuery():
r.fn.init {} //Expanding this reveals functions inside of __proto__ (which is obviously a clue, but I need more info)
When I try to do something like this I end up overwriting the name (as I would expect):
//Declare an object:
var planetEarth = {
'sky': 'blue',
'grass': 'green'
}
//Now overwrite the object as a function:
function planetEarth(){
console.log('inside of a function now');
}
This is understandable within the realm of JavaScript, so my question is how does a library like jQuery pull off having both at the same time?
Ultimately (using the above example) I would like to be able to do something like this:
planetEarth().sky; //"blue"
So my roundabout question is simply what is this called?
And a follow-up of where can I learn the basics of accomplishing this?
I've found resources on object-oriented JavaScript and classes, as well as prototypes, but I'm not sure if either (or both) of those concepts are what this is. All of the articles I've found aren't starting at the beginning and seem to always jump into unnecessarily complex examples. I'm not looking to make a giant library- I just want to get some more experience at the very basic level. Again, this could be embarrassingly simple and I've just never come across the concept before, so I appreciate being pointed in the right direction, thanks.
Every JavaScript function is also an object, just as every array is also an object. I don't think there is a special name for this, it's just how the language works.
You may be confusing two different things: what a function returns when you call it, vs. the function itself. Take your example:
planetEarth().sky; // "blue"
This code does not rely on the function planetEarth being an object and having any properties. You're calling the function, so to make this code work the function would need to return an object.
Because a function is an object, you can also set properties directly on the function itself. The code below uses both of these features. This version of planetEarth() returns an object with sky and grass properties, so it works as in your example: you call the function to get an object with those properties.
The code also sets an exists property directly on the function, and you can access that property without calling the function:
function planetEarth() {
// Return an object when this function is called
return {
sky: 'blue',
grass: 'green'
}
}
// Set a property directly on the function itself
planetEarth.exists = true;
// Test accessing the function's property
console.log( planetEarth.exists );
// Test accessing a property in the object that the function returns when called
console.log( planetEarth().sky );
jQuery makes use of both of these facilities. jQuery is a function that you can call. It returns a value commonly called a "jQuery object". That object has properties and methods, such as jQuery('#foo').show(). It also has "array-like" properties that you can access as you would any array, e.g. jQuery('#foo').length and jQuery('#foo')[0]. The jQuery function adds those properties to the value it returns.
At the same time, the jQuery library adds other properties and methods to the jQuery function itself. You access without calling the function, e.g. jQuery.ajax({...}).
A couple of other notes to help you understand the jQuery code. First, download the uncompressed version of jQuery. It looks like you are studying the compressed version, which has shortened names that won't make any sense.
Also, if you are wondering what jQuery.fn is, it is simply an alias for jQuery.prototype. Whenever you see jQuery.fn you can mentally substitute jQuery.prototype; learn how JavaScript prototypes work and you will understand jQuery.fn.
And now you may wonder why jQuery.fn exists at all. Why not use jQuery.prototype directly like other JavaScript code that uses prototypical inheritance? In fact, you could, and it would work the same as jQuery.fn. But the very first version of jQuery, back in 2006, didn't work this way. jQuery.fn was its own separate object, and every time you called the jQuery function, it copied all of the methods and properties from this object into the value it returned.
As you can guess, this was rather inefficient, so jQuery was changed to use .prototype so it could return an object that inherits all the methods such as .show() and .hide() instead of copying them all one by one. At the same time, jQuery.fn was kept around as an alias for jQuery.prototype to avoid breaking existing code.
This is a silent answer...
function f() {
return {};
}
console.log(typeof f, typeof f());
This is how jQuery does it. f is a function but when it gets called it returns an object.
Interesting part: function is also an object. f instanceof Function and f instanceof Object are both valid. So, you can call a variable as a function and / or assign some properties because it is also an object.
f.test = 123;
First-Class Objects
In Javascript, functions are first-class objects. This means that functions are just another kind of object. You can put a function in a variable, you can return a function, you can make an array of functions, and all that. Functions are objects.
Consider a slight change in your attempt:
//Declare a function:
function planetEarth(){
console.log('inside of a function now');
}
// Now add fields to it, since it is also an object
planetEarth.sky = 'blue';
planetEarth.grass = 'green';
// Test stuff
planetEarth(); // works
console.log(planetEarth.sky, planetEarth.grass); // works
You mention that you would like to use planetEarth().sky, but observe that while planetEarth is a function (and an object, as I said), planetEarth() is the result of calling planetEarth with no parameters. Therefore, whether you can or can't do planetEarth().sky does not depend on planetEarth as an object having the sky field, but rather depends on whatever you return from planetEarth having that field.
Bonus: did you know that functions can be declared very much like "normal" variables? See below:
// Both lines of code below are identical:
function myFunc() { ... }
var myFunc = function() { ... };
Perhaps looking at the code above will help you clear the confusion. The function is myFunc. myFunc() is simply the act of calling that function. If typeof myFunc() gives function, it is just a coincidence that the object that myFunc returned happened to also be a function.
jQuery is a function. Properties can be assigned to a defined function.
function planetEarth(options){
console.log('inside of a function now', options);
return window["planetEarth"];
}
var planetEarthOptions = {
'sky': 'blue',
'grass': 'green'
}
for (let prop in planetEarthOptions) {
planetEarth[prop] = planetEarthOptions[prop];
}
window["planetEarth"] = planetEarth;
planetEarth("selector");
console.log(planetEarth.sky);
console.log(planetEarth().grass);
I was always taught that in Javascript there is no distinction between objects and classes. Then can someone explain why this code generate error:
var firstObj = function() {};
firstObj.prototype.sayHi = function() {
document.write("Hi!");
};
firstObj.sayHi();
Whereas this one works:
var firstObj = function() {};
firstObj.prototype.sayHi = function() {
document.write("Hi!");
};
new firstObj().sayHi();
What's the difference? Why isn't the first one working?
The key issue here is that your firstObj variable is a Function object, not a firstObj object. This is a subtle distinction, but the type of object determines which prototype it inherits.
The prototype is like a template that is applied to newly created objects of a particular type. You must create a firstObj object (usually with new which invokes the constructor and assigns a prototype) in order to have that template applied to it. In the first example, your firstObj variable is a Function object, not a firstObj object so it has the prototype of a Function not of anything else..
In your second example, you actually create a firstObj object so it inherits the prototype for that type of object.
If you want the method applied in your first example so it works on the function object you've already created, just put the method directly on your already existing function object, not on the prototype.
There is no difference in the language between objects and classes1. However, there is a big difference between one kind of object and another. In the first case:
firstObj.sayHi();
you are trying to access the sayHi property of firstObj, which is a Function object that does not have such a property. (You could, however, do firstObj.prototype.sayHi().)
In the second case:
new firstObj().sayHi();
you are first invoking the new operator on the firstObj object, which evaluates to a new object. That new object has firstObj as it's constructor property and a prototype equal to the prototype property of firstObj. You are then accessing the sayHi property of that returned object, which succeeds because sayHi is in the prototype chain for that object.
1 Technically, JavaScript doesn't have classes2 (in the traditional sense), just constructor functions that are usually called "classes".
2 However, class is a future reserved word.
when you write this:
var firstObj = function() {};
you only define a constructor function, thus you need to use the key word new for the new objects created with this constructor function.
A function is just a function until new is issued. At that point, a Function Object is created based on the prototype for the function. That is why you will not see the sayHi method present in the first version.
Also, firstObj is a function, and not an object, so you need to invoke it to actually have anything happen. firstObj will not actually invoke the function, you must use firstObj().
Further, there are ways to have the prototype used without explicitly requiring the new keyword. This is done in a number of popular frameworks (such as jQuery). It is done by checking to see if new was used, and if it was not, then it news one up for you on the spot:
jsFiddle Demo
var firstObj = function() {
if( !(this instanceof firstObj) ){
return new firstObj();
}
};
firstObj.prototype.sayHi = function() {
alert("hi");
};
firstObj().sayHi();
(function($)
{
$.vari = "$.vari";
$.fn.vari = "$.fn.vari";
// $.fn is the object we add our custom functions to
$.fn.DoSomethingLocal = function()
{
return this.each(function()
{
alert(this.vari); // would output `undefined`
alert($(this).vari); // would output `$.fn.vari`
});
};
})(jQuery);
// $ is the main jQuery object, we can attach a global function to it
$.DoSomethingGlobal = function()
{
alert("Do Something Globally, where `this.vari` = " + this.vari);
};
$(document).ready(function()
{
$("div").DoSomethingLocal();
$.DoSomethingGlobal();
});
I am confused why in the $.fn.DoSomethingLocal function, $(this).vari is the string "$.fn.vari" and not "$.vari". I thought that the this reference in the each call gives a dom object, so calling $(this) returns a standard jquery object with prototype $, not $.fn.
How does this happen?
The jQuery.fn namespace is inherited by jQuery objects. So if you write $.fn.somewhat you now can access this by $().somewhat. That is why any jQuery plugin has to extend the jQuery.fn object/namespace.
The pattern jQuery.somewhat would just extend the plain object with somewhat. That is as good as window.somewhat or anyobject.somewhat.
$.fn has a prototypal inheritance, that is all the magic.
return this.each(function()
{
alert(this.vari); // would output `undefined`
alert($(this).vari); // would output `$.fn.vari`
});
In this context, this always refers to the object of invocation, that is, a jQuery object. Since all jQuery objects inherit from jQuery.fn you are getting $.fn.vari here.
note
Within the scope of $.fn, this references to a jQuery object !
Within the scope of .each(), this references to a DOM node !
Why?
Javascript's .apply() function allows you to specify the context in which you want to execute a function. That principle is used by "Resig & the boys" to "derefer" this.
when iterating through a jquery object with .each(), this referes to the element NOT wrapped in a jquery object. The method jQuery.vari() is on the jquery object, not the elements, so you have to wrap the element in a jquery object before you can call the $.vari() method.
If we look at the latest jQuery source at http://code.jquery.com/jquery-latest.js we see the following:
var jQuery = function( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
return new jQuery.fn.init( selector, context );
}
My understanding of the new keyword in Javascript is essentially JavaScript passes the function an empty object {} and the function sets stuff on it via this.blah.
Also from my understanding new differs from .call/.apply etc.. in that the return object also has the prototype set to that of the function. So the return value should have a prototype that the same as jQuery.prototype.init.prototype (or jQuery.fn.init.prototype). However from what I see its prototype is set to jQuery.prototype thus all the commands available to work on the set.
Why is this? What am I missing in my understanding?
If you look deeper into jQuery's code, you'll notice this line:
// Give the init function the jQuery prototype for later instantiation
jQuery.fn.init.prototype = jQuery.fn;
This is for readability/structure purposes so the constructor can have its own method.
There's no real "magic" being done here, just standard JavaScript, albeit in a slightly less commonly used way, perhaps. It's useful in jQuery's case since the library is pretty lengthy and this adds good structure/readability to it.
In that source file, search for the string "Give the init function the jQuery prototype for later instantiation" :-)
The code sets the prototype reference of jQuery.fn.init to jQuery.prototype (which is the same as jQuery.fn I think).
Little extended question is why jQuery do
jQuery.fn = jQuery.prototype = {
init: function() {...},
f1: function() {...},
...
};
jQuery.fn.init.prototype = jQuery.fn;
Why not simply add f1() etc into init.prototype? Is it only aesthetic or there are some deep ideas?
The function jQuery.fn.init is the one that is executed when you call jQuery(".some-selector") or $(".some-selector"). You can see this in this snippet from jquery.js:
jQuery = window.jQuery = window.$ = function( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
return new jQuery.fn.init( selector, context );
}
So, in fact, the line you mention is critical to how jQuery allows the addition of functionality to jQuery objects, both inside jQuery itself and from plugins. This is the line:
jQuery.fn.init.prototype = jQuery.fn;
By assigning jQuery.fn as the prototype of this function (and because the first snippet uses 'new' to treat jQuery.fn.init as a constructor), this means the functionality added via jQuery.fn.whatever is immediately available on the objects returned by all jQuery calls.
So for example, a simple jQuery plugin might be created and used like this:
jQuery.fn.foo = function () { alert("foo!"); };
jQuery(".some-selector").foo();
When you declare 'jQuery.fn.foo' on the first line what you're actually doing is adding that function to the prototype of all jQuery objects created with the jQuery function like the one on the second line. This allows you to simple call 'foo()' on the results of the jQuery function and invoke your plugin functions.
In short, writing jQuery plugins would be more verbose and subject to future breakage if the implementation details changed if this line didn't exist in jQuery.
The jQuery.fn is just an alias for jQuery.prototype. I suppose it is defined for aesthetic and less typing reasons.
So
jQuery.fn.init.prototype = jQuery.fn;
is actually
jQuery.prototype.init.prototype = jQuery.prototype;
As why this needs to be done, this forum post is helpful:
It gives the init() function the same
prototype as the jQuery object. So
when you call init() as a constructor
in the "return new jQuery.fn.init(
selector, context );" statement, it
uses that prototype for the object it
constructs. This lets init()
substitute for the jQuery constructor
itself.
What you achieve is that the object returned from a jQuery.fn.init constructor has access to jQuery methods.