0

I'm trying to create a builder pattern in JavaScript. But I'm wondering why if I call Object.create twice I got the same list as before.

Here's my code.

var filterBuilder = {
  filters: [],
  addFilter: function(options) {
    var filter = { 
      'type': 'selector',
      'dimension': options.dimension,
      'value': options.value
    }
    this.filters.push(filter);
    return this;
  },
  build: function() {
    return this.filters;
  }
};

If I run Object.create(filterBuilder).build() I get [] which is good. But when I start adding filter

Object.create(filterBuilder).addFilter({dimension: '1', value: 'v'}).build();

I get one filter which is good

But then if I do

Object.create(filterBuilder).addFilter({dimension: '1', value: 'v'}).addFilter({dimension: '1', value: 'v'}).build();

I will get three filters, the first one is from the previous call. Isn't Object.create supposed to create a new object for me?

toy
  • 11,711
  • 24
  • 93
  • 176
  • Because `filter` is a property of the prototype not the newly created object. All objects that inherit from that prototype, share that property. Similar case [here](http://stackoverflow.com/questions/34363094/understanding-prototypal-inheritance-methodologies-in-js/34363433) – MinusFour Jan 21 '16 at 16:11
  • `Object.create` does create a new empty object that inherits properties from `filterBuilder`. It does not create a new array or a `filters` property. – Bergi Jan 21 '16 at 16:22

3 Answers3

1

Prototype properties are shared, so the filters array is the same for both objects you created. If you want each object to have its own filters you have to add it as an own property (a property that is owned by the object, not the prototype), ie:

var filterBuilder = {
  addFilter: function(options) {
    var filter = {
      'type': 'selector',
      'dimension': options.dimension,
      'value': options.value
    }
    this.filters.push(filter);
    return this;
  },
  build: function() {
    return this.filters;
  }
};

var a = Object.create(filterBuilder)
a.filters = [];
a.addFilter({dimension: '1', value: 'v'}).build();

var b = Object.create(filterBuilder)
b.filters = []
b.addFilter({dimension: '1', value: 'v'}).addFilter({dimension: '1', value: 'v'}).build();

console.log(a.filters.length, b.filters.length); // 1 2

You can do this with new as well:

function FilterContainer() {
  this.filters = [];
}

FilterContainer.prototype.addFilter = function() {
  ...
};

FilterContainer.prototype.build = function() {
  ...
};

// Has its own `filters` array
new FilterContainer().addFilter(...).build();
elclanrs
  • 92,861
  • 21
  • 134
  • 171
  • Thanks for the clear answer. I read somewhere and I'm supposed to avoid using `new` I guess that only applies for inheritance? – toy Jan 21 '16 at 16:14
  • 1
    It is ok advice, it depends. You can make your own thing with `Object.create`, I'd aim for something like `MyObject.new()`, this composes better than `new MyObject()`. But with ES6 `class` and the spread operator you avoid some of the confusion and unfamiliar syntax. – elclanrs Jan 21 '16 at 16:17
  • @toy: Yes, you should avoid `new` when creating prototype objects for subclasses that should inherit from another object. There's no reason to fear `new` anywhere else. – Bergi Jan 21 '16 at 16:23
1

When you call Object.create(filterBuilder) several times, you get two objects which hold references to the same filters array.

var b1 = Object.create(filterBuilder);
var b2 = Object.create(filterBuilder);

// now b1.filters is the _same_ object as b2.filters,
// so calling
b1.filters.push(...);

// will inevitably modify b2.filters as well.

Your best choice here is using classical functions and prototypes

function FilterBuilder() {
    this.filters = [];
}

FilterBuilder.prototype.addFilter = function() { };

var builder = new FilterBuilder();
builder.addFilter();
Andrew Khmylov
  • 732
  • 5
  • 18
1

Object.create() takes an optional second argument which can define properties that are not inherited from the prototype, so you can (re-)define the filters property of newly created object like this:

Object.create(filterBuilder, { 
  filters : { writable : true, enumerable: true, value : [] }
})

https://jsfiddle.net/1m7xx4ge/2/

// full code

var filterBuilder = {
  filters: [],
  addFilter: function(options) {
    var filter = { 
      'type': 'selector',
      'dimension': options.dimension,
      'value': options.value
    }
    this.filters.push(filter);
    return this;
  },
  build: function() {
    return this.filters;
  }
};

Object.create(filterBuilder, { 
  filters : { writable : true, enumerable : true, value : [] }
})
.addFilter({dimension: '1', value: 'v'}).build();

Object.create(filterBuilder, { 
   filters : { writable : true, enumerable : true, value : [] }
})
.addFilter({dimension: '1', value: 'v'})
.addFilter({dimension: '1', value: 'v'}).build();
pawel
  • 35,827
  • 7
  • 56
  • 53