Why does jQuery do this in its constructor function implementation? - javascript

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).

Related

What JavaScript concept allows for the same name to be both a function and an object?

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);

Proxying jQuery.init. Awkward behaviour

I'm trying to use jQuery.fn.extend to override jQuery.fn.init with my own implementation which is going to behave differently from the original implementation but is going to need to call the original implementation at certain places to do the real work.
So, the very first thing would be to actually "proxy" for the original implementation in the new one. That's what I'm attempting to do and I'm observing what seems like awkward behaviour (this seems like one of those JS things).
You can see my latest attempt here. So the problem is. It is only supposed to apply the border to the .parent .child. Not both .parent and .child. That's what it seems to be doing right now. If you remove my jQuery.fn.extend call. You can see the original, correct behavior.
So, the question is what am I doing wrong? Is this the right approach to proxying any JS function and specially the jQuery init function? Is there a better way?
Extra
I saw this old question and the answer there refers to jQuery.sub which has been moved to the jQuery Migrate plugin now. Should I try to use that? Is it any more likely to work than what I'm trying right now? Do I really need to?
Note: you really shouldn't be doing this, as internal jQuery code also calls the constructor, which could bring about very unusual issues if you aren't extremely careful.
Your problem is that you aren't calling oldInit as a constructor - rather as a function, which doesn't really work because anything set inside jQuery.fn.init will go on jQuery.fn, rather than a new jQuery object.
Why doesn't just setting the ThisBinding to {} work then?
Although this can seem somewhat intuitive after learning about the way the "new" operator works, this doesn't actually do the same thing. Why?
function Foo() { this.bar(); }
Foo.prototype.bar = function () { alert(1); };
new Foo; // 1 is alerted
Foo.apply({}); // TypeError: Object #<Object> has no method 'bar'
When new is used, it also gives the instance's __proto__ the constructor's prototype object. When you create an object literal, this is the standard Object.prototype, not the intended prototype object.
What to do then?
If you are attempting to override jQuery.fn.init, you need to use something like this:
var oldInit = jQuery.fn.init;
jQuery.fn.extend(
{ init: function ()
{ return oldInit.apply(new oldInit, Array.prototype.slice.call(arguments));
}
});
How does this work?
By calling new oldInit with no arguments, we'll just get back an empty object with __proto__ set to jQuery.fn, exactly what we want. Then, we'll supply the arguments to oldInit, and any arguments will go straight to this empty new object, just like the original.
This works because when you call it, your actual ThisBinding (the value of this inside the function call) will already be the new jQuery object, because you are meant to refer to this to add new properties to an instance of the jQuery.fn.init constructor.
Your original code was the following:
var oldInit = jQuery.fn.init;
jQuery.fn.extend({
init: function () {
return oldInit.apply(jQuery.fn, Array.prototype.slice.call(arguments));
}
});
That would make your constructor think that the new function is meant to be jQuery.fn, which is probably not what you had intended. If you had a standard function, that would work (as that is what the ThisBinding is meant to be), however since the "new" operator changes the ThisBinding, this no longer means the same thing as before.

jQuery fn Property

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

Is it possible to create a function where [[prototype]] refers to another function

I would like to create function objects (yes, all functions are objects) with some control over the prototypal inheritance, that is, I would like one function to inherit from another.
I can make objects that have prototypal inheritance, and know to set the prototype property of a function performing as a constructor to initialize the [[prototype]] property of the object.
However, when creating a function, I must use the function operator or the Function constructor. I could try to swizzle Function.prototype, but (1) don't know if that is writable, and (2) that just seems quite dangerous [still, I should try doing that].
btw: I only care to do this for V8 within node.JS, so if there are means that work only for that environment, that would be acceptable.
For the record, I have seen this:
Is it possible to create a function with another prototype than Function.prototype?
In V8 (and most other browsers/engines except IE) you can change an object's prototype by setting the __prototype__ __proto__ attribute. Setting the prototype attribute will instead change the prototype that is used to create an object if the function is invoked as a constructor function. This should not be what you want.
Afaik there currently is no standard conform way to directly "subclass" a function (or array for that matter). There's only Object.create, but there is no Function.create or Array.create.
EDIT: I just realized that function objects do not have the __prototype__ attribute and changing / setting it will not turn an object into a function.
I believe though that I just recently watched a talk by Brendan Eich (the creator of JavaScript) in which he talked about Function and Array equivalents of Object.create. And infact, googling for "Function.create brendan eich" reveals the following blog post by Eich in which he talks about his wish to implement Function.create and Array.create.
EDIT 2: Ok, I screwed up. Pretty much. The non-standard attribute is of course __proto__ and not __prototype__. Setting __proto__ works fine for functions with some restrictions, which are:
To be able to call aFunction you must initialize it with an actual function, eg:
var aFunction = function () {};
This is important. Calling a function does not access the prototype chain, so if you define aFunction as an object and simply set the __proto__ attribute, you will not able to call aFunction.
You can now assign any other function to aFunction.__proto__ and reading any members (including methods) will correctly delegate to the prototype chain if the porperty is not found on aFunction itself.
Calling aFunction() will always invoke the function that was originally declared when aFunction was defined and will never invoke aFunction's prototype function. So the body of the function is not subject to inheritence.
Sorry for screwing up first with the name of the attribute. Hope this helps you nevertheless.
I came up with a solution that solves my needs. It is not cross-browser, but can be used cross-browser. My most important use case is as a module for node.JS. In that case, the mechanism of setting __proto__ works just fine, in which case I can call methods on the base function object
f.method(args...);
and it executed by code in the "super" function. Because the method is invoked by the method invocation pattern, "this" is set to the base function object, and so the proper properties are accessed even though the method resides in the "super."
Now for the in-Browser case: when I use my library client-side, I provide a proxy mechanism. Alas, code intended for the browser must be written differently. The invocation is:
f.proxy(methodName, args...);
The proxy method in the base function object is:
f.proxy = function (methodName) {
var p = this.constructor.prototype;
return p.proxy(this, methodName, arguments);
};
The proxy in the "super" object's prototype is:
proxy: function (instance, methodName) {
var args = Array.prototype.splice.apply(arguments, [2]),
method = this[methodName];
return (method) ? method.apply(instance, args) : undefined;
}
I probably should have named this "forward" or some such, but "proxy" is good enough.
Perhaps this mechanism might be useful to someone...
I think I understand what you're trying to do. In short, there's no way to really do it natively. You'd have to access the Function constructor, which for function expressions and definitions (i.e. anything using the 'function' keyword), isn't possible as far as I can tell. You could overwrite Function and just always use new Function([args], string) but I doubt you (or any JS programmer) want to do that.
Your best bet would probably be to send your function expressions to another function that returns the function object with your custom methods dynamically added:
wrapFunc = function(f){
f.customFunc = someCustomFunc;
return f;
}
var myNewFunc = wrapFunc(
function(){
//do something
}
);
myNewFunc.customFunc();

Why jQuery do this: jQuery.fn.init.prototype = jQuery.fn?

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.

Categories

Resources