Archives
-
JavaScript: this
JavaScript is a language steeped in juxtaposition. It was made to “look like Java,” yet is dynamic and classless. From this origin, we get the
new
operator and thethis
keyword. You are probably used tothis
referring to the current instance of a class, so what could it mean in a language without classes?In JavaScript,
this
refers to the object off of which a function is referenced when it is invoked (unless it is invoked viacall
orapply
). What this means is thatthis
is not bound to your function, and can change depending on how your function is invoked. It also means thatthis
changes when declaring a function inside another function (i.e. each function has its ownthis
), such as when writing a callback. Let's see some of this in action:var obj = { count: 0, increment: function () { this.count += 1; }, logAfterTimeout = function () { setTimeout(function () { console.log(this.count); }, 1); } }; obj.increment(); console.log(obj.count); // 1 var increment = obj.increment; window.count = 'global count value: '; increment(); console.log(obj.count); // 1 console.log(window.count); // global count value: 1 var newObj = {count:50}; increment.call(newObj); console.log(newObj.count); // 51 obj.logAfterTimeout();// global count value: 1 obj.logAfterTimeout = function () { var proxiedFunction = $.proxy(function () { console.log(this.count); }, this); setTimeout(proxiedFunction, 1); }; obj.logAfterTimeout(); // 1 obj.logAfterTimeout = function () { var that = this; setTimeout(function () { console.log(that.count); }, 1); }; obj.logAfterTimeout(); // 1
The last couple of examples here demonstrate some methods for making sure you get the values you expect. The first time
logAfterTimeout
is redefined, we usejQuery.proxy
to create a new function which has itsthis
permanently set to the passed in value (in this case, the currentthis
). The second timelogAfterTimeout
is redefined, we save the value ofthis
in a variable (namedthat
in this case, also often namedself
) and use the new variable in place ofthis
.Now, all of this is to clarify what’s going on when you use
this
. However, it’s pretty easy to avoid usingthis
altogether in your code (especially in the way I’ve demonstrated above). Instead of usingthis.count
all over the place, it would have been much easier if I’d madecount
a variable instead of a property, and then I wouldn’t have to usethis
to refer to it.var obj = (function () { var count = 0; return { increment: function () { count += 1; }, logAfterTimeout = function () { setTimeout(function () { console.log(count); }, 1); }, getCount: function () { return count; } }; }());
If you’re writing your code in this way, the main place you’ll run into issues with
this
is when handling DOM events (wherethis
is the element on which the event occurred). In that case, just be careful when using a callback within that event handler, that you’re not expectingthis
to still refer to the element (and useproxy
orthat
/self
if you need to refer to it).Finally, as demonstrated in the example, you can use
call
orapply
on a function to set itsthis
value. This isn’t often needed, but you may also want to know that you can useapply
to pass in an array of arguments to a function (e.g.console.log.apply(console, [1, 2, 3, 4])
). -
JavaScript: Global Variables
One of the biggest issues that you can run into with writing JavaScript is abusing the shared global namespace, and overwriting the variables of an unrelated script. Everything that you declare in JavaScript is available to every other script on the page, unless you wrap the declaration in a function. Also, if you use a variable without declaring it (even within a function), it will be declared for you in the global scope.
There are a couple of remedies we have against creating a global soup of conflicting variables. Knowing is half the battle, so step one is just keeping an eye out. Recognize when you’re creating a function or variable, and make sure that it’s not a global. The main technique for this is to declare everything inside a function. If you’re writing jQuery scripts, most of your script is probably being written inside the callback passed to
jQuery(document).ready()
, so you’re mostly safe already. If you’re not using a function already, you can wrap your code in an immediately invoked function expression.Step two is to catch when you miss declaring a variable. There are two ways to do this. The first is to turn on strict mode, so that browsers which support strict mode will throw an error when you try to access an undefined variable (instead of defining it as a global). In addition, you should be running your code through a linter (JSLint or JSHint) to catch issues like this.
So, now that you know how not to expose global variable, what about if you need to? If you need to write code in one script and reference it from another, you’ll need to expose a reference to that code somehow, right?
In this case, you should still probably be using all of the above techniques to keep the scope clean, but then explicitly expose anything that you need to. Again, if you’re using jQuery, this is fairly natural. Your script will take a reference to the global jQuery function, and you can add your script as a plugin to
jQuery.fn
. If you’re not exposing a jQuery plugin, you can take a reference to the globalwindow
object, and add your object explicitly. As an example:(function (window, $) { “use strict”; // turn on strict mode var x = 10; // x will not be accessible outside of this function y = x * 20; // y will be declared as a global variable, unless the browser supports strict mode $.fn.myPlugin = function (args) { ... }; // expose your plugin to the rest of the page window.textbox2_validate = function (sender, args) { ... }; // expose a standard function }(this, jQuery)) // immediately invoke this function expression, passing in this (a reference to the global scope) and jQuery
-
JavaScript Functions
When coming from a strongly-typed, object-oriented background, there may be some surprising elements when looking at how functions are used in JavaScript.
Functions in JavaScript don’t have any real concept of a signature, like they would in .NET. All functions return a value (which will be the value
undefined
if nothing is explicitly returned), so there are no void functions, and, while you can define named parameters for a function, callers of the function can provide more or less parameters than specified. If less parameters are provided, the other parameters will beundefined
, if more are provided, they are accessible via an implicit array-like variable namedarguments
. This also means that it’s not possible to have function overloads which have the same name and differ just by signature.There are also two different ways to create a function. The one that looks more like the object-oriented style is a function statement. This is a statement that starts with function, then gives the function name, parameter list, and body. One special characteristic of function statements is that they are defined throughout the entire scope of their containing function (i.e. they can be used before they are defined). They are said to be hoisted to the top of the containing function.
The other option is a function expression. This can look just like a function statement, but doesn’t require a function name, and is a value that can be assigned to a variable, passed to another function, or invoked. Let’s see some examples:
var myFunc = function (x) { if (typeof x === 'undefined') { return 'no args'; } else if (typeof x === 'function') { return x(); } else if (arguments.length === 1) { return 'x was ' + x; } else { return arguments.length - 1 + ' extra arguments provided'; } }, result1 = myFunc(), result2 = myFunc(function (y) { console.log(y); }), result3 = myFunc(statement), result4 = myFunc(1), result5 = myFunc({}, true, 'text'); console.log(result1, result2, result3, result4, result5); function statement(x, y, z) { console.log(x, y, z); }
So, we start here by defining a variable
myFunc
, whose value is a function expression. The function has one parameter,x
, and returns a string value indicating what was passed in. Forresult1
, we callmyFunc
without any arguments, and see thatx
isundefined
. Forresult2
andresult3
, we callmyFunc
with a function argument (forresult2
it’s a function expression, forresult3
it’s a reference to a function defined via a function statement), call that function, and return the result. Because both functions we pass in don’t return anything, the result isundefined
. Note that inresult3
, we were able to use statement before it was defined. Forresult4
, we callmyFunc
with one argument, and refer to the value via the parameterx
. Forresult5
, we callmyFunc
with more than one argument, and refer to thearguments
collection. -
Why isn’t My Scheduled Task Executing?
A quick diversion from the JavaScript Common Difficulties and Misconceptions series (I have another post queued up, just need to write the sample code), for an issue I just ran into.
Created a scheduled task for DNN, but it’s sitting in the scheduler queue, just getting more and more overdue. Why won’t my task run?
Somehow I missed the constructor from the tasks I copied. Your task needs a constructor that takes a
ScheduleHistoryItem
to run via the scheduler.