August 12, 2011

jQuery.type

In my last post I wrote about how to detect a JavaScript array, so I thought it would be a good idea to see how jQuery does it.

Here's the jQuery.isArray() method (in 1.6.2):
isArray: Array.isArray || function( obj ) {
    return jQuery.type(obj) === "array";
}
It uses the native Array.isArray() method if it exists, if not it will find out the type of whatever is passed in using the jQuery.type() method and see if that equals the string "array".

But come on, that's not much of a learning experience not to mention it makes for a pretty short blog post.

So instead, I looked into the jQuery.type() method to see how that works.

jQuery.type()
It looks something like this:
...
// Save a reference to some core methods
toString = Object.prototype.toString
...
// [[Class]] -> type pairs
class2type = {};
...
type: function( obj ) {
    return obj == null ?
        String( obj ) :
        class2type[ toString.call(obj) ] || "object";
}
...
// Populate the class2type map
jQuery.each(
    "Boolean Number String Function Array Date RegExp Object".split(" "),
    function(i, name) {
	class2type[ "[object " + name + "]" ] = name.toLowerCase();
})
(Distant pieces of code are separated with three dots.)

Breaking it down
  • Line 3 - A reference to the toString() method of Object
  • Line 6 - An internal object (map) that will contain all types as strings.
  • Line 15-19 - For each member in the array we're passing in, we'll create a property in the object and set its value to the name of the array member in lowercase.
  • Line 8-12 - The method will check if the argument passed in is null or undefined, if so, it will return the string "null" or "undefined" (by calling the String constructor without the new keyword, which is allowed for some constructors). If it's something else, we'll call the toString() method on the argument and look to see if the class2type object has a value for it, if not, we'll just return the string "object".

To give you a better overall picture, here's how the class2type object would look after page load:
var class2type = {
    "[object Array]": "array",
    "[object Boolean]": "boolean",
    "[object Date]": "date",
    "[object Function]": "function",
    "[object Number]": "number",
    "[object Object]": "object",
    "[object RegExp]": "regexp",
    "[object String]": "string"
};

Epiphany
That's why we call the toString() method of Object when we're looking for the property name. Remember how this method returns this sort of "[object Constructor]" string pattern?

So, if we did jQuery.type("hello") we'd actually be doing something like class2type["[object String]"], which would return the corresponding value "string"

And that's how the jQuery.type() method works.

It's not that complicated when you have an "Epiphany" section, huh?

Sources:
jQuery.type() - jQuery API Documentation
jQuery 1.6.2 Source Code

July 21, 2011

How to detect a JavaScript array

"Piece of cafe" I bet you're thinking, oh I wish it were that easy.

But fear not, JavaScript does have the typeof operator (returns a string) which is very useful for type-checking:
var str = 'hello',
    num = 64,
    bool = false,
    func = function () {},
    undef;

typeof str; // "string"
typeof num; // "number"
typeof bool; // "boolean"
typeof func; // "function"
typeof undef; // "undefined"
Like shown in the previous code, it works fine for most things. However, it's a little flawed:
var n = null,
    str = new String('hello'), // any wrapper function or custom constructor
    arr = [1, 2, 3];

typeof n; // "object", which is wrong
typeof str; // "object", which is correct but not very useful.
typeof arr; // "object", which is correct but not very useful.

constructor property
Every created object has a constructor property that points to the function that created the object's prototype. You can leverage that reference to do some accurate detections:
var str = new String('hello'),
    arr = [1, 2, 3],
    obj,
    Person = function (name) {
        this.name = name;
    };

obj = new Person('John');

obj.constructor === Person; // true
str.constructor === String; // true
arr.constructor === Array; // true
isArray method
ECMAScript 5 defines a new method for the Array function called isArray(). If it's not implemented by the browser you can define it like this:
if (!Array.hasOwnProperty('isArray')) {
    Array.isArray = function (value) {
        return Object.prototype.toString.call(value) === '[object Array]';
    };
}
Invoking the toString() method of Object, gets us a string representation of the object, for arrays it's [object Array]:
var arr1 = [1, 2, 3],
    arr2 = new Array(1, 2, 3);

Array.isArray(arr1); // true
Array.isArray(arr2); // true
Array.isArray(new Number('12')); // false
Array.isArray({}); // false

Sources:
typeof operator (MDC)
constructor property (MDC)

July 9, 2011

The this keyword in JavaScript

What does this mean?
Short answer, it depends.

In general terms, you could say that the this keyword, in JavaScript, refers to the function's context, how it's invoked. There are four ways in which a function can be invoked: method form, function form, constructor form and apply form.

Method form
If a function is invoked in the method form, this will be bound to the object that owns the function:
var myObj = {
    name: 'Chuck',
    sayName: function () {
        return 'Hello ' + this.name;
    }
};

myObj.sayName(); // this refers to myObj, returns 'Hello Chuck'

Function form
This means taking a function value and calling it. In ECMAScript 3, this would be bound to the global object:
var myFunc = function () {
    return this.location.href;
};

myFunc(); // this refers to window (in browsers), returns the current URL
In ECMAScript 5 Strict Mode (almost fully supported), this was changed so that it would refer to undefined instead:
var myFunc = function () {
    'use strict';
    return this.location.href;
};

myFunc(); // error, this is undefined

Constructor form
A constructor is meant to be called using the new keyword, in this form, this will refer to the object being produced:
var MyConstructor = function (name) {
    this.name = name;
    this.sayName = function () {
        return 'Hello ' + this.name;
    };
};

var myObj = new MyConstructor('Chuck');
myObj.name; // 'Chuck'
myObj.sayName(); // 'Hello Chuck'

Apply form
Functions are objects and, as most objects, they have methods. This form refers to using the function's methods apply() or call() to call a function. Both methods take whatever first argument you pass and bound this to it. They differ in the way they provide the arguments to the function being invoked.
The apply() method takes an array of arguments:
var myObj1 = {
    name: 'Chuck'
};
var myObj2 = {
    name: 'Charles'
};
var sayWelcome = function (location) {
    return 'Hello ' + this.name + '. Welcome to ' + location;
};

sayWelcome.apply(myObj1, ['New York']); // 'Hello Chuck. Welcome to New York'
sayWelcome.apply(myObj2, ['Toronto']); // 'Hello Charles. Welcome to Toronto'
The call() method takes a list of arguments separated by commas:
sayWelcome.call(myObj1, 'New York'); // 'Hello Chuck. Welcome to New York'
sayWelcome.call(myObj2, 'Toronto'); // 'Hello Charles. Welcome to Toronto'

Summary
The value of this depends on the calling form:
  • Method form: it's bound to the object containing the method.
  • Function form: it's bound to either the global object (ECMAScript 3), or to undefined in (ECMAScript 5 Strict Mode).
  • Constructor form: it's bound to the new object being constructed.
  • Apply form: it's bound to the first argument passed to apply() or call().

Source:
Crockford on JavaScript - Act III: Function the Ultimate