2012 • SoundCloud API client • the long running app • ~45,000 LOC, 80% of that is JS • supports only modern browsers • better experience Wednesday, October 9, 13
depend on third-party libraries • helpful methods for data manipulation • easy to understand and extend • but not opinionated about app structure Wednesday, October 9, 13
a standard view declaration • rendering of templates • establish a data source • support the nested views • setup and teardown event listeners • don’t depend on the context Wednesday, October 9, 13
require('views/waveform'); var waveform = new Waveform({ resource_id: 123 }); soundView.addSubview(waveform); soundView.$el.append(waveform.render().el); You can add subviews both in a template and in js file Wednesday, October 9, 13
= require('lib/view'); var WaveformView = module.exports = View.extend({ template: require('views/sound/waveform.tmpl'), css: require('views/sound/waveform.css'), ModelClass: Sound, initialize: function(args) { // new instance is created for each view this.model = new this.ModelClass({ id: args.id }); this.setupModelListeners(); } }); Wednesday, October 9, 13
{}; Sound = function(attributes.id) { var id = attributes.id; // check if this model has already been created if (store[id]) { return store[id]; // ← return the other one } // otherwise, store this instance store[id] = this; // regular instantiation... this.initialize(); } }; Wednesday, October 9, 13
function(attrs) { var id = attributes.id; // check if this model has already been created if (store[id]) { return store[id]; } // otherwise, store this instance store[id] = this; // ← it’s a massive a memory leak // regular instantiation... this.initialize(); } }; Wednesday, October 9, 13
= {}, B = {}; A.b = B; B.a = A; } // Circular reference does exist // but A and B will be cleaned up // as you can't reach either from global scope leak(); Wednesday, October 9, 13
= require('lib/view'), Sound = require('models/sound'); var WaveformView = module.exports = View.extend({ setup: function() { this.sound = new SoundModel({ id: 123 }); }, // sound model is not disposed properly later on // as we mistakenly redefined `dispose` method dispose: function() {} }); Wednesday, October 9, 13
Warnings: 1 Sound model is not disposed. Release it in `dispose` method. A very nice read - http://research.google.com/pubs/pub40738.html Global leaking detector - https://github.com/kesla/node-leaky Wednesday, October 9, 13
heap is limited to jsHeapSizeLimit : 793000000, // memory allocated by JS (including free space) usedJSHeapSize : 18200000, // memory currently being used totalJSHeapSize : 39600000 } setInterval(function() { if (usedJSHeapSize/jsHeapSizeLimit > 0.9) { console.log(‘He is almost dead, Jim!’); } }, 30 * 1000); Wednesday, October 9, 13
// introduce all properties in the constructor // to enable “fast-object” mode. this.x = x; this.y = y; this.z = z; } var point = new Point(11, 22, 33); point 11 22 33 Point ‘x’ ‘y’ Point Point ‘x’ ‘y’ ‘z’ Point ‘x’ x y z Wednesday, October 9, 13
22, 33); // forcing the object into dictionary mode delete point.z; // accessing a hash table is much slower // than accessing a field at a known offset. console.log(point.x); point 11 22 HashMap x : 11 y : 22 Wednesday, October 9, 13
{ this.title = title; this.duration = duration; } var SlowSound = function (title, duration) { this.title = title; this.duration = duration; }; var fast = new FastSound('Hommage a Rameau', '350000'); var slow = new SlowSound('Hommage a Rameau', '350000'); delete slow.duration; Wednesday, October 9, 13
size -------------------------------------------------------- SlowSound 10000 120 000 4 240 000 FastSound 10000 200 000 200 000 Slow object is using up 20 times more memory Wednesday, October 9, 13