Search code examples
javascriptprototypesettergettermutation

Write getters as a prototype


I'm working on making performance updates on my javascript code.

In Firefox I got this warning:

mutating the [[Prototype]] of an object will cause your code to run very slowly; instead create the object with the correct initial [[Prototype]] value using Object.create

I wrote some scripts to prove this, and the results are great: without mutation a simple script runs 66% faster.

But I have trouble converting my code without mutation, I can't write the getters:

This is what I have now:

// Class
function FooBar(options) {
  this.options = options;
}

// Prototype
FooBar.prototype = {

  // Getters
  get a() {
      return this.options.a;
    },

    get b() {
      return this.options.b;
    },

    get ab() {
      return this.options.a + this.options.b;
    },

    // Methods
    displayOptions: function() {
      console.log(this.options);
    }
};

// Code
var options = {
  a: 'foo',
  b: 'bar'
};

var fooBar = new FooBar(options);

console.log(fooBar.a);
console.log(fooBar.b);
console.log(fooBar.ab);
fooBar.displayOptions();

The getters as a prototype using the this keyword in their return are the problem.

If I use Object.defineProperty the this keyword is wrong, unless I do it inside the constructor, but it would recreate the property on each instance of the class and slow my code down even further.


Solution

  • This works (I just messed up the syntax in my previous attempt):

    // Class
    function FooBar (options) {
    	this.options = options;
    }
    
    //Prototype getters
    Object.defineProperty(FooBar.prototype, 'a', {
    	get: function() {
    		return this.options.a;
    	}
    });
    
    Object.defineProperty(FooBar.prototype, 'b', {
    	get: function() {
    		return this.options.b;
    	}
    });
    
    
    Object.defineProperty(FooBar.prototype, 'ab', {
    	get: function() {
    		return this.options.a + this.options.b;
    	}
    });
    
    // Methods
    FooBar.prototype.displayOptions = function() {
    	console.log(this.options);
    };
    
    // Code
    var options = {
    	a:'foo',
    	b:'bar'
    };
    
    var fooBar = new FooBar (options);
    
    console.log(fooBar.a);
    console.log(fooBar.b);
    console.log(fooBar.ab);
    fooBar.displayOptions();

    For those who are curious about the benefits of converting scripts like this to run faster: Run following code and look to your output in the console (Chrome - 66% faster, Firefox - no difference (curious, since I got the warning from Firefox)):

    // WITHOUT PROTOTYPING
    var Person1 = function() {
    	this.name = 'myName';
    	this.changeName = function(name) {
    		this.name = name;
    	};
    	this.changeName2 = function(name) {
    		this.name = name;
    	};
    	this.changeName3 = function(name) {
    		this.name = name;
    	};
    	this.changeName4 = function(name) {
    		this.name = name;
    	};
    }
    
    
    // WITH PROTOTYPING, WITH MUTATION
    var Person2 = function() {
    	this.name = 'myName';
    }
    Person2.prototype = {
    	changeName: function(name) {
    		this.name = name;
    	},
    	changeName2: function(name) {
    		this.name = name;
    	},
    	changeName3: function(name) {
    		this.name = name;
    	},
    	changeName4: function(name) {
    		this.name = name;
    	}
    };
    
    // WITH PROTOTYPING, WITHOUT MUTATION
    var Person3 = function() {
    	this.name = 'myName';
    }
    Person3.prototype.changeName = function(name) {
    	this.name = name;
    };
    Person3.prototype.changeName2 = function(name) {
    	this.name = name;
    };
    Person3.prototype.changeName3 = function(name) {
    	this.name = name;
    };
    Person3.prototype.changeName4 = function(name) {
    	this.name = name;
    };
    
    
    // DO THE TEST
    var i=0, len=1000000;
    
    // TEST1
    window.performance.mark('mark_test_start');
    for(i=0;i<len;i++) {
    	p = new Person1();
    	p.changeName('myName2');
    }
    window.performance.mark('mark_test_end');
    window.performance.measure('no-prototyping', 'mark_test_start', 'mark_test_end');
    
    
    // TEST2
    window.performance.mark('mark_test2_start');
    for(i=0;i<len;i++) {
    	p = new Person2();
    	p.changeName('myName2');
    }
    window.performance.mark('mark_test2_end');
    window.performance.measure('prototyping-with-mutation', 'mark_test2_start', 'mark_test2_end');
    
    // TEST3
    window.performance.mark('mark_test3_start');
    for(i=0;i<len;i++) {
    	p = new Person2();
    	p.changeName('myName2');
    }
    window.performance.mark('mark_test3_end');
    window.performance.measure('prototyping-without-mutation', 'mark_test3_start', 'mark_test3_end');
    
    
    
    // OUTPUT tests
    var items = window.performance.getEntriesByType('measure');
    for (var i = 0; i < items.length; ++i) {
      var req = items[i];
      console.log(req.name + ': ' + req.duration.toFixed(2));
    }