@bblocks/compose
Version:
This library is a handy helper if you work with objects, inheritance and composition, like to code efficiently and need to support all modern browser and IE11 without transpiling.
204 lines (159 loc) • 6.98 kB
Markdown
# Discover super powers of composition and inheritance in javascript with compose library
This library is a handy helper if you work with objects, inheritance and composition, like to code efficiently and need to support all modern browser and IE11 without transpiling.
[Quick start](#quick-start)
[Solving problem #1: Composition of objects keeping property descriptors](#discover-super-powers-of-composition-and-inheritance-in-javascript-with-compose-library)
[Solving problem #2: IE11 doesn't support Object.assign, Object.getOwnPropertyDescriptors](#problem-composition-of-objects-keeping-property-descriptors)
[Solving problem #3: Bulky code to define properties, inherit and compose object using native javascript methods](#problem-bulky-code-to-define-properties-inherit-and-compose-object-using-native-javascript-methods)
[Have some fun](https://bblocks.github.io/compose/fun.html)
## Quick start
install
```nmp
npm install @bblocks/compose --save-dev
```
include
```html
<!-- include the library -->
<script src="https://cdn.jsdelivr.net/npm/@bblocks/compose@0.1.1/compose.umd.js"></script>
```
```javascript
// Optionally create shortcuts in lodash style
var _ = Object.assign(_ || compose); // Now you can use helpers from the library_.mix _.clone _.Block _.block
```
enjoy
```javascript
// Composition
_.mix({prop1: 1}, {prop2: 2});
// Inheritance
_.clone({prop1: 1}, {prop2: 2}, {foo: function() {}});
// Handy object with built-in features to compose, clone and define properties
var myObj = new _.Block({prop1:1}); // Our building block
var myClone = myObj
.mix({prop2:2})
.define({
prop3: {
get: function() {return 3},
}
})
.clone({prop4: 4});
// Check results
console.log(myClone.__proto__, myClone.prop1, myClone.prop2, myClone.prop3, myClone.prop4); // {...} 1 2 3 4
```
## Problem: Composition of objects keeping property descriptors
Objects became more powerful in javascript since ES5. Now we can create super powerful objects thanks to [property descriptors](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty).
Let me show you. In the first example we create and object that can display numbers with suffixes like 1M, 1k, 1b etc...
```javascript
// Display big numbers with suffixes
var bigNumber = {
suffixes: ["", "k", "m", "b","t"],
toString: function (value) {
var newValue = value;
if (value >= 1000) {
var suffixes = this.suffixes;
var suffixNum = Math.floor( (""+value).length/3 );
var shortValue = '';
for (var precision = 2; precision >= 1; precision--) {
shortValue = parseFloat( (suffixNum != 0 ? (value / Math.pow(1000,suffixNum) ) : value).toPrecision(precision));
var dotLessShortValue = (shortValue + '').replace(/[^a-zA-Z 0-9]+/g,'');
if (dotLessShortValue.length <= 2) { break; }
}
if (shortValue % 1 != 0) shortNum = shortValue.toFixed(1);
newValue = shortValue+suffixes[suffixNum];
}
return newValue;
}
};
console.log('100000 = ' + bigNumber.toString(100000)); // 1000000 = 1m
```
```javascript
// We can automatically generate a short string when value changes thanks to getters and setters
Object.defineProperty(bigNumber, 'value', {
get: function() {
return this._value || 0;
},
set: function(newValue) {
if (newValue != this._value) {
this.stringValue = this.toString(newValue);
}
this._value = newValue;
}
});
// Create a new instance
var myNumber = Object.create(bigNumber);
myNumber.value = 1000;
console.log(myNumber.value + ' = ' + myNumber.stringValue); // 1000 = 1k
```
I discovered a problem that we loose all property descriptors when we try to compose two objects.
```javascript
// Define a new feature to increment a number
var increment = {
increment: function() {
this.value = (this.value || 0) + 1;
}
}
// Compose increment and big number features
var incNumber = Object.create(increment);
Object.assign(incNumber, bigNumber);
incNumber.increment(); // value = 1;
console.log(incNumber.value); // 1
console.log(incNumber.stringValue); // undefined :(
```
So we lost all the magic after composition. But you can use **mix** or **clone** methods from the library to solve this problem.
```javascript
// Re-define our mixed object
var myIncNumber = _.clone(increment);
_.mix(myIncNumber, bigNumber);
// or
var myIncNumber2 = _.clone(increment, bigNumber);
myIncNumber.increment();
myIncNumber2.value = 1000;
console.log(myIncNumber.stringValue, myIncNumber2.stringValue); // 1, 1k
```
## IE11 doesn't support Object.assign, Object.getOwnPropertyDescriptors
We can use native javascript function Object.assign for composition.
```javascript
var sourceObj = {prop1:1};
Object.assign(sourceObj, {prop2: 2}); // Fails in IE11
console.log(sourceObj.prop1 + sourceObj.prop2); // 3
```
But in order to keep descriptors we need to use something like this.
```javascript
var sourceObj = {prop1:1};
var extraObj = Object.defineProperty({}, 'prop2', {get: function() {return 2;}});
Object.defineProperties(sourceObj, Object.getOwnPropertyDescriptors(extraObj)); // Fails in IE11. Object doesn't support property or method 'getOwnPropertyDescriptors'
console.log(sourceObj.prop1 + sourceObj.prop2); // 3
```
Besides helpful **mix** and **clone** methods the library goes with two polyfills. Including the library automatically fixes the problem in IE11.
## Problem: Bulky code to define properties, inherit and compose object using native javascript methods
The example above looks bulky in native JS code.
```javascript
var sourceObj = {prop1:1};
var extraObj = Object.defineProperty({}, 'prop2', {get: function() {return 2;}});
Object.defineProperties(sourceObj, Object.getOwnPropertyDescriptors(extraObj));
console.log(sourceObj.prop1 + sourceObj.prop2); // 3
```
Library has a bonus for you. A handy Block you can start with.
```javascript
var extraObj = (new _.Block()).define({prop2: {get: function() {return 2;}}});
var sourceObj = (new _.Block({prop1:1})).mix(extraObj);
console.log(sourceObj.prop1 + sourceObj.prop2); // 3
```
Let me explain step by step.
```javascript
// create new objects
var obj = new _.Block({prop1:1});
// composing with other objects
obj.mix({prop2: 2});
// configuring properties with descriptors
obj.define({prop3: {get: function() {return 3;}}});
// inherit
var myClone = obj.clone({prop4: 4});
// Checking results
console.log(myClone.prop1 + myClone.prop2 + myClone.prop3 + myClone.prop4); // 1 + 2 + 3 + 4 = 10
// You can use multiple arguments and chaining
var myClone = (new _.Block({prop1: 1}))
.mix({prop2: 2})
.define({prop3: {get: function() {return 3;}}})
.clone({prop4: 4});
// Checking results
console.log(myClone.prop1 + myClone.prop2 + myClone.prop3 + myClone.prop4); // 1 + 2 + 3 + 4 = 10
```