Protect objects/arrays from mutation without deep cloning.
Alhambra uses Proxies and shallow clones to protect the "original" object from mutation. The "protected" object has the same behavior as the original object: getters, setters, methods, and the prototype chain act the same.
If you want to remove the Proxy wrappers from the protected object, use alhambra.release()
on it.
- Pros:
- Creation cost does not increase as size increases.
- Cons:
- Interaction cost of the clone is more expensive than the original.
- Interaction cost increases as the nesting depth of properties increase. For example, getting
clone.foo.bar
is more expensive than gettingclone.foo
.
- Pros:
- Interaction (get, set, etc.) cost of the clone is the same as the original.
- Interaction cost does not increase as size increases.
- Cons:
- Creation cost increases as size of original increases.
- Alhambra is better when you "clone big/frequently and interact small/infrequently".
- For example, if you clone an array of 10 million objects and only interact with a few objects.
- Deep cloning is better when you "clone small/infrequently and interact big/frequently".
- For example, if you clone an array of 10 million objects and iterate over the entire array.
npm install alhambra
Directly mutating an object's properties keeps the original unchanged.
const obj = { id: 1 };
const p = alhambra.protect(obj);
p.id = 2;
const newObj = alhambra.release(p);
console.log(obj.id === 1); // True
console.log(newObj.id === 2); // True
Nested properties are protected, too.
const obj = { foo: { bar: 1 } };
const p = alhambra.protect(obj);
p.foo.bar = 2;
const newObj = alhambra.release(p);
console.log(obj.foo.bar === 1); // True
console.log(newObj.foo.bar === 2); // True
The original object is returned when the protected object isn't changed.
const obj = { id: 1 };
const p = alhambra.protect(obj);
const newObj = alhambra.release(p);
console.log(obj === newObj); // True
Instantiation.
class Foo {
constructor() {
this.id = 1;
}
greet() {
console.log('Hello!');
}
}
const obj = new Foo();
const p = alhambra.protect(obj);
p.id = 2;
p.greet(); // Still works.
const newObj = alhambra.release(p);
console.log(obj.id === 1); // True
console.log(newObj.id === 2); // True
Prototype methods work.
const alhambra = require('alhambra');
const arr = [1, 2, 3];
const p = alhambra.protect(arr);
p.push(4);
const newArr = alhambra.release(p);
console.log(arr.length === 3); // True
console.log(newArr.length === 4); // True
Nested array.
const alhambra = require('alhambra');
const obj = {
foo: {
arr: [1, 2, 3],
},
};
const p = alhambra.protect(obj);
p.foo.bar.arr.push(4);
const newObj = alhambra.release(p);
console.log(obj.foo.arr.length === 3); // True
console.log(newObj.foo.arr.length === 4); // True
Unchanged objects keep the same reference.
const alhambra = require('alhambra');
const obj = {
foo: {
arr: [{ a: 1 }, { a: 2 }, { a: 3 }],
},
};
const p = alhambra.protect(obj);
p.foo.arr[1].a = 100;
const reversed = alhambra.release(p);
console.log(reversed.foo.arr[0] === obj.foo.arr[0]); // True
console.log(reversed.foo.arr[1] === obj.foo.arr[1]); // False
console.log(reversed.foo.arr[2] === obj.foo.arr[2]); // True
Mutations to the source can still affect the new object. This is because the protect()
method is designed to protect the source, rather than new object.
const alhambra = require('alhambra');
const obj = { id: 1 };
const p = alhambra.protect(obj);
obj.id = 2;
const newObj = alhambra.release(p);
console.log(obj.id === 2); // True
console.log(newObj.id === 2); // True