3 ways for a function to return an Object:
- Constructor functions
- ES6 Class (made up constructor)
- Factory
- methods stored in a shared prototype
- private data via closures
function Drums () {}
Drums.prototype.play = function () {
console.log('BOUM');
}
const drumInstance = new Drums();
drumInstance.play(); // BOUM
class Drums {
play () {
console.log('BOUM');
}
}
const drumInstance = new Drums();
drumInstance.play(); // BOUM
Under the hood, still a constructor:
typeof class DoubleRainbow {} // function
Babel
var Drums = function () {
function Drums() {
_classCallCheck(this, Drums);
}
// Attach play method to Drums prototype
_createClass(Drums, [{
key: 'play',
value: function play() {
console.log('BOUM');
}
}]);
return Drums;
}();
var drumInstance = new Drums();
- more flexible
- can create objects with initial data
function createDrum() {
return {
play() {
console.log('BOUM');
}
}
}
const drumInstance = createDrum();
drumInstance.play(); // BOUM
// play is a drumInstance's property
console.log(drumInstance) // { play: ƒ }
- costs less space & memory (it doesn't create properties)
The Object.create() method creates a new object with the specified prototype object and properties.
function createDrum() {
const prototypeLike = {
play () {
console.log('BOUM');
}
};
return Object.create(prototypeLike);
}
const drumInstance = createDrum();
drumInstance.play(); // BOUM
// play is a drumInstance.prototype's property
console.log(drumInstance) // {}
- Closures, closures and closures
Examples with...
...class...
class Drums {
constructor() {
const sound = 'BOUM'; // private workaround
this.getSound = () => sound;
}
play () {
console.log(this.getSound());
}
}
const drumInstance = new Drums();
drumInstance.play(); // BOUM
using Symbol
const _name = Symbol('name');
class Test {
constructor(name) {
this[_name] = name;
}
get name() { return this[_name]; }
}
...factory...
function createDrum() {
const sound = 'BOUM'; // private variable
return {
play () {
console.log(sound);
}
};
}
const drumInstance = createDrum();
drumInstance.play(); // BOUM
Prototype is a chain, populated with each extension
- protoA inherit from protoB
- protoB inherit from protoC
- ...
- protoZ inherit from Object's prototype
Reminder: When you try to access a property on an object, it checks the object’s own properties first. If it doesn’t find it there, it checks the prototype.
class A {}
class B extends A {}
function Parent() {}
function Child() {}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
function createDrum() {
return Object.create({
whoAreYou() {
console.log('A Drum!');
},
play () {
console.log('BOUM');
}
})
}
function createSmallDrum() {
return Object.create(Object.assign(createDrum.call(this), {
play() {
console.log('little boum')
}
}));
}
const smallDrumInstance = createSmallDrum();
smallDrumInstance.play(); // little boum
smallDrumInstance.whoAreYou(); // A Drum!
Note: if you want to use Object spread, createSmallDrum becomes:
const drum = createDrum.call(this);
return Object.create({
...drum.__proto__,
play() {
console.log('little boum')
}
});
__proto__property of an object is a pointer to the object's constructor function's prototype property
foo.__proto__ === foo.constructor.prototype
function createDrum() {
return {
sound: 'BOUM',
whoAreYou() {
console.log('A Drum!');
},
play() {
console.log(this.sound);
}
}
}
function createSmallDrum() {
return Object.assign(createDrum.call(this), {
sound: 'little boum'
});
}
const smallDrumInstance = createSmallDrum();
smallDrumInstance.play(); // little boum
smallDrumInstance.whoAreYou(); // A Drum!
Note that you should not use arrow function in the returned Object because the context will be the function and not the object itself.
- return an object of any subtype of their return type: the object to be returned could be of several different types depending on some parameter
- mix custom properties + prototype
- compose multiple objects: more flexible
const createHuman() {
return {
canWalk: true,
canTalk: true,
canSee: false,
canSleepWithoutPeeing: true
}
}
const createBaby() {
return {
canSleepWithoutPeeing: false
}
}
const createAlien() {
return {
canFly: true,
canTalk: false
}
}
const createSuperMixedEntity() {
return Object.assign(createHuman(), createBaby(), createAlien())
}
https://jsperf.com/prototype-vs-factory-vs-class
- Factory: 1 093 Ops / sec
- Prototype: 4 370 Ops / sec
- Class: 4 414 Ops / sec
Factory 75% slower