Thursday, February 12, 2009

After the Array, subclassed String

As I wrote months ago in this documentation about JavaScript prototypal inheritance, subclass native data type is not that simple (almost not possible at all) but since JS is loads of weird bits and bops, I often try to break its documented limits.

It was the Array, some time ago, now it is about time for String.

The concept is similar, if you subclass a native data type its methods will return that native data type like instance, so concat, charAt, toString, etc etc, will return a String unless we do not override every inherited method (so performances wont be that good).

Subclassed String



(function(Function, slice, push){
// from WebReflection: Subclassed String
function String(String){
if(arguments.length) // clever constructor, accepts more than a string as argument
push.apply(this, slice.call(arguments).join("").split(""));
};
String.prototype = new Function;
try{
(new String) + ""; // exception in FireFox
var join = Array.prototype.join;
String.prototype.toString = String.prototype.valueOf = function(){
return join.call(this, "");
};
}catch(e){ // no way to retrieve the length with FireFox
String.prototype.toString = String.prototype.valueOf = function(){
for(var Array = [], i = 0; Array[i] = this[i]; i++);
return Array.join("");
};
};
(window.$ || ($ = {})).String = String; // let's put this in a namespace
})(String, Array.prototype.slice, Array.prototype.push);

That's it, every String.prototype.method should act as it has been called via native String with probably every browser.

Performances


concat, charAt, charCodeAt, toLowerCase, etc, etc will be almost the same of native strings but the constructor will be a bit slower (instances instead of regular "strings").
On the other hand, in every browser the returned instance will be an Array like String, so ...

new $.String("Here", " ", "we", " ", "are!")[0];

will be exactly the char "H" in every compatible browser.

Alternatives?


To create a valid alternative that will NOT be an instanceof String we could use the same Stack trick:

// every browser
(function(Function, join, push){
function String(String){
if(arguments.length)
push.apply(this, slice.call(arguments).join("").split(""));
};
String.prototype.length = 0;
String.prototype.valueOf = String.prototype.toString = function(){
return join.call(this, "");
};
// here we can add every String prototype to the $.String prototype
(window.$ || ($ = {})).String = String;
})(String, Array.prototype.join, Array.prototype.slice, Array.prototype.push);


Have fun ;-)

Tuesday, February 10, 2009

About isNative, is wasting time?

kangax as reply to Diego comment, then Ajaxian as desert with my comments, but three of us completely in the wrong direction.

Why isNative?


As I though and as Diego Perini confirmed, sometimes we would like to trust the environment itself before we act in a certain way.
I wrote about an XMLHttpRequest wrapper for Internet Explorer, but cases are numerous, like a "not compliant" implementation of the Array.prototype.forEach, or others proto, or some injection problems about window.Array, window.String, or who knows what else.
Accordingly, since the JavaScript nature is dynamic, widely dynamic, we would like to be sure that a method is as "specs says" rather than how this or that library implemented it.

Can we trust an unreliable environment?


The first error we all committed was to find the "best way ever" to understand if a function/method has been changed, without considering the fact that the function isNative itself could be a fake!
No matter how good is our way to discover the native code because isNative(this, "isNative"); will ironically return false every time.
A way to partially solve the problem is the usage of const but this keyword makes sense only for FireFox and Safari, so Opera and even worse case Internet Explorer will not be able to create an immutable function.

/*@cc_on var // @*/ const
isNative = function(){
// whatever You need
};

Above snippet, described in one of my old posts, will let us create an immutable function only for certain browsers, but the isNative problem, as I said, will persist for IE, Opera, and probably other browsers as well.

About proposed solutions, plus mine


Both kangax and Diego have written a couple of functions, the first one created a sandbox via iframe while the second one a string sniff.
Both solutions are not that perfect because of iframes or because a function toString method coul be changed arbitrary, since functions are objects as well.
My solution has probably some side effect as well, but unless smebody will be able to demonstrate that it could be hacked, which was for Diego case, I will consider my proposal the most reliable without iframes.

function isNative(object, method){
var r = !!object && !/undefined|string/.test(typeof object[method]);
if(r){
var toString = (method = object[method]).toString;
try{ // necessary for native functions (alert, as example)
delete method.toString;
r = method.toString !== Object.prototype.toString && /\[.+?\]/.test(method);
if(method.toString != toString)
method.toString = toString;
}catch(e){
r = true;
}
};
return r;
};

Come on, break it and demonstrate that as I said, JavaScript is 100% unreliable programming language ;-)

P.S. this discussion remind me those days me and kentaromiura were trying to find a way to solve eval injections, we gave up after a week of tries hacking our code by our own :D

Monday, February 9, 2009

JavaScript, Bear in Mind DOM Methods

We all like the jQuery code style where the instance is returned almost everywhere, but we often forget about DOM methods that do the same and ages before whatever library. I rarely spot some "returned instance" trick in daily posts or even famous libraries.
Here a couple of example:

// daily way ...
var div = document.createElement("div");
document.body.appendChild(div);

// easy way
var div = document.body.appendChild(document.createElement("div"));


// boring way
var div = document.createElement("div");
div.appendChild(document.createTextNode("test"));
document.body.appendChild(div);

// funny way
var div = document.body.appendChild(
document.createElement("div")
).appendChild(
document.createTextNode("test")
).parentNode
;

... on and on, there are several methods used on daily basis that do not consider returned values. So, why don't we like to use them?

var el = document.createElement("div"),
div = document.createElement("div");
alert([
el === document.documentElement.appendChild(el), // el in the DOM
el === el.parentNode.removeChild(el), // el removed
div === document.documentElement.appendChild(div), // div in the DOM
div === div.parentNode.replaceChild(el, div), // div removed, el in the DOM
div === el.parentNode.insertBefore(div, el) // both in the DOM, div returned
].join("\n"));

As summary, try to find a leak possibility with this:

with((document.body || document.documentElement)
.appendChild(
document.createElement("div")
)
.appendChild(
document.createTextNode("not")
)) parentNode
.insertBefore(
document.createTextNode("Why "),
parentNode.firstChild
)
.parentNode
.appendChild(
document.createTextNode(" ?")
)
;

Not even a single variable declared, three elements inserted in a specific node of the DOM and in a different order :D