How does basic object/function chaining work in javascript? - javascript

I'm trying to get the principles of doing jQuery-style function chaining straight in my head. By this I mean:
var e = f1('test').f2().f3();
I have gotten one example to work, while another doesn't. I'll post those below. I always want to learn the first principle fundamentals of how something works so that I can build on top of it. Up to now, I've only had a cursory and loose understanding of how chaining works and I'm running into bugs that I can't troubleshoot intelligently.
What I know:
Functions have to return themselves, aka "return this;"
Chainable functions must reside in a parent function, aka in jQuery, .css() is a sub method of jQuery(), hence jQuery().css();
The parent function should either return itself or a new instance of itself.
This example worked:
var one = function(num){
this.oldnum = num;
this.add = function(){
this.oldnum++;
return this;
}
if(this instanceof one){
return this.one;
}else{
return new one(num);
}
}
var test = one(1).add().add();
But this one doesn't:
var gmap = function(){
this.add = function(){
alert('add');
return this;
}
if(this instanceof gmap) {
return this.gmap;
} else{
return new gmap();
}
}
var test = gmap.add();

In JavaScript Functions are first class Objects. When you define a function, it is the constructor for that function object. In other words:
var gmap = function() {
this.add = function() {
alert('add');
return this;
}
this.del = function() {
alert('delete');
return this;
}
if (this instanceof gmap) {
return this.gmap;
} else {
return new gmap();
}
}
var test = new gmap();
test.add().del();
By assigning the new gmap();to the variable test you have now constructed a new object that "inherits" all the properties and methods from the gmap() constructor (class). If you run the snippet above you will see an alert for "add" and "delete".
In your examples above, the "this" refers to the window object, unless you wrap the functions in another function or object.
Chaining is difficult for me to understand at first, at least it was for me, but once I understood it, I realized how powerful of a tool it can be.

Sadly, the direct answer has to be 'no'. Even if you can override the existing methods (which you probably can in many UAs, but I suspect cannot in IE), you'd still be stuck with nasty renames:
HTMLElement.prototype.setAttribute = function(attr) {
HTMLElement.prototype.setAttribute(attr) //uh-oh;
}
The best you could probably get away with is using a different name:
HTMLElement.prototype.setAttr = function(attr) {
HTMLElement.prototype.setAttribute(attr);
return this;
}

To "rewrite" a function, but still be able to use the original version, you must first assign the original function to a different variable. Assume an example object:
function MyObject() { };
MyObject.prototype.func1 = function(a, b) { };
To rewrite func1 for chainability, do this:
MyObject.prototype.std_func1 = MyObject.prototype.func1;
MyObject.prototype.func1 = function(a, b) {
this.std_func1(a, b);
return this;
};
Here's a working example. You just need to employ this technique on all of the standard objects that you feel need chainability.
By the time you do all of this work, you might realize that there are better ways to accomplish what you're trying to do, like using a library that already has chainability built in. *cough* jQuery *cough*

First, let me state that i am explaining this in my own words.
Method chaining is pretty much calling a method of the object being returned by another function/method. for example (using jquery):
$('#demo');
this jquery function selects and returns a jquery object the DOM element with the id demo. if the element was a text node(element), we could chain on a method of the object that was returned. for example:
$('#demo').text('Some Text');
So, as long as a function/method returns an object, you can chain a method of the returned object to the original statement.
As for why the latter don't work, pay attention to where and when the keyword this is used. It is most likely a context issue. When you are calling this, make sure that this is referring to that function object itself, not the window object/global scope.
Hope that helps.

Just call the method as var test = gmap().add();
as gmap is a function not a variable

Related

JS: The Good Parts: The `superior` function

I am trying to understand an example from Douglas Crockford’s “Javascript: The Good Parts”, Chapter 1 “Good Parts”, regarding inheritance. Specifically the superior function. In the book it is as follows:
Object.method('superior', function(name) {
var that = this, method = that[name];
return function() {
// Why can’t this just be `return method`?
return method.apply(that, arguments);
};
});
As mentioned in the code comment above I don’t understand why we need to use apply when simply returning the function itself seems to work find in my experimentation.
Supplementary Info
The above uses a method function defined as
Function.prototype.method = function(name, func) {
this.prototype[name] = func;
return this;
};
This is a bit of a convoluted way of doing things indeed. One possible advantage is that the resulting function will automatically bind to the initial this context (i.e you will subsequently be able to call it without having to reference the instance).
I guess the following implementation would reveal the intention more clearly:
Object.method("superior", function(name) {
var that = this, method = that[name];
return method.bind(that);
});
(This possible advantages is not directly apparent from the coolcat example on the next page as the get_name method does not make use of a this context.)

Override JS method i Java style [duplicate]

I would like to override a Javascript built-in function with a new version that calls the original (similarly to overriding a method on a class with a version that calls super in many languages). How can I do this?
For example...
window.alert = function(str) {
//do something additional
if(console) console.log(str);
//super.alert(str) // How do I do this bit?
}
Store a reference to the original function in a variable:
(function() {
var _alert = window.alert; // <-- Reference
window.alert = function(str) {
// do something additional
if(console) console.log(str);
//return _alert.apply(this, arguments); // <-- The universal method
_alert(str); // Suits for this case
};
})();
The universal way is <original_func_reference>.apply(this, arguments) - To preserve context and pass all arguments. Usually, the return value of the original method should also be returned.
However, it's known that alert is a void function, takes only one argument, and does not use the this object. So, _alert(str) is sufficient in this case.
Note: IE <= 8 throws an error if you try to overwrite alert, so make sure that you're using window.alert = ... instead of alert = ....
There is no "super". Anyway, create a closure to "keep" around the original function-object.
Note the "self invoking function" that returns a new function-object (that is assigned to the window.alert property). The new function-object returned creates a closure around the variable original which evaluates to the original value of window.alert that was passed in to the "self invoking function".
window.alert = (function (original) {
return function (str) {
//do something additional
if(console) {
console.log(str)
}
original(str)
}
})(window.alert)
However, I believe some browsers may prevent alert and other built-ins from being modified...
Happy coding.
I'm assuming your question is how do you overwrite a built-in and still be able to call it. First off as a disclaimer, you should never overwrite built ins unless you have a good reason for doing it since it will make it impossible to debug/test.
This is how you would do it:
window._alert = window.alert;
window.alert = function(str) {
if(console) console.log(str);
window._alert(str);
}
How to do simple classical inheritance in Javascript:
SuperClass.call(this) // inherit from SuperClass (multiple inheritance yes)
How to override functions:
this.myFunction = this.myFunction.override(
function(){
this.superFunction(); // call the overridden function
}
);
The override function is created like this:
Function.prototype.override = function(func)
{
var superFunction = this;
return function()
{
this.superFunction = superFunction;
return func.apply(this,arguments);
};
};
Works with multiple arguments.
Fails when trying to override undefined or nonfunctions.
Makes "superFunction" a "reserved" word :-)
JavaScript does not use a classical inheritance model. There is a nice article here which describes a way to write your classes so that a similar syntax can be used, but it's not natively supported.
By using proxy object you can do this.
window.alert = new Proxy(window.alert , {
apply: function(target,that,args){
console && console.log(args.join('\n'));
target.apply(that,args)
}})

Javascript multiple prototype functions - how to call one from another

I've been working a lot lately on making cleaner Javascript code, and using objects with prototypes etc. But I'm confused on some points...
function TimeCard(){
this.date = new Date();
this.pay_period_begin = null;
this.pay_period_end = null;
}
Here is my timecard object with some initial values. I have a bunch of functions that I've written and more to come that are part of that timecard and, if I understand correctly, they will be prototype functions. Here is some of what I have so far:
TimeCard.prototype = {
init : function(){
this.pay_period_begin = $("#pay_period_begin");
this.pay_period_end = $("#pay_period_end");
},
getTimeCardData : function(){
//ajax request
},
selectAll : function(){
this.getTimeCardData();
}
...
};
My problem is that when I try to call this.getTimeCardData() it says that my object has no such method. I can obviously access the other variables because they are declared in my constructor, but I don't understand how prototype scopes I guess. So far I have gotten around this by using tc.getTimeCardData() instead of this.getTimeCardData(), with tc being the instance of my object declared outside - var tc = new TimeCard();. I'm sure that that's not the correct way to go about this, but what is?
My problem is that when I try to call this.getTimeCardData() it says that my object has no such method.
It sounds like this is no longer referring to your instance. You'll have to show us the actual call for us to be sure, but in JavaScript, this is set primarily by how a function is called, not where it's defined, and so it's fairly easy for this to end up being something different.
Here's a hypothetical example:
TimeCard.prototype = {
// ...
doSomething: function() {
// here, `this` probably refers to the timecard
someArray.forEach(function() {
this.getTimeCardData(); // <=== Problem, `this` has changed
});
}
// ...
};
If I call this.doSomething(); on a TimeCard object, within the call this will refer to the timecard. But within the forEach callback, this will no longer refer to the timecard. The same sort of thign happens with all kinds of callbacks; ajax, etc.
To work around it, you can remember this to a variable:
TimeCard.prototype = {
// ...
doSomething: function() {
var thisCard = this;
someArray.forEach(function() {
thisCard.getTimeCardData(); // <=== Problem
});
}
// ...
};
There are also various other ways to work around it, depending on your specific situation. For instance, you have selectAll calling getTimeCardData. But suppose selectAll is called with the wrong this value? In your comment, you said you were doing it like this:
$('#container').on('click', '#selectAll', tc.selectAll);
That means that when selectAll is called, this will refer to the DOM element, not to your object.
You have three options in that specific situation:
Since you're using jQuery, you can use $.proxy, which accepts a function and a value to use as this, and returns a new function that, when called, will call the original with this set to the desired value:
$('#container').on('click', '#selectAll', $.proxy(tc.selectAll, tc));
Use ES5's Function#bind, which does the same thing. Note that IE8 and earlier don't have it unless you include an "ES5 shim" (hence my noting $.proxy above; you know you have that):
$('#container').on('click', '#selectAll', tc.selectAll.bind(tc));
Use a closure (don't let the name bother you, closures are not complicated):
More (on my blog):
$('#container').on('click', '#selectAll', function() {
tc.selectAll();
});
In all of the above, you'll lose the benefit of this referring to the DOM element. In that particular case, you probably don't care, but if you did, you can get it from the event object's currentTarget property. For instance, this calls tc.selectAll with this referring to tc and passing in what would have been this (the DOM element you hooked the handler on) as the first argument:
$('#container').on('click', '#selectAll', function(e) {
tc.selectAll(e.currentTarget);
});
Mythical methods
You must remember this
Another, less likely, possibility relates to how you're updating TimeCard.prototype. The way you're doing it, it's possible to create objects via new TimeCard() before your code that replaces the TimeCard.prototype object runs, which means they'll have the old prototype.
In general, I strongly recommend not replacing the object automatically created for the prototype property of the constructor function. Instead, just add to the object already there, like this:
function TimeCard(){
this.date = new Date();
this.pay_period_begin = null;
this.pay_period_end = null;
}
TimeCard.prototype.getTimeCardData = function(){
//ajax request
};
// ...
Here's why: Timing. If you replace the object on the prototype property, any objects you create via new TimeCard() before you do that replacement will have the old prototype, not the new one.
I also recommend always creating these within a scoping function so you know that the declaration and the prototype additions happen at the same time:
var TimeCard = (function() {
function TimeCard(){
this.date = new Date();
this.pay_period_begin = null;
this.pay_period_end = null;
}
TimeCard.prototype.getTimeCardData = function(){
//ajax request
};
// ...
return TimeCard;
})();
...primarily because it prevents the timing issue.

Calling a function from another function within the same prototype and context of this

In attempting to keep with the DRY principle, I decided to revise a function's prototype in an effort to reduce the number of function.call() calls.
Following is a snippet of what I currently have and further clarification as to what I am attempting to do.
com.domain.$ = function(s){
if(!this){return new com.domain.$(s);}
this.selector = s;
this.elements = document.querySelectorAll(s);
}
com.domain.$.prototype = (function(){
function exe(f){
var e = this.elements,
el = e.length;
for(var i=0; i<el; i++){
f(e[i]);
}
}
function addClass(c){exe.call(this,function(el){el.classList.add(c);});}
function removeClass(c){exe.call(this,function(el){el.classList.remove(c);});}
function toggleClass(c){exe.call(this,function(el){el.classList.toggle(c);});}
return {
addClass:addClass,
removeClass:removeClass,
toggleClass:toggleClass
}
}());
I realize this looks very much like I am attempting to mimic the functionality of jQuery. While intentional, this is not meant to act as a replacement but rather a better personal understanding of JavaScript.
That said, what I would like to do is remove the need to invoke exe() via exe.call(this[, fnc]); in order for the context of this to be what I want. I believe I can do this through function binding (.bind()), though perhaps not the way I would like to. I understand it is possible to instead do something like:
com.domain.$.prototype.exe = function(){}
and call it like:
function addClass(c){this.exe(function(){});}
In doing so, however, I lose the private visibility of exe() provided by the closure in my original code. I would like to keep that in tact, if possible.
My question, then, is whether or not it is possible to bind exe() within my original code in such a way that I can reduce the redundant use of exe.call(this, possess the correct context for this within exe(), and maintain the private visibility within the closure?
If this seems a poor implementation of what I am trying to accomplish, I am more than happy to consider other options.
Thank you in advance.
No, you can't, since at the moment you define exe(), the instance you want to call exe() on does not exist yet.
In fact, if you are going to make several calls to com.domain.$, you will use exe() with different instances and therefore it does not make sense to bind exe() to a specific instance.
If you wanted to do that, you'd have to define all these methods inside the constructor and you would loose all the advantages of prototypes:
(function() {
function exe(f){
// ...
}
com.domain.$ = function(s){
// ...
var exe_ = exe.bind(this);
this.addClass = function(c) {
exe_(function(el){el.classList.add(c);});
};
// ...
};
}());
I would suggest, if you don't want to use .call, to just modify exe() so that it accepts an array of elements as argument and pass this.elements to it from the prototype functions. I don't see why exe() needs to use this at all. It's merely a helper which passes each element of an array to a given function and by making it more general, it is easier to reuse.
For example:
var com.domain.$ = (function(o) {
function exe(arr, f){
var el = e.length;
for(var i=0; i<el; i++){
f(arr[i]);
}
}
var $ = function(s){
// ...
}
$.prototype.addClass = function(c){
exe(this.elements, function(el){
el.classList.add(c);
});
};
// ...
return $;
}());

jQuery: Add function self to array?

Given a function, like so:
var allev = new Array();
function events(index) {
allev[index] = $(this); // doesn't work
}
Is there any way to get this to work in the way above, or am I always restricted to:
var allev = new Array();
function events(index) {
// ...
}
function() { // out of scope, so make objects global
var count = allev.length;
var inst = events(count);
allev[count] = inst;
}
If there are any betters options than the two above, I'm open to those as well.
When you call functions (as opposed to methods) in JavaScript, this is the global object, or undefined in strict mode. So using this like you have above will never work.
You could do this:
function events(index) {
arr[index] = arguments.callee;
alert("Hi");
}
but that will break if you ever use strict mode (which is actually a really good idea).
You could also do this:
function events(index) {
allev[index] = this; // will work if you call with apply or call
}
If, and only if, you only ever call events like this:
events.call(events, 12);
call and apply both allow you to specify what this will be equal to inside of a function call. The difference is that call expects all arguments to be listed out, while apply expects all arguments to be passed in an array. So the above would be equivalent to
events.apply(events, [12]);
DEMO
I decided to look a little more into the jQuery API and I found $.proxy. It does exactly what I want, without having a method pass itself to make this work properly.
allev[index] = $.proxy(function() { return this; }, this);

Categories