elijahr

notes on javascript, linux, and more

11.11.2009

typeof(null) and Pointers in JavaScript

I had a bit of an epiphany this morning when I was trying to figure out why typeof(null) === 'object' yields true. If you have some value where typeof(value) === "object" and you assign that value to a variable, that variable becomes a reference to that value. If you have some value where typeof(value) === "string" || typeof(value) === "number" || typeof(value) === "boolean", and you assign that value to a variable, that variable now contains a copy of that value rather than a reference. But sometimes, typeof(my_string_variable) === 'object', and assigning that string variable to another variable will create a reference to, not a copy of, that variable's value. Why is that, and why is typeof(null) === 'object' true?

First let's run through some examples to illustrate what I'm talking about.


var
a = 'a',
b = a

a.foo = 'bar'
alert(b.foo) // alerts 'undefined'

a = new Object()
b = a

a.foo = 'bar'
alert(b.foo) // alerts 'bar'


What if I want to create a pointer to a string, a number, or a boolean?


alert(typeof('a')) // alerts 'string'
alert(typeof(true)) // alerts 'boolean'
alert(typeof(1)) // alerts 'number'


So, assigning those values will result in a copy, not a reference. But:


alert(typeof(new String('a'))) // alerts 'object'
alert(typeof(new Boolean(true))) // alerts 'object'
alert(typeof(new Number(1))) // alerts 'object'


What does this mean? It means you can create pointers to JavaScript primitives. This answers my first question. If typeof(value) === 'object' but value is a number, a string, or a boolean value, then you've got yourself a pointer to an object. If you want to assign your object's value to a variable, sans pointer, just use value.valueOf().

You can also check whether some value is a pointer or primitive using instanceof:


alert('a' instanceof String) // alerts false
alert(true instanceof Boolean) // alerts false
alert(1 instanceof Number) // alerts false
alert(new String('a') instanceof String) // alerts true
alert(new Boolean(true) instanceof Boolean) // alerts true
alert(new Number(1) instanceof Number) // alerts true


The below function makes it easy to point to primitive values.


function point(value) { // return a pointer to some primitive value
switch( typeof(value) ) {
case 'string': case 'number': case 'boolean':
return new value.constructor(value)
}
return value
}


Use it like this:


var
my_string = point('a'),
my_string_alias = my_string

my_string.foo = 'bar'
alert(my_string_alias.foo) // alerts 'bar'


It should be noted, that if you pass a variable containing a primitive into point, you will not get a pointer to that primitive; a new object will be created with the value you passed in. Here's an example:


var
my_string = 'a',
my_string_alias = point(my_string)

my_string_alias.foo = 'bar'

alert(my_string.foo) // alerts 'undefined'


One way that this can be resolved is by a simple reassignment of the original variable to the new object:


var
my_string = 'a',
my_string_alias = my_string = point(my_string)

my_string_alias.foo = 'bar'

alert(my_string.foo) // alerts 'bar'


Ok, so now that that has been cleared up, what about my second question: why is typeof(null) === 'object' true? The only thing I can think is that null is a pointer, not a primitive value, and that when typeof returns 'object', it means that the passed in value is a pointer, not a primitive value. Perhaps when I have some time I'll download the TraceMonkey source and grep through the logic in typeof to verify this, but its the best explanation I've found thus far; many JavaScripters seem to consider typeof broken because it returns "object" when a null value is passed in. What's the use all of this information? Well, one use is that we can use this knowledge to check if some value is iterable:


function is_iterable ( value ) {
return value !== null && typeof( value ) === 'object'
}


If is_iterable returns true for some variable or value that you pass in, you are assured that for (var x in value) loops and x in value statements on that value will execute without throwing a TypeError.

I'm sure more knowledgeable JavaScripters than I might take all of this information for granted, but I was unable to find any informed resources on this particular subject in my googlings. I hope that you've found it useful.

5.06.2009

Multiple Class Inheritance with Prototypes in Javascript

Here is a simple example to demonstrate multiple class inheritance in Javascript. Let's say we are trying to describe a dog named Lily who is is a pit bull & black labrador mix. We can do this by combining a Dog class, a PitBull class, and a BlackLabrador class into a PitLabMix class. Prototyping splits the constructor from the class template, which allows us to inherit from several classes without having to instantiate any of them. In the end, we have an instance of the PitLabMix class named 'lily', which has all the properties of Dog, PitBull, and BlackLabrador. The script makes use of the window.console.log function in Firebug to demonstrate what the final object looks like. For non-Firefox browsers, there is also Firebug Lite.

function Dog (name) {
this.name = name;
}

Dog.prototype = {
kingdom : "Animalia",
subkingdom : "Eumetazoa",
phylum : "Chordata",
subphylum : "Vertebrata",
class : "Mammalia",
subclass : "Theria",
order : "Carnivora",
suborder : "Caniformia",
family : "Canidae",
subfamily : "Caninae",
tribe : "Canini",
genus : "Canis",
species : "C. lupus",
subspecies : "C. l. familiaris"
}

function PitBull (name) {
this.name = name;
for (var x in Dog.prototype)
this[x] = Dog.prototype[x];
}

PitBull.prototype = {
build : "Muscular",
demeanor : "Loyal"
}

function BlackLabrador (name) {
this.name = name;
for (var x in Dog.prototype)
this[x] = Dog.prototype[x];
}

BlackLabrador.prototype = {
"fur color" : "Black"
}

