added mixins(..) and hasMixin(..) functions + refactoring + docs...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2020-05-07 21:02:05 +03:00
parent 858151e53f
commit 86ffc914c3
3 changed files with 151 additions and 59 deletions

View File

@ -437,6 +437,12 @@ sources(<object>, <name>, <callback>)
-> <list> -> <list>
``` ```
```
callback(<source>)
-> 'stop' | false
-> undefined
```
### `parent(..)` ### `parent(..)`
@ -497,9 +503,36 @@ This will copy the content of each input object without touching the
objects themselves, making them fully reusable. objects themselves, making them fully reusable.
### `mixins(..)`
Get matching mixins
```
mixins(<base>, <object>)
mixins(<base>, [<object>, ..])
mixins(<base>, <object>, <callback>)
mixins(<base>, [<object>, ..], <callback>)
-> list
```
```
callback(<match>, <object>, <parent>)
-> 'stop' | false
-> undefined
```
### `hasMixin(..)`
Check of object has mixin
```
hasMixin(<base>, <mixin>)
-> <bool>
```
### `mixout(..)` ### `mixout(..)`
Remove the first occurrence of each object out of a prototype chain Remove the first match for each object out of a prototype chain
``` ```
mixout(<base>, <object>, ..) mixout(<base>, <object>, ..)
mixout(<base>, 'first', <object>, ..) mixout(<base>, 'first', <object>, ..)
@ -512,13 +545,6 @@ mixout(<base>, 'all', <object>, ..)
-> <base> -> <base>
``` ```
This relies on first level object structure to identify the target
objects in the prototype chain, for a successful match the following
must apply:
- attribute count must match,
- attribute names must match,
- attribute values must be identical.
This is the opposite of `mixin(..)` This is the opposite of `mixin(..)`
@ -600,6 +626,23 @@ This is used to format `.toString(..)` return values for nested functions
to make source printing in console more pleasant to read. to make source printing in console more pleasant to read.
### `match(..)`
Test if the two objects match in attributes and attribute values
```
match(base, obj)
-> bool
```
This relies on first level object structure to identify the target
objects in the prototype chain, for a successful match the following
must apply:
- attribute count must match,
- attribute names must match,
- attribute values must be identical.
## Limitations ## Limitations

149
object.js
View File

