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>
```
```
callback(<source>)
-> 'stop' | false
-> undefined
```
### `parent(..)`
@ -497,9 +503,36 @@ This will copy the content of each input object without touching the
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(..)`
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>, 'first', <object>, ..)
@ -512,13 +545,6 @@ mixout(<base>, 'all', <object>, ..)
-> <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(..)`
@ -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.
### `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

149
object.js
View File

@ -57,6 +57,33 @@ function(text, tab_size){
.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...
@ -239,59 +266,99 @@ function(proto, name, that, ...args){
// Mix a set of methods/props/attrs into an object...
//
// mixinFlat(root, object, ...)
// -> root
// mixinFlat(base, object, ...)
// -> base
//
//
// NOTE: essentially this is just like Object.assign(..) but copies
// properties directly rather than copying property values...
var mixinFlat =
module.mixinFlat =
function(root, ...objects){
function(base, ...objects){
return objects
.reduce(function(root, cur){
.reduce(function(base, cur){
Object.keys(cur)
.map(function(k){
Object.defineProperty(root, k,
Object.defineProperty(base, k,
Object.getOwnPropertyDescriptor(cur, k)) })
return root }, root) }
return base }, base) }
// Mix sets of methods/props/attrs into an object as prototypes...
//
// mixin(root, object, ..)
// -> root
// mixin(base, object, ..)
// -> base
//
//
// This will create a new object per set of methods given and
// mixinFlat(..) the method set into this object leaving the
// 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...
var mixin =
module.mixin =
function(root, ...objects){
root.__proto__ = objects
function(base, ...objects){
base.__proto__ = objects
.reduce(function(res, cur){
return Object.keys(cur).length > 0 ?
module.mixinFlat(Object.create(res), cur)
: res }, root.__proto__)
return root }
: res }, base.__proto__)
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 first occurrence of each matching object...
// mixout(root, object, ..)
// mixout(root, 'first', object, ..)
// -> root
// mixout(base, object, ..)
// mixout(base, 'first', object, ..)
// -> base
//
// Mix-out all occurrences of each matching object...
// mixout(root, 'all', object, ..)
// -> root
// mixout(base, 'all', object, ..)
// -> base
//
//
// This will match an object to a mixin iff:
@ -303,43 +370,25 @@ function(root, ...objects){
// NOTE: this is the opposite to mixin(..)
var mixout =
module.mixout =
function(root, ...objects){
function(base, ...objects){
var all = objects[0] == 'all' ?
!!objects.shift()
: objects[0] == 'first' ?
!objects.shift()
// default...
: false
var _match = function(root, obj){
// identity...
if(root === obj){
return true }
// attr count...
if(Object.keys(root).length != Object.keys(obj).length){
return false }
// names and values...
var e = Object.entries(obj)
while(e.length > 0){
var [k, v] = e.pop()
if(!root.hasOwnProperty(k) || root[k] !== v){
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 }
var remove = []
mixins(base, objects, function(match, obj, parent){
parent && remove.push(parent)
// when removing the first occurrence, don't check for obj again...
all || objects.splice(objects.indexOf(obj), 1) })
// NOTE: we are removing on a separate stage so as not to mess with
// mixins(..) iterating...
remove
// XXX not sure why we need to reverse here -- needs more thought...
.reverse()
.forEach(function(p){
p.__proto__ = p.__proto__.__proto__ })
return base }

View File

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