My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.

Saturday, March 29, 2014

What Books Didn't Tell You About ES5 Descriptors - Part 4

I've left Part 3 feeling guilty about the impression developers have on modern JavaScript, and this comment confirmed this feeling:
Peter StJ
I have read all 3 parts and to tell you the truth it does look bad.
... just looking at that code you have used as example gives headache to everyone I show it to, including JS developers ...
I think Peter is absolutely right, what I've shown 'till now is an explanation of the modern pattern, enriched with all possible problems that this could have with not so outdated browsers ... where, this part is the catch: I gonna propose you a simple way to forget about all these problems and write meaningful code that looks beautiful and dose not need explanation while you read it, as well as telling you even more about how screwed has been in history ECMAScript 5.x, and why now is the right moment to adopt it!

In other words, metaphorically speaking, if anyone would ever tell you what jQuery is actually trying to fix, behind the scene, you'll feel dizzy, hoping nobody had to inform you about how bad is the web development these days!
Regardless, this is why I've chosen such topic for these posts, where the purpose is to enlight, if possible, and warn you it ain't gonna be any better any time soon, cross platform and engine speaking ... so you better understand what's going on ;-)

The Internet Explorer Hysterical ES5's Adoption

In Part 1, I kinda blamed @felixge for some avoided adoptions, specially from node.js developers ... the truth is, that all this new JavaScript stuff has been reluctant for Desktop browsers developers too!
"will it work in IE?" has been probably the most boring, frustrating, and at the same time honest question, and Desktop web developer would ask you ... and the answer has always been kinda, but such word has rarely been considered in softare development ... so here a quick rewind of IE history

Getters & Setters, Since Ever!

Microsoft Developers related pages are great these days, but not so informative 8 years ago. What we missed since IE6 time, is that IE6 had Getters and Setters too but we had to use VBScript in order to obtain such functionality ... in JScript world ... you think that's screwed? Think twice, Dart language these days is offering different behaviors available in JavaScript ... yeah, Dart language is what VBScript has represented ages ago ... before it was cool, IMO!
In any case, the moment I've realized we could have getters and setters in IE too, was already too late, people never used them because at that time Desktop web development was "everything needed in JS world".

Broken ES5 in IE8

When Internet Explorer 8 came out, it came out non standard, supporting only the DOM world, and in a not fully ES5 specd way.
Thanks gosh that was enough to be able to write polyfills like DOM4 so that at least modern HTML5 development, when IE8 support is necessary, even if it current version of IE is 11, is possible without compromising our code style, most modern features, easy custom event dispatching, and everything else that was not present at that IE8 time.
Good part is: IE8 had native JSON object, so that the heavy JavaScript polyfill can be finally dropped, and CSS capabilities were not bad neither plus it still supports conditional HTML or CSS comments! Yaiiii, no other browser will ever be compromised!

Extremely Broken IE9 on Windows Phone 7.X

While version 9 of Internet Explorer finally adopted officially ECMAScript instead of its own JScript pseudo standard, the worst part of this IE 9 story is the fragmentation between Desktop 9, and Mobile 9.
While someone could think and rightly expect these are the same product, there are bugs here and there that differs from Desktop to Mobile in inimaginable ways ...
(function(){'use strict'; // only in strict !!!

  var i = 0; // surprise later on ...

  function Getter() {}
  Object.defineProperty(
    Getter.prototype,
    'fuq',
    {
      configurable: true,
      get: function () {
        // let's get rid of the inherited property
        // and define another one as allowed in ES5
        Object.defineProperty(
          // in IE9 MObile, if it's not a function,
          // it might throw!!!
          this, 'fuq', {value: Math.random}
        );
        // since the property is now ownProperty
        // next time it is accessed will be already there
        // ... right ?
        ++i;
        // WRONG, it will be re-invoked next line
        return this.fuq; // will trigger recursion
                         // unless filtered!
      }
    }
  );

  var da = new Getter;
  alert([
    da.fuq === da.fuq, // false
    i                  // 2 in IE9 Mobile
  ].join('\n'));
}());
Everything that could possibly go wrong in above snippet went wrong, and you can see with your eyes if you have such phone.
First of all, the behavior is not reproducible without use strict when hilariously, IE9 was not even supposed to respect use strict directive.
Secondly, the recursion will kick any of your code/logic badly because while setting the new property, IE9 still triggers the inherited get descriptor.
You don't return this.prop and just the value? That throws!
The only way to prevent recursion is to actually check before setting the descriptor is !this.hasOwnProperty('propertyName') otherwise the app will be basically doomed.
How freaking awesome is all this?

