mirror of
https://github.com/flynx/object.js.git
synced 2025-10-30 02:50:10 +00:00
added mixins(..) and hasMixin(..) functions + refactoring + docs...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
858151e53f
commit
86ffc914c3
59
README.md
59
README.md
@ -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
149
object.js
@ -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 }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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": {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user