@ -57,6 +57,33 @@ function(text, tab_size){
.trim() } .trim() }
// Match two objects...
//
// XXX this will match any two objects with no enumerable keys...
var match =
module.match =
function(base, obj){
// identity...
if(base === obj){
return true }
// typeof -- sanity check...
if(typeof(base) != typeof(obj)){
return false }
// attr count...
//var o = Object.entries(obj)
var o = Object.keys(Object.getOwnPropertyDescriptors(obj))
if(Object.keys(Object.getOwnPropertyDescriptors(base)).length != o.length){
return false }
// names and values...
o = o.map(function(k){
return [k, obj[k]] })
while(o.length > 0){
var [k, v] = o.pop()
if(!base.hasOwnProperty(k) || base[k] !== v){
return false } }
return true }
//--------------------------------------------------------------------- //---------------------------------------------------------------------
// Prototype chain content access... // Prototype chain content access...
@ -239,59 +266,99 @@ function(proto, name, that, ...args){
// Mix a set of methods/props/attrs into an object... // Mix a set of methods/props/attrs into an object...
// //
// mixinFlat(root, object, ...) // mixinFlat(base, object, ...)
// -> root // -> base
// //
// //
// NOTE: essentially this is just like Object.assign(..) but copies // NOTE: essentially this is just like Object.assign(..) but copies
// properties directly rather than copying property values... // properties directly rather than copying property values...
var mixinFlat = var mixinFlat =
module.mixinFlat = module.mixinFlat =
function(root, ...objects){ function(base, ...objects){
return objects return objects
.reduce(function(root, cur){ .reduce(function(base, cur){
Object.keys(cur) Object.keys(cur)
.map(function(k){ .map(function(k){
Object.defineProperty(root, k, Object.defineProperty(base, k,
Object.getOwnPropertyDescriptor(cur, k)) }) Object.getOwnPropertyDescriptor(cur, k)) })
return root }, root) } return base }, base) }
// Mix sets of methods/props/attrs into an object as prototypes... // Mix sets of methods/props/attrs into an object as prototypes...
// //
// mixin(root, object, ..) // mixin(base, object, ..)
// -> root // -> base
// //
// //
// This will create a new object per set of methods given and // This will create a new object per set of methods given and
// mixinFlat(..) the method set into this object leaving the // mixinFlat(..) the method set into this object leaving the
// original objects intact. // original objects intact.
// //
// root <-- object1_copy <-- .. <-- objectN_copy <- root.__proto__ // base <-- object1_copy <-- .. <-- objectN_copy <- base.__proto__
// //
// //
// NOTE: this will only mix in non-empty objects... // NOTE: this will only mix in non-empty objects...
var mixin = var mixin =
module.mixin = module.mixin =
function(root, ...objects){ function(base, ...objects){
root.__proto__ = objects base.__proto__ = objects
.reduce(function(res, cur){ .reduce(function(res, cur){
return Object.keys(cur).length > 0 ? return Object.keys(cur).length > 0 ?
module.mixinFlat(Object.create(res), cur) module.mixinFlat(Object.create(res), cur)
: res }, root.__proto__) : res }, base.__proto__)
return root } return base }
// Get matching mixins...
//
// NOTE: if base matches directly callback(..) will get undefined as parent
// NOTE: this will also match base...
var mixins =
module.mixins =
function(base, object, callback){
object = object instanceof Array ?
object
: [object]
var res = []
var stop
var parent
while(base != null){
// match each object...
for(var obj of object){
if(match(base, obj)){
res.push(base)
stop = callback
&& callback(base, obj, parent)
if(stop === true || stop == 'stop'){
return res }
// match found, no need to test further...
break } }
parent = base
base = base.__proto__ }
return res }
// Check of base has mixin...
//
// hasMixin(base, mixin)
// -> bool
//
var hasMixin =
module.hasMixin =
function(base, object){
return mixins(base, object, function(){ return 'stop' }).length > 0 }
// Mix-out sets of methods/props/attrs out of an object prototype chain... // Mix-out sets of methods/props/attrs out of an object prototype chain...
// //
// Mix-out first occurrence of each matching object... // Mix-out first occurrence of each matching object...
// mixout(root, object, ..) // mixout(base, object, ..)
// mixout(root, 'first', object, ..) // mixout(base, 'first', object, ..)
// -> root // -> base
// //
// Mix-out all occurrences of each matching object... // Mix-out all occurrences of each matching object...
// mixout(root, 'all', object, ..) // mixout(base, 'all', object, ..)
// -> root // -> base
// //
// //
// This will match an object to a mixin iff: // This will match an object to a mixin iff:
@ -303,43 +370,25 @@ function(root, ...objects){
// NOTE: this is the opposite to mixin(..) // NOTE: this is the opposite to mixin(..)
var mixout = var mixout =
module.mixout = module.mixout =
function(root, ...objects){ function(base, ...objects){
var all = objects[0] == 'all' ? var all = objects[0] == 'all' ?
!!objects.shift() !!objects.shift()
: objects[0] == 'first' ? : objects[0] == 'first' ?
!objects.shift() !objects.shift()
// default...
: false : false
var remove = []
var _match = function(root, obj){ mixins(base, objects, function(match, obj, parent){
// identity... parent && remove.push(parent)
if(root === obj){ // when removing the first occurrence, don't check for obj again...
return true } all || objects.splice(objects.indexOf(obj), 1) })
// attr count... // NOTE: we are removing on a separate stage so as not to mess with
if(Object.keys(root).length != Object.keys(obj).length){ // mixins(..) iterating...
return false } remove
// names and values... // XXX not sure why we need to reverse here -- needs more thought...
var e = Object.entries(obj) .reverse()
while(e.length > 0){ .forEach(function(p){
var [k, v] = e.pop() p.__proto__ = p.__proto__.__proto__ })
if(!root.hasOwnProperty(k) || root[k] !== v){ return base }
return false } }
return true }
var _drop = function(obj){
var cur = root
var found = false
while(cur.__proto__ != null
// continue iff ...
&& (all || !found)){
found = _match(cur.__proto__, obj)
found
&& (cur.__proto__ = cur.__proto__.__proto__)
cur = cur.__proto__ } }
// do the work...
objects.map(_drop)
return root }

View File

@ -1,6 +1,6 @@
{ {
"name": "ig-object", "name": "ig-object",
"version": "3.2.0", "version": "3.3.0",
"description": "", "description": "",
"main": "object.js", "main": "object.js",
"scripts": { "scripts": {