├── .babelrc ├── .travis.yml ├── .gitignore ├── ts ├── tsconfig.json └── index.ts ├── package.json ├── es6 └── index.js ├── vanilla └── index.js ├── vanilla-promote └── index.js ├── README.md └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 6 4 | script: 'npm start' 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | es6/index-babel.js 3 | es6/index-buble.js 4 | ts/index.js 5 | ts/index.d.ts 6 | -------------------------------------------------------------------------------- /ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es5", 5 | "noImplicitAny": false, 6 | "sourceMap": false 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /ts/index.ts: -------------------------------------------------------------------------------- 1 | export class Level0 { 2 | protected num: number; 3 | 4 | constructor () { 5 | this.num = 0; 6 | } 7 | 8 | method () { 9 | this.num += 1; 10 | } 11 | } 12 | 13 | export class Level1 extends Level0 { 14 | constructor () { 15 | super(); 16 | this.num += 1; 17 | } 18 | 19 | method () { 20 | super.method(); 21 | this.num += 1; 22 | } 23 | } 24 | 25 | export class Level2 extends Level1 { 26 | constructor () { 27 | super(); 28 | this.num += 1; 29 | } 30 | 31 | method () { 32 | super.method(); 33 | this.num += 1; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-inheritance", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node --expose-gc index.js", 8 | "install": "tsc -d --project ts && babel es6/index.js --out-file es6/index-babel.js && buble es6/index.js -o es6/index-buble.js", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "present": "^1.0.0" 15 | }, 16 | "devDependencies": { 17 | "babel-cli": "^6.16.0", 18 | "babel-preset-es2015": "^6.16.0", 19 | "buble": "^0.14.0", 20 | "typescript": "^2.0.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /es6/index.js: -------------------------------------------------------------------------------- 1 | class Level0 { 2 | constructor () { 3 | this.num = 0; 4 | } 5 | method () { 6 | this.num += 1; 7 | } 8 | } 9 | module.exports.Level0 = Level0; 10 | 11 | class Level1 extends Level0 { 12 | constructor () { 13 | super(); 14 | this.num += 1; 15 | } 16 | method () { 17 | super.method(); 18 | this.num += 1; 19 | } 20 | } 21 | module.exports.Level1 = Level1; 22 | 23 | class Level2 extends Level1 { 24 | constructor () { 25 | super(); 26 | this.num += 1; 27 | } 28 | method () { 29 | super.method(); 30 | this.num += 1; 31 | } 32 | } 33 | module.exports.Level2 = Level2; 34 | -------------------------------------------------------------------------------- /vanilla/index.js: -------------------------------------------------------------------------------- 1 | // Level 0 2 | function Level0 () { 3 | this.num = 0; 4 | } 5 | Level0.prototype.method = function() { 6 | this.num += 1; 7 | } 8 | module.exports.Level0 = Level0; 9 | 10 | // Level 1 11 | function Level1 () { 12 | Level0.call(this); 13 | this.num += 1; 14 | } 15 | Level1.prototype.method = function() { 16 | Level0.prototype.method.call(this); 17 | this.num += 1; 18 | } 19 | Level1.prototype = new Level0(); 20 | Level1.constructor = Level1; 21 | module.exports.Level1 = Level1; 22 | 23 | // Level 2 24 | function Level2 () { 25 | Level1.call(this); 26 | this.num += 1; 27 | } 28 | Level2.prototype.method = function() { 29 | Level1.prototype.method.call(this); 30 | this.num += 1; 31 | } 32 | Level2.prototype = new Level1(); 33 | Level2.constructor = Level2; 34 | module.exports.Level2 = Level2; 35 | -------------------------------------------------------------------------------- /vanilla-promote/index.js: -------------------------------------------------------------------------------- 1 | function extend(subclass, superclass) { 2 | function o() { this.constructor = subclass; } 3 | o.prototype = superclass.prototype; 4 | return (subclass.prototype = new o()); 5 | }; 6 | 7 | function promote (subclass, prefix) { 8 | var subP = subclass.prototype, supP = (Object.getPrototypeOf&&Object.getPrototypeOf(subP))||subP.__proto__; 9 | if (supP) { 10 | subP[(prefix+="_") + "constructor"] = supP.constructor; // constructor is not always innumerable 11 | for (var n in supP) { 12 | if (subP.hasOwnProperty(n) && (typeof supP[n] == "function")) { subP[prefix + n] = supP[n]; } 13 | } 14 | } 15 | return subclass; 16 | } 17 | 18 | // Level 0 19 | function Level0 () { 20 | this.num = 0; 21 | } 22 | Level0.prototype.method = function() { 23 | this.num += 1; 24 | } 25 | module.exports.Level0 = Level0; 26 | 27 | // Level 1 28 | function Level1 () { 29 | this.Level0_constructor(); 30 | this.num += 1; 31 | } 32 | extend(Level1, Level0); 33 | Level1.prototype.method = function() { 34 | this.Level0_method(); 35 | this.num += 1; 36 | } 37 | promote(Level1, "Level0"); 38 | module.exports.Level1 = Level1; 39 | 40 | // Level 2 41 | function Level2 () { 42 | this.Level1_constructor(); 43 | this.num += 1; 44 | } 45 | extend(Level2, Level1); 46 | Level2.prototype.method = function() { 47 | this.Level1_method(); 48 | this.num += 1; 49 | } 50 | promote(Level2, "Level1"); 51 | module.exports.Level2 = Level2; 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | JavaScript Inheritance Benchmark 2 | === 3 | 4 | > For detailed report on source code output size, compilation and execution time 5 | > of different build tools, have a look at [The cost of transpiling es2015 in 6 | > 2016](https://github.com/samccone/The-cost-of-transpiling-es2015-in-2016) 7 | 8 | This project investigates code execution performance when using inheritance with 9 | transpiled and not transpiled JavaScript code. 10 | 11 | Motivation 12 | --- 13 | 14 | Now that JavaScript supports 15 | [classes](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Classes), 16 | we just want to use them. Unfortunately, it's still necessary to transpile ES6/7 17 | down to ES5 to support all browser environments. 18 | 19 | Current results 20 | === 21 | 22 | See below the results achieved by running each scenario 10x on Node v6.3.0. 23 | 24 | Machine specs: Macbook Air / 1.3 GHz Intel Core i5 / 8GB 1600 MHz DDR3 25 | 26 | **Label** 27 | 28 | - [vanilla](vanilla/index.js): Plain ES5 standards JavaScript. 29 | - [vanillaPromote](vanilla-promote/index.js): Derivation of "vanilla", used by libraries such as [EaselJS](https://github.com/CreateJS/EaselJS) 30 | - [ts](ts/index.ts): TypeScript code transpiled down to ES5 using [TypeScript](https://github.com/Microsoft/TypeScript) 31 | - [babel](es6/index.js): JavaScript code (ES6) transpiled down to ES5 using 32 | [babel](https://github.com/babel/babel) 33 | - [buble](es6/index.js): JavaScript code (ES6) transpiled down to ES5 using 34 | [buble](https://gitlab.com/Rich-Harris/buble) 35 | - [es6](es6/index.js): Plain ES6 standards JavaScript. 36 | 37 | Execution time 38 | --- 39 | 40 | | scenario (10 times) | vanilla | vanillaPromote | ts | babel | buble | es6 | 41 | | --- | --- | --- | --- | --- | --- | --- | 42 | | eval declarations | 1.66ms | 1.77ms | 2.91ms | 6.55ms | 1.52ms | 0.59ms | 43 | | constructor without inheritance | 0.26ms | 0.12ms | 0.10ms | 0.25ms | 0.10ms | 0.09ms | 44 | | constructor with 2 levels of inheritance | 0.31ms | 0.17ms | 0.16ms | 0.35ms | 0.16ms | 0.15ms | 45 | | method call with two levels of inheritance | 0.11ms | 0.13ms | 0.17ms | 0.41ms | 0.22ms | 0.16ms | 46 | 47 | Memory usage ( `process.memoryUsage().heapUsed` ) 48 | --- 49 | 50 | | scenario (10 times) | vanilla | vanillaPromote | ts | babel | buble | es6 | 51 | | --- | --- | --- | --- | --- | --- | --- | 52 | | eval declarations | 80.38k | 97.67k | 134.30k | 222.82k | 65.09k | 62.42k | 53 | | constructor without inheritance | 3.47k | 2.32k | 2.32k | 3.09k | 2.38k | 2.34k | 54 | | constructor with 2 levels of inheritance | 5.77k | 4.53k | 4.27k | 8.61k | 4.80k | 5.27k | 55 | | method call with two levels of inheritance | 2.13k | 3.32k | 3.36k | 10.02k | 3.53k | 2.97k | 56 | 57 | How to run 58 | --- 59 | 60 | ``` 61 | git clone https://github.com/endel/js-inheritance-benchmark.git 62 | cd js-inheritance-benchmark 63 | npm install 64 | npm start 65 | ``` 66 | 67 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const present = require('present'); 3 | 4 | const targets = { 5 | vanilla: { module: require('./vanilla'), source: fs.readFileSync('./vanilla/index.js').toString() }, 6 | vanillaPromote: { module: require('./vanilla-promote'), source: fs.readFileSync('./vanilla-promote/index.js').toString() }, 7 | ts: { module: require('./ts'), source: fs.readFileSync('./ts/index.js').toString() }, 8 | babel: { module: require('./es6/index-babel.js'), source: fs.readFileSync('./es6/index-babel.js').toString() }, 9 | buble: { module: require('./es6/index-buble.js'), source: fs.readFileSync('./es6/index-buble.js').toString() }, 10 | es6: { module: require('./es6/index.js'), source: fs.readFileSync('./es6/index.js').toString() }, 11 | }; 12 | 13 | const numLoops = 10; 14 | 15 | let suites = []; 16 | let executionTable = []; 17 | let executionRow = []; 18 | 19 | let memoryTable = []; 20 | let memoryRow = []; 21 | let previousMemory = null; 22 | function addExecutionRow(elapsedTime) { 23 | executionRow.push(elapsedTime.toFixed(2) + "ms"); 24 | 25 | let currentMemory = process.memoryUsage(); 26 | memoryRow.push( ((currentMemory.heapUsed - previousMemory.heapUsed)/1024).toFixed(2) + "k" ); 27 | } 28 | 29 | function updateComparisionTable () { 30 | executionTable.push(executionRow); 31 | executionRow = []; 32 | 33 | memoryTable.push(memoryRow); 34 | memoryRow = []; 35 | } 36 | 37 | function bench (name, callback, numLoops, cleanup) { 38 | flushMemoryUsage(); 39 | 40 | let now = present(); 41 | 42 | for (let i=0; i