function PitLabMix (name) {
this.name = name;
for (var x in Dog.prototype)
this[x] = Dog.prototype[x];
for (var x in PitBull.prototype)
this[x] = PitBull.prototype[x];
for (var x in BlackLabrador.prototype)
this[x] = BlackLabrador.prototype[x];
}

var lily = new PitLabMix('Lily');
window.console.log(lily);

4.13.2009

A simple general-purpose Lexer written in JavaScript

If you need to tokenize a string, this code might come in handy. Just modify the regular expressions in 'definitions' to suit your needs.

/*
Copyright (c) 2009 Elijah Rutschman <elijahr@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

var definitions = {
identifier : { regexp : /^[a-zA-Z]+[a-zA-Z0-9]*$/, skip : false },
string : { regexp : /^\"[^\"]*\"|\'[^\']*\'$/, skip : false },
decimal_integer : { regexp : /^[1-9]*\d+$/, skip : false },
left_parenthesis : { regexp : /^\($/, skip : false },
right_parenthesis : { regexp : /^\)$/, skip : false },
comma : { regexp : /^,$/, skip : false },
whitespace : { regexp : /^[\t \n]$/, skip : true }
}

var Lexer = function ( string ) {
var index = 0;
var length = 1;
var arrayPosition = -1;
var _Lexer = this;
_Lexer.tokens = [];
_Lexer.lexemes = [];
_Lexer.token = null;
_Lexer.lexeme = null;
_Lexer.bof = true;
_Lexer.eof = false;
_Lexer.line_number = 0;
_Lexer.char_position = 0;

// fill tokens and lexemes
while ( index + length <= string.length ) {
var small = string.substr( index, length ) ;
var big = string.substr( index, length + 1 ) ;
for ( var def in definitions ) {
var smallmatch = small.match( definitions[def].regexp ) !== null;
var bigmatch = big.match( definitions[def].regexp ) !== null;
if ( smallmatch &&( ! bigmatch || small == big ) ) {
// found a token
index += length;
length = 0;
_Lexer.tokens.push(def);
_Lexer.lexemes.push(small);
break;
}
}
length++;
}

_Lexer.next = function (count) {
if (count == undefined) count = 1;
while (count-- > 0) {
arrayPosition++;
if ( arrayPosition < _Lexer.tokens.length) {
_Lexer.token = _Lexer.tokens[arrayPosition];
_Lexer.lexeme = _Lexer.lexemes[arrayPosition];
_Lexer.bof = false;
_Lexer.char_position += _Lexer.lexeme.length;
if (definitions[_Lexer.token].skip) {
if (_Lexer.token == 'newline') {
_Lexer.line_number++;
_Lexer.char_position = 0;
}
_Lexer.next();
}
} else {
_Lexer.token = 'EOF';
_Lexer.lexeme = null;
_Lexer.eof = true;
}
}
}

_Lexer.prev = function (count) {
if (count == undefined) count = 1;
while (count-- > 0) {
arrayPosition--;
if ( arrayPosition-- > 0) {
_Lexer.token = _Lexer.tokens[arrayPosition];
_Lexer.lexeme = _Lexer.lexemes[arrayPosition];
_Lexer.eof = false;
_Lexer.char_position -= _Lexer.lexeme.length;
if (definitions[_Lexer.token].skip) {
if (_Lexer.token == 'newline') {
_Lexer.line_number--;
_Lexer.char_position = 0;
}
_Lexer.prev();
}
} else {
_Lexer.token = 'BOF';
_Lexer.lexeme = null;
_Lexer.bof = true;
break;
}
}
}
}

var lexer = new Lexer("hello('world', 2)");
while (! lexer.eof ) {
lexer.next()
document.write( lexer.token + ' ' );
}

This will output "identifier left_parenthesis string comma decimal_integer right_parenthesis EOF" to the page.

4.06.2009

Mirror - A Two-sided Dictionary for Javascript

I was playing around and came up with this class for "two-sided" dictionaries, meaning that there is no differentiation between keys and values.


var Mirror = function () {
this.couples = [];
};
Mirror.prototype.place = function (x, y) {
if (x == undefined) x = null;
if (y == undefined) y = null;

if (this.reflect(x) != undefined)
this.dissolve(x);
if (this.reflect(y) != undefined)
this.dissolve(y);

this.couples.push([x,y]);
};
Mirror.prototype.dissolve = function(key) {
for (var i = 0; i<this.couples.length; i++ ) {
for (var j = 0; j<2; j++ ) {
if (this.couples[i][j] === key) {
this.couples.splice(i,1);
}
}
}
};
Mirror.prototype.reflect = function (key) {
for (var i = 0; i<this.couples.length; i++ ) {
for (var j = 0; j<2; j++ ) {
if (this.couples[i][j] === key) {
return this.couples[i][j ^ 1];
}
}
}
};

var mirror = new Mirror();
// place items in the Mirror
mirror.place("johnny", "mark");
mirror.place("sammy", "sarah");
mirror.place("andrew", "engelbert");
mirror.place("yasha", "katrina");

// get the 'mate' for the key you pass in
mirror.reflect("katrina"); // yasha
mirror.reflect("engelbert"); // andrew
mirror.reflect("sammy"); // sarah

// uh oh! scandal
mirror.place("engelbert", "sarah");

// where did they go?
mirror.reflect("sammy"); // undefined, he's out of the picture.
mirror.reflect("andrew"); // undefined, he's out of the picture.

mirror.reflect("engelbert"); // sarah
mirror.reflect("sarah"); // engelbert

About Me

My Photo
chicago, il, United States
I'm a software engineer by profession.

Labels