Messing Around in JS Object - Prevent Extensions

Lately I've been messing around with Javascript Object in Javascript 1.8.5 or greater. It's interesting but I've come across some peculiarities.

Object.preventExtensions tests

function d(){ this._test = 5; } //Object.preventExtensions(d.prototype); if (Object.isExtensible(d.prototype)){ Object.defineProperty(d.prototype, "test", { get: function(){ console.log("get called"); return this._test; }, enumerable: false, configurable: true } ); } var s = new d(); Object.preventExtensions(s); s.test = 6; console.log(s.test); s.jason = "jason"; console.log(s.jason);

Calling preventExtensions on d.prototype, the "test" property is never defined with the getter, so console.log(s.test) won't also log "get called". Object.preventExtensions(d.prototype) does not prevent extensions on instances of d. This one was weird to me, but they are two separate objects, so creating the property "jason" on "s" works unless I prevent extensions on "s". When preventExtensions is called on "s", console.log(s.test) prints 5 and console.log(s.jason) does not error out although it raises an error if I try to extend the prototype when it is not extensible.

Just some observations... This is the first in a series of posts on Object and its new methods in 1.8.5, I feel I need to know everything about it

I missed something

When rebuilding my site in Node.js, I forgot to include the Google Analytics code, and wondered why I was getting no visitors, felt bad about myself, etc. I've resolved the issue as of last week.

2100% increase in visitors over the past 4-5 days

Amazingly, a hit registered last week at some point, before resolving it. I have no idea how.

Updated JS Combiner with JSMin!!

I updated my JS combiner functionality in the web server with JSMin! I found a Node.js JSMin implementation, added the line of code that minifies my combined file, and writes that out as the file to download. The one instance combined three big jQuery libraries that are used on almost every page, and it went from 37KB to 26KB. Before it was comparable in size to 37KB (probably identical) but it was three calls to the web server. This should limit the calls a ton.

Synchronized Array Access in Node.JS

I couldn't think of a good title for this one, but here's what I needed to accomplish: To loop over an array, but not moving onto the next element until finished with the current one. This is simple in synchronous, blocking code, but once you get to the world of Node.js and non-blocking calls going on everywhere, it becomes wildly more difficult. One could add the word "Semaphore" to this post and it wouldn't be too off the wall.

Take the following code for example:

var array = ["www.google.com", "www.yahoo.com", "www.microsoft.com", "www.jasontconnell.com", "givit.me"]; array.forEach(function(d, i){ http.get({ host: d, port: 80, path: "/" }, function(res){ console.log("index " + i + " got the response for " + d); }); });

Add in the appropriate "require" statements, run the code, and this is what I got the first time I ran it:

index = 0 - got response for www.google.com index = 3 - got response for givit.me index = 2 - got response for www.microsoft.com index = 4 - got response for www.jasontconnell.com index = 1 - got response for www.yahoo.com

That is not a predictable order! So how do we fix this? EventEmitter!! This took me a surprisingly small amount of time to figure out. Here's the code:

var http = require("http"); var EventEmitter = require("events").EventEmitter; var sys = require("sys"); function SyncArray(array){ this.array = array }; require("util").inherits(SyncArray, EventEmitter) SyncArray.prototype.forEach = function(callback, finishedCallback){ var self = this; this.on("nextElement", function(index, callback, finishedCallback){ self.next(++index, callback, finishedCallback); }); this.on("finished", function(){ }); self.next(0, callback, finishedCallback); } SyncArray.prototype.next = function(index, callback, finishedCallback){ var self = this; var obj = index < self.array.length ? self.array[index] : null; if (obj){ callback(obj, index, self.array, function(){ self.emit("nextElement", index, callback, finishedCallback); }); } else { finishedCallback(); self.emit("finished"); } } var array = ["www.google.com","www.yahoo.com", "www.microsoft.com", "givit.me", "www.jasontconnell.com"]; var sync = new SyncArray(array); sync.forEach(function(d, i, array, finishedOne){ http.get({ host: d, port: 80, path: "/" }, function(res){ console.log("index = " + i + " - got response from " + d ); finishedOne(); }); }, function(){ console.log("finished the sync array foreach loop"); });

And the output as we expected:

index = 0 - got response from www.google.com index = 1 - got response from www.yahoo.com index = 2 - got response from www.microsoft.com index = 3 - got response from givit.me index = 4 - got response from www.jasontconnell.com finished the sync array foreach loop

Feel free to update this with best practices, better code, better way to do it in Node.js (perhaps built in?), etc, in the comments. I'm really excited about this since holy F#@$ that's been frustrating!

Node.js Javascript Combining

The practice of combining and minifying JS files is one that should be done at all times. There's two ways to do this, one involves creating the combined file manually. The other involves fun and less future work.

I write my JS into many different files, breaking down functionality, but the client doesn't need to see that.

On my web server, I have a server side tag library that allows me to do stuff like include other files (so I can break up development into components), run "foreach" statements over arrays, conditional processing, and other stuff. I've added to this arsenal with a "combine" tag.

It's used like this:

<jsn:combine> /js/file1.js /js/file2.js /js/file3.js </jsn:combine>

These files get combined and added to the /js/combine/ folder, and the script tag gets written out in the response. This checks each time if one of the js files has been updated since the combined javascript file has been written.

There would be way too much code to spit out to show how all of the tags and the server works, so I'll just show the code that does the combine

var combineRegex = /(.*?\.js)\n/g; var rootdir = site.path + site.contentRoot; var combineFolder = "/js/combine/"; var file = combineFolder + jsnContext.localScope + ".js"; var writeTag = function(jsnContext, file){ var scriptTag = "<script type=\"text/javascript\" src=\"" + file + "\"</script>"; jsnContext.write(scriptTag); callback(jsnContext); } var combineStats = filesystem.fileStats(rootdir + file); var writeFile = combineStats == null; var altroot = null; if (site.config.isMobile && site.main != null){ altroot = site.path + site.main.contentRoot; } var paths = []; while ((match = combineRegex.exec(files)) != null){ var localPath = match[1].trim(); var fullPath = rootdir + localPath; var origStats = filesystem.fileStats(fullPath); var altStats = filesystem.fileStats(altroot+localPath); // add paths and determine if we should overwrite / create the combined file if (origStats != null){ paths.push(fullPath); writeFile = writeFile || combineStats.mtime < origStats.mtime; } else if (altroot != null && altStats != null){ paths.push(altroot+localPath); writeFile = writeFile || combineStats.mtime < altStats.mtime; } } writeTag(jsnContext, file); if (writeFile){ filesystem.ensureFolder(rootdir + combineFolder, function(success){ if (success){ filesystem.readFiles(paths, function(content){ filesystem.writeFile(rootdir+file, content, function(success){ }); }); } }); }

Some other stuff going on... my server has built in mobile site support. You can specify "this is the mobile site for this full site" and "for this mobile site, this is the main site", both running independent but they know of each other. I can have the file /js/mobile-only.js combined on the mobile site, where that file exists in the mobile site's content root, and I can have the file /js/main-site-only.js combined on mobile, where that file only exists on the main site.

Some nice to haves would be to also minify the files on the fly.