Skip to content

Instantly share code, notes, and snippets.

@alessioalex
Created February 3, 2016 12:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alessioalex/c0562958b112ae7fcccd to your computer and use it in GitHub Desktop.
Save alessioalex/c0562958b112ae7fcccd to your computer and use it in GitHub Desktop.
ES6 Proxy magic for arrays
// https://curiosity-driven.org/array-slices#mimicking-array
function emulateArray(obj) {
var length = obj.length || 0;
return new Proxy(obj, {
get: function(target, property) {
if (property === 'length') {
return length;
}
if (property in target) {
return target[property];
}
if (property in Array.prototype) {
return Array.prototype[property].bind(obj);
}
},
set: function(target, property, value) {
if (property === 'length') {
for (var i = value; i < length; i++) {
delete target[i];
}
length = value;
return;
}
target[property] = value;
if (Number(property) >= length) {
length = Number(property) + 1;
}
}
});
}
var obj = {
0: 17,
1: 29,
2: 51,
length: 3
};
obj = emulateArray(obj);
@alessioalex
Copy link
Author

alessioalex commented Feb 26, 2018

'use strict';

// https://curiosity-driven.org/array-slices#mimicking-array
function isObject(obj) {
  return Object.prototype.toString.call(obj) === '[object Object]';
}

// TODO: '.' failures with custom obj props
function computePath(p1, p2, delimiter) {
  if (!delimiter || delimiter === '.') {
    if (!p1) { return p2; }

    return p1 + '.' + p2;
  } else {
    if (!p1) { return p2; }

    // array
    return p1 + '[' + p2 + ']';
  }
}

function subProxyObj(obj, update, dotPath) {
  var path = dotPath || '';
  var newPath = '';
  var isArray = Array.isArray(obj);

  Object.keys(obj).forEach(function(property) {
    if (isArray) {
      newPath = computePath(path, property, '[]');
    } else {
      newPath = computePath(path, property);
    }

    if (Array.isArray(obj[property])) {
      obj[property] = emulateArray(obj[property], update, newPath);
    } else if (isObject(obj[property])) {
      obj[property] = proxyObject(obj[property], update, newPath);
    }
  });
}

function updateValue(target, property, value, newPath, update) {
  if (Array.isArray(value)) {
    target[property] = emulateArray(value, update, newPath);
  } else if (isObject(value)) {
    target[property] = proxyObject(value, update, newPath);
  } else {
    target[property] = value;
  }

  update(newPath, value);
}

function emulateArray(obj, update, dotPath) {
  var length = obj.length || 0;

  subProxyObj(obj, update, dotPath);

  var path = dotPath || '';
  var newPath = '';

  return new Proxy(obj, {
    get: function(target, property) {
      if (property === 'length') {
        return length;
      }

      if (property in target) {
        return target[property];
      }

      if (property in Array.prototype) {
        // TODO: what about non-fns?
        return function() {
          return Array.prototype[property].apply(obj, arguments);
        };
      }
    },
    set: function(target, property, value) {
      if (property === 'length') {
        for (var i = value; i < length; i++) {
          delete target[i];
        }

        if (length !== value) {
          length = value;
          // needed when deleting stuff (splice, shift, pop)
          newPath = computePath(path, property);
          update(newPath, value);
        }

        return true;
      } else {
        newPath = computePath(path, property, '[]');
      }

      // when updating a value check if it's an obj / array, might need to
      // proxy that set value
      updateValue(target, property, value, newPath, update);


      if (Number(property) >= length) {
        length = Number(property) + 1;
      }

      return true;
    },
    deleteProperty(target, property) {
      if (property in target) {
        newPath = computePath(path, property, '[]');
        target[property] = undefined;
        update(newPath, undefined, true);
      }

      return true;
    }
  });
}

// TODO: proxy array root?
// TODO: implement __isProxy property?
function proxyObject(obj, update, dotPath) {
  subProxyObj(obj, update, dotPath);

  var path = dotPath || '';
  var newPath = '';

  return new Proxy(obj, {
    get: function(target, property) {
      return target[property];
    },
    set: function(target, property, value) {
      newPath = computePath(path, property);

      updateValue(target, property, value, newPath, update);

      return true;
    },
    deleteProperty(target, property) {
      if (property in target) {
        newPath = computePath(path, property);
        delete target[property];
        update(newPath, undefined, true);
      }

      return true;
    }
  });
}

module.exports = proxyObject;

@alessioalex
Copy link
Author

alessioalex commented Feb 27, 2018

'use strict';

const proxyObject = require('./');

const o = {
  a: [
    1, 2, 3, 4, [ 5, 6, 7]
  ],
  b: {
    c: { d: [1, 2, 3], e: 5 }
  }
};

const obj = proxyObject(o, (path, val, isRemoved) => {
  if (isRemoved) {
    console.log(`removed ${path}`);
  } else {
    console.log(`${path} = `, val);
  }
});

/*
console.log('obj.a[4]:', obj.a[4]);
console.log('obj.a[4].pop()');
obj.a[4].pop();
console.log('delete obj.a[4][1]');
delete obj.a[4][1]
console.log('obj.b.c.d[2] = 222');
obj.b.c.d[2] = 222;
*/

console.log('----------------');
obj.a[3] = { bb: 22, cc: 44 };
// delete obj.a[3].cc;
obj.a[3].bb = { a: 1, b: 2, c: [33, 44] };
// delete obj.a[3].bb.c[1];
console.log('-------push-------');
obj.a[3].bb.c.push(2,3,4);
console.log('-------unshift----');
obj.a[3].bb.c.unshift(0,1,1);
console.log('-------pop--------');
obj.a[3].bb.c.pop();
console.log('-------shift------');
obj.a[3].bb.c.shift();
console.log('------------------');

// obj.c = { b: 2, c: 4 };
// delete obj.c.c;

console.log(obj.a[3].bb.c.length, JSON.stringify(obj));

// console.log('JSON.stringify(obj.a[4]):', JSON.stringify(obj.a[4]));
// console.log('obj.a[4].valueOf():', obj.a[4].valueOf());
// obj2.a[4]
// > Proxy {0: 5, 1: 6, 2: 7, length: 3} << 'hide' length

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment