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);
Related
I want to make a class in node that runs a set of stages that can be easily added or removed. My current code looks like this:
function MyClass(){
this._stages = [this.function1, this.function2];
this._info = {'a':'special object info'};
};
MyClass.prototype.run = function(){
for(var i = 0; i<this._stages.length; i++){
this._stages[i]();
}
};
MyClass.prototype.function1 = function(){
this.subfunction1();
};
MyClass.prototype.subfunction1 = function(){};
Unfortunately, it seems like putting the function inside the Array messes up their 'parent', and I get an error saying that
TypeError: Object function (query) {
[...long list of elements]
} has no method 'subfunction1'
at Array.MyClass.function1...
Is there a way to accomplish this by-stage execution without having this happen?
Thanks!
When you put it in the array, it loses its association with this. So, when you then call those functions, they won't have the right this value. You can fix it with .bind():
function MyClass(){
this._stages = [this.function1.bind(this), this.function2.bind(this)];
this._info = {'a':'special object info'};
};
A different way to fix it would be to reattach this when you call the functions by using .call() to specifically set the this pointer like this:
MyClass.prototype.run = function(){
for(var i = 0; i<this._stages.length; i++){
this._stages[i].call(this);
}
};
As an explanation, when you do this:
var x = this.function1;
x();
then, all that's in the x variable is a pointer to the function1 function. If you call x(), it will be called without any object reference so this in the function will be either the global object or undefined if running in strict mode. It will not be the desired object.
.bind() creates a stub function that stores the this value and then calls the original function with the right object reference such that the this value is set properly.
If you want a review of how this is set, you can see this answer: When you pass 'this' as an argument
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.
I just started programming and I've been playing around with it. I watched a programming course on lynda.com to start out, but it didn't cover functions very well, I know what they are, but I don't know the different formats of functions or how to call them. I know how to call simple things like this:
var foo=function {
//code...
}
but I want to use more complicated things like this (I'm starting to to things with HTML):
$(document).keypress(function(e)) {
if(e.which == 13) {
alert('You pressed enter!');
}
});
or just whatever other styles there are.
There are no different formats of functions. All of them are defined with function keyword followed by arguments and body. If you have a function foo then you call it via foo(); (you can pass arguments as well, foo(1,2,3);). There are more complex ways of calling functions, for example via foo.call or foo.apply but I don't think it is necessary to talk about that here.
Note that functions in JavaScript are first class citizens. That means that they can be treated as objects. In particual you can pass them to another function. Have a look at this example:
var foo = function(fn) {
return fn()+1;
};
What happens here? foo takes a function as an argument (we know that because fn() is called in body), calls it and adds 1 to result. So if I call foo like this:
foo(function() {
return 1;
});
what would the result be? That's a simple example. Consider something more complex:
var foo = function(list, fn) {
var res = [];
for (var i = 0; i < list.length; i++) {
var mapped = fn(list[i]);
res.push(mapped);
}
return res;
};
This is a simple version of the .map method on lists. So it takes a list and a function as arguments, then applies the function to each element of the list and returns a new list:
> foo([1, 2, 3], function(el) { return -el; });
[-1, -2, -3]
It is the same as calling
> [1, 2, 3].map(function(el) { return -el; });
[-1, -2, -3]
Different ways of declaring functions
function A() {}; // function declaration
var B = function() {}; // function expression
var C = ( function() {} ); // function expression with grouping operators
var D = function foo() {}; // named function expression
var E = ( function() { // immediately-invoke function expression (IIFE) that returns a function
return function() {}
})();
var F = new Function(); // Function constructor
var G = new function() {}; // special case: object constructor
Your code is actually a good example of the different types of functions. First is $(), which represents the JQuery library--a useful add-on to JavaScript. You typically give it a string called a CSS selector, which is a way to pick out a part (or parts) of a document. In this case, you're picking out the whole document, but you could also say something like $("#logo"), which will return a document with id="logo" or $("img") will return all elements in the document.
An object in JS can be just about anything, and there are different types of objects. Each type of object has special functions available to it. These functions are called methods.
In the above example, the document object returned by $(document) has the .keypress() method available to it, which listens for a particular keypress. Keypress needs to know what to do when a key is pressed, and you do so by giving it a function as a parameter. That function will execute every time a key is pressed.
One thing that's useful to remember about JavaScript is that functions are thought of as "first-class citizens", meaning they are as good as straight-up values as far as JavaScript is concerned. So if I have a function like:
var myString = function(word)
{
return word;
}
If I do:
var myWord = "hello";
It's the same as:
var otherWord = myString("hello");
If I compare them:
var compareWords = function(word1, word2)
{
return word1 == word2;
}
var compareMyWords = compareWords(myWord, otherWord);
It's the same as saying
var compareMyWords = true;
This is important to know because it means functions can take other functions as arguments without a problem. We see that happening in the keypress method. It might be a bit hard to notice, but you can see it if you declare the function beforehand:
var keypressFunction = function(e)) {
if(e.which == 13) {
alert('You pressed enter!');
}
The following is the same as your example:
$(document).keypress(keypressFunction);
The difference is that your example uses an anonymous function. It hasn't been declared with "var" or "function", so I couldn't reuse it if I wanted to. That's not really a bad thing. Anonymous functions are very useful when you get into more advanced stuff.
Your anonymous function is allowed one parameter, an object representing the key that was actually pressed. In this case it only wants to do something if the key represented by 13 is pressed (Enter).
Finally there's the alert function, which pops up a message. Note that functions don't always have to return a value. Usually when they don't, they do something to their environment instead (otherwise what's the point). In your example only one function actually returns something--the $() function, although nothing gets done with it, but you could keep going and replace the semicolon at the end with a period and use the ready() method or whatever methods are available to the object returned by $(document). In fact, as long as you know what type of object is being returned, you could do something like:
$("#message").addClass("alert").fadeIn().delay().fadeOut();
Because each of these methods return the same object, we can do this "method chaining". jQuery purposely tries to make most of its methods return the DOM objects it acted on so that you can do this. Other libraries, like d3.js, make use of method chaining for some very cool visual transformations.
I don't think starting off using jQuery is as horrible an idea as others might advise. It's still JavaScript. It could be argued that JQuery forces you to learn advanced concepts (callbacks, CSS selectors, etc) that you could otherwise hobble by for years without knowing. I could also argue that JavaScript itself is a bad language to start with when learning to program.
Instead I'll say dive in and have fun! The ultimate purpose is to build awesome things & not get too crippled by having to do it the right way. Find a good balance between learning and building, and don't get too discouraged by the few jealous stackoverflow users who come here to snuff the joy they once had in building awesome things.
I order to know how to call different types of functions in Javascript it is important to know all JavaScript Function types. Below is a link that take you to:
All JavaScript Function Types?
I personally think you are over complicating it.
Yes, the different ways of writing a function tend to have different names (function, anonymous function, colosure, lambda,....) but in the end its all a function:
They combine several statements into one.
For example:
// Crating a function (most commomn way)
function a() {
alert('a');
};
// Creating a anonymous (nameless) function and assigning it to b
var b = function() {
alert('b');
};
var c = a;
var d = b;
var e = {};
// Creating a function that accepts a function and executes it.
// This is in essence what $(document).keypress() does
e.f = function(f){
f();
};
// Alerts: a
a();
// Alerts: b
b();
// Alerts: a
c();
// Alerts: b
d();
// Sample how you can call $(document).keypress()
// Alerts: c
e.f( function() {
alert('c');
});
// Alerts: a
e.f(a);
// Alerts: b
e.f(b);
I have this code:
PageList: function(url, index, classes){
this.url = url;
this.index = index;
...
};
PageList.prototype.toHTML = function(){
var div = $('<div class="container"></div>');
var p = $('<p></p>');
var link = $('<a></a>');
$.each(this.elements, function(index, array_value){
console.log(this.url);
...
}
}
And it worked as expected.
The problem was that console.log(this.url) was printing undefined, so I reworked the code to look like this:
PageList.prototype.toHTML = function(){
var div = $('<div class="container"></div>');
var p = $('<p></p>');
var link = $('<a></a>');
var instance = this;
$.each(this.elements, function(index, array_value){
console.log(instance.url);
}
}
I know that the problem was on the closure not taking this as the value of the instance, but as far as i know a reference to this inside a function that doesn't have an instance bound to it must refer to the window object, instead of undefined, at least that's the case on many of the browsers out there.
So what exactly is going on on my code.
Note: I'm using jQuery and this.elements is already defined.
Edit: Now im figuring out that $.each is a non-instance function, so my callback is being called from $.each but it must be window the reference to this, still thinking about it.
According to the jQuery docs for $.each:
The value [of the current element] can also be accessed through the this keyword...
In JavaScript, when you hand off a callback function to a higher-order function (in this case, $.each), the higher-order function can decide what the value of this will be when the callback runs. There is no way for you to control this behavior -- simply don't use this (e.g., by using a reference like instance in your example or via a closure).
Check out the context-setting functions Function.call and Function.apply to understand how a higher-order function like $.each sets the this context of a callback. Once you read those MDN pages, it might clear a few things up.
Here's a quick example:
Array.prototype.forEachWithContext(callback, this_in_callback) {
for(var i = 0; i < this.length; ++i) {
callback.call(this_in_callback, i, this[i]);
}
}
And to use it:
PageList.prototype.toHTML = function(){
//...
this.elements.forEachWithCallback(function(index, array_value){ ... }, this);
}
My example Array.forEachWithContext is similar to Array.forEach. However, it takes a callback and a second argument that is used as the value of this during the execution each of those callbacks.
Try wrapping your $.each function with a $.proxy like this...
$.each(this.elements, $.proxy(function(index, array_value){
console.log(this.url);
},this));
The $.proxy will ensure that this references your PageList...
I know that the problem was on the closure not taking this as the value of the instance, but as far as i know a reference to this inside a function that doesn't have an instance bound to it must refer to the window object, instead of undefined, at least that's the case on many of the browsers out there.
this is window. You're printing window.url, which is undefined. Try console.log(this), and it should yield window.
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