Finally IE10

At least there is an happy ending, IE10 Desktop and Mobile is fully compliant with ES 5.1 specifications so eventually, after 16 years, we have a very good and standard browser working in MS platform.

Not Only IE

The amount of inconsistent behaviors across browsers and platforms when it comes to ES5.1 standard is embarrassing, right today I've filed 3 push request to sanitize Opera Mini with es5-shim project, because of some extra weirdness behind this browser that has only Object.defineProperty from ES5 but it can work with some little effort.

Good news is, I've solved all these problems for you today ;-)

prototypal

I've realized myself the previously discussed Class snippet was way too close to the metal and not good looking for most common cases or patterns, so here I am with a revisited and fully tested, covered, and cross browser and platform version of that Class via the prototypal package, already in npm and bower.
Here an example of how it works:
var Rectangle = Class({
  constructor: function (width, height) {
    this.width = width;
    this.height = height;
  },
  toString: function () {
    return '[object Rectangle]';
  },

  // default properties
  width: 0,
  height: 0,

  // an explicit getter to retrieve the area
  area: Class.descriptor({
    get: function () {
      return this.width * this.height;
    }
  })
});

// extending
var Square = Class( Rectangle, {
  constructor: function (size) {
    Rectangle.call(this, size, size);
  },
  toString: function () {
    return '[object Square]';
  }
});

var s = new Square(3);
s.area; // 9
'' + s; // [object Square]
As extra useful features the lazy property definition patter has been solved in core and tested again everywhere plus another bound feature based internally of the lazy pattern:
var MouseHandler = Class({
  onClick: Class.bound(function (evt) {
    evt.preventDefault();
    alert(this instanceof MouseHandler); // true
  })
});

var mh = new MouseHandler;
document.body.addEventListener(
  'click', mh.onClick
);
Defined via Class.bound utlity, every instance of MouseHandler will have a bound method called onClick but only when accessed instead of at runtime during instance creation.
I think architectures possibilities with these few simple utilities that are fully tested are many, but if there's any very pecific pattern you think would be handy to put in prototypal.Clas package, this is the right time to speak ;-)
Last, but not least, I can humbly say I've done many other attempts in my past to find a good, simple, performant, and as reasonably cross browser/platform as possible without too many surprising behaviors while working on some Classical Inheritance like project and this prototypal feels and look the best, and most likely the last, attempt I could made to date, and it fits in around 700 bytes minzipped, nothing at all considering how much it resolve consistently cross platform.

A Typed Experiment

The old redefine.js and the new born protoypal are successful attempt to dominate ES5 descriptors with ease.
However, once we've mastered how these works, and even on top of any of these utilities, we could find descriptors as the most natural place, ECMAScript speaking, to described properties type or method argumsnt and returned type, maybe enabling overloads and multiple returns check, and in a completely zero performance impact on production code.
Well, defineStrictProperties aim is to enrich native methods in an unobtrusive way bringing types in JavaScript in a way that integrates properly with current specifications, is backward compatible, and it could be used after es5-shim, if necessary, with zero code size and performance problem once in production: you just don't include the file and you are good to go!
The undesired effect? Well, if combined with prototypal.Class, when types are needed/meant, properties should be described via Class.descriptor({ .. object .. }) ... maybe not such a big deal?

// just as shortcut
var as = Class.descriptor;


var Rectangle = Class({
  constructor: function (width, height) {
    this.width = width;
    this.height = height;
  },
  toString: function () {
    return '[object Rectangle]';
  },

  // default typed properties
  width:  as({type: 'number',
              value: 0}),
  height: as({type: 'number',
              value: 0}),

  // a getter
  area: as({type: 'number',
            get: function () {
              return this.width *
                     this.height;
            }
          })

});
Well, as I've said this is just en experiment that has nothing to do with prototypal but it could be easily integrated in your code without effort, since all types are completely optional.

The End

I am not sure if it was worth writing all this about descriptors, but I feel relieved now that everything you could say about ES5 and 5.1 descriptors is described in one of these 4 posts ... of course, unless I've forgotten something, which if the case, please let me know, and I'll update!

Thanks for your patience reading all this!

1 comment:

Anonymous said...

No patience required, these were all very interesting, and an unusual viewpoint of the state of web development over the last decade or so :-)