I want to insert an object into a somewhat predefined object:
var obj = {
"scripts": {
"libs":{}
},
"plugins":{}
}
//....
function addobj(path, obj){
console.log(path); // Object {libs: Object}..
path.push(obj); // TypeError: undefined is not a function
}
// Test cases:
addobj(obj["scripts"],{"test":{}});
console.log(obj);
But an error occurs: TypeError: undefined is not a function Why is this happening?
http://jsfiddle.net/Qn3Tb/
Using jQuery, you can use $.extend():
demo
$.extend(path,obj);
You can't .push onto an Object. An Object is a key-value store, therefore you need to assign a key to the object (value) you want to store on the parent object. How you go about achieving that is another question, but something like this might work:
function addobj(path, obj, key) {
path[key || "unnamed"] = obj;
}
If you wanted to add libs to scripts you would do the following:
addobj(script, libs, "libs");
However given what this addobj method actually does, my suggestion would be to drop the abstraction altogether, it's not needed.
Why not simply do
function addProp(prop, value, targetObject){
targetObject[prop] = value;
}
addProp('scripts', { test:{}}, obj);
Based on your question, you can use this to target a specific property:
var obj = {
"scripts": {
"libs":{
"labs": {
foo: 1
}
}
},
"plugins":{}
};
function setPropByString(obj, propString, value) {
if (!propString)
return obj;
var prop, props = propString.split('.');
for (var i = 0, iLen = props.length - 1; i < iLen; i++) {
prop = props[i];
var candidate = obj[prop];
if (candidate !== undefined) {
obj = candidate;
} else {
break;
}
}
obj[props[i]] = value;
}
setPropByString(obj, 'scripts.libs.labs', { something: 1 });
console.log(obj);
Note that this will overwrite the existing prop. So it's propably easier to just extend with jQuery like #A.Wolff suggest.
http://jsfiddle.net/Mn45R/
You cannot do this in the way mentioned in the question.
I believe you should create a function, like
function Node(key) {
var currentNode = this;
this.getKey = function() {
return key;
};
var children = [];
this.addNode(childKey) {
children[children.length] = new Node(childKey);
}
this.search(searchKey) {
if (searchKey === key) {
return currentNode;
}
for (var childIndex in children) {
var searchResult = children[childIndex].search(searchKey);
if (!!searchResult) {
return searchResult;
}
}
return null;
}
}
You can create your root this way:
var root = new Node();
You can add a child to the root this way:
root.addNode("scripts");
This function can be used to add some node to another node having a key
function addNodeToTree(tree, key, newKey) {
var node = tree.search(key);
if (!!node) {
node.addNode(new Node(newKey));
return true;
}
return false;
}
Finally, you can add a node like this:
addNodeToTree(root, "scripts", "test");
Related
I have an object like
const obj = { field1: obj1, field2: obj2 }
and now I'd like to run a function when anything in obj was changed:
function objChanged() { ... }
// decorate obj somehow ...
obj.field3 = data; // objChanged should be called (Proxy can see it)
obj.field1.val = data; //objChanged should be called (Proxy can't see it?)
AFAIK there is a MutationObserver which works only for DOM and Proxy which intercepts only own properties, right?
I do not own obj1 so I can not change it. Is there a way to achieve this functionality?
Following the piece of code will listen to object property you can iterate over object properties to listen all. I am curious, what are you trying to achieve?
const dog = { bark: true };
function Observer(o, property) {
var _this = this;
this.observers = [];
this.Observe = function (notifyCallback) {
_this.observers.push(notifyCallback);
};
Object.defineProperty(o, property, {
set: function (val) {
_this.value = val;
for (var i = 0; i < _this.observers.length; i++) {
_this.observers[i](val);
}
},
get: function () {
return _this.value;
},
});
}
const observer = new Observer(dog, "bark");
observer.Observe(function (value) {
l("Barked");
});
dog.bark = true;
dog.bark = true;
dog.bark = true;
dog.bark = true;
Orgil's answer works only with a single property that needs to be known and encoded. I wanted a solution which works for all properties, including later added. Inspired by his idea to create an observing object, I created a dynamic Proxy that adds another Proxies when needed.
In the following code dog1 serves as proxy: setting its properties modifies the original dog object and logs the assigned value to console.
function AssignProxy(o, fn, path) {
var tree = {};
if(!path) path = "obj";
return new Proxy(o, {
get: (_, prop) => {
if(typeof o[prop] != "object") return o[prop];
if(tree[prop] === undefined) tree[prop] = AssignProxy(o[prop], fn, `${path}.${prop}`);
return tree[prop];
},
set: (_, prop, val) => fn(o[prop] = val, prop, o, path) || 1
});
}
/****** TEST *******/
const dog = {
sounds: {},
name: "Spike"
};
let callback = (val, prop, o, path) => console.log(`assigning ${path}.${prop} to ${val}`)
const dog1 = AssignProxy(dog, callback, "dog1");
dog1.name = "Tyke"; // overwrite property
dog1.age = 4; // create a property
dog1.sounds.howl = "hoooooowl"; // create a deep property
dog1.sounds.howl = {text: "hoowl", pitch: 5000}; // overwrite the deep property
var howl = dog1.sounds.howl; // access by reference
howl.pitch = 6000; // overwrite later added property
console.log(dog); // verify the original object
I can't seem to find the way to overload the [] operator in javascript. Anyone out there know?
I was thinking on the lines of ...
MyClass.operator.lookup(index)
{
return myArray[index];
}
or am I not looking at the right things.
You can do this with ES6 Proxy (available in all modern browsers)
var handler = {
get: function(target, name) {
return "Hello, " + name;
}
};
var proxy = new Proxy({}, handler);
console.log(proxy.world); // output: Hello, world
console.log(proxy[123]); // output: Hello, 123
Check details on MDN.
You can't overload operators in JavaScript.
It was proposed for ECMAScript 4 but rejected.
I don't think you'll see it anytime soon.
The simple answer is that JavaScript allows access to children of an Object via the square brackets.
So you could define your class:
MyClass = function(){
// Set some defaults that belong to the class via dot syntax or array syntax.
this.some_property = 'my value is a string';
this['another_property'] = 'i am also a string';
this[0] = 1;
};
You will then be able to access the members on any instances of your class with either syntax.
foo = new MyClass();
foo.some_property; // Returns 'my value is a string'
foo['some_property']; // Returns 'my value is a string'
foo.another_property; // Returns 'i am also a string'
foo['another_property']; // Also returns 'i am also a string'
foo.0; // Syntax Error
foo[0]; // Returns 1
foo['0']; // Returns 1
Use a proxy. It was mentioned elsewhere in the answers but I think that this is a better example:
var handler = {
get: function(target, name) {
if (name in target) {
return target[name];
}
if (name == 'length') {
return Infinity;
}
return name * name;
}
};
var p = new Proxy({}, handler);
p[4]; //returns 16, which is the square of 4.
We can proxy get | set methods directly. Inspired by this.
class Foo {
constructor(v) {
this.data = v
return new Proxy(this, {
get: (obj, key) => {
if (typeof(key) === 'string' && (Number.isInteger(Number(key)))) // key is an index
return obj.data[key]
else
return obj[key]
},
set: (obj, key, value) => {
if (typeof(key) === 'string' && (Number.isInteger(Number(key)))) // key is an index
return obj.data[key] = value
else
return obj[key] = value
}
})
}
}
var foo = new Foo([])
foo.data = [0, 0, 0]
foo[0] = 1
console.log(foo[0]) // 1
console.log(foo.data) // [1, 0, 0]
As brackets operator is actually property access operator, you can hook on it with getters and setters. For IE you will have to use Object.defineProperty() instead. Example:
var obj = {
get attr() { alert("Getter called!"); return 1; },
set attr(value) { alert("Setter called!"); return value; }
};
obj.attr = 123;
The same for IE8+:
Object.defineProperty("attr", {
get: function() { alert("Getter called!"); return 1; },
set: function(value) { alert("Setter called!"); return value; }
});
For IE5-7 there's onpropertychange event only, which works for DOM elements, but not for other objects.
The drawback of the method is you can only hook on requests to predefined set of properties, not on arbitrary property without any predefined name.
one sneaky way to do this is by extending the language itself.
step 1
define a custom indexing convention, let's call it, "[]".
var MyClass = function MyClass(n) {
this.myArray = Array.from(Array(n).keys()).map(a => 0);
};
Object.defineProperty(MyClass.prototype, "[]", {
value: function(index) {
return this.myArray[index];
}
});
...
var foo = new MyClass(1024);
console.log(foo["[]"](0));
step 2
define a new eval implementation. (don't do this this way, but it's a proof of concept).
var MyClass = function MyClass(length, defaultValue) {
this.myArray = Array.from(Array(length).keys()).map(a => defaultValue);
};
Object.defineProperty(MyClass.prototype, "[]", {
value: function(index) {
return this.myArray[index];
}
});
var foo = new MyClass(1024, 1337);
console.log(foo["[]"](0));
var mini_eval = function(program) {
var esprima = require("esprima");
var tokens = esprima.tokenize(program);
if (tokens.length == 4) {
var types = tokens.map(a => a.type);
var values = tokens.map(a => a.value);
if (types.join(';').match(/Identifier;Punctuator;[^;]+;Punctuator/)) {
if (values[1] == '[' && values[3] == ']') {
var target = eval(values[0]);
var i = eval(values[2]);
// higher priority than []
if (target.hasOwnProperty('[]')) {
return target['[]'](i);
} else {
return target[i];
}
return eval(values[0])();
} else {
return undefined;
}
} else {
return undefined;
}
} else {
return undefined;
}
};
mini_eval("foo[33]");
the above won't work for more complex indexes but it can be with stronger parsing.
alternative:
instead of resorting to creating your own superset language, you can instead compile your notation to the existing language, then eval it. This reduces the parsing overhead to native after the first time you use it.
var compile = function(program) {
var esprima = require("esprima");
var tokens = esprima.tokenize(program);
if (tokens.length == 4) {
var types = tokens.map(a => a.type);
var values = tokens.map(a => a.value);
if (types.join(';').match(/Identifier;Punctuator;[^;]+;Punctuator/)) {
if (values[1] == '[' && values[3] == ']') {
var target = values[0];
var i = values[2];
// higher priority than []
return `
(${target}['[]'])
? ${target}['[]'](${i})
: ${target}[${i}]`
} else {
return 'undefined';
}
} else {
return 'undefined';
}
} else {
return 'undefined';
}
};
var result = compile("foo[0]");
console.log(result);
console.log(eval(result));
You need to use Proxy as explained, but it can ultimately be integrated into a class constructor
return new Proxy(this, {
set: function( target, name, value ) {
...}};
with 'this'. Then the set and get (also deleteProperty) functions will fire. Although you get a Proxy object which seems different it for the most part works to ask the compare ( target.constructor === MyClass ) it's class type etc. [even though it's a function where target.constructor.name is the class name in text (just noting an example of things that work slightly different.)]
So you're hoping to do something like
var whatever = MyClassInstance[4];
?
If so, simple answer is that Javascript does not currently support operator overloading.
Have a look at Symbol.iterator. You can implement a user-defined ##iterator method to make any object iterable.
The well-known Symbol.iterator symbol specifies the default iterator for an object. Used by for...of.
Example:
class MyClass {
constructor () {
this._array = [data]
}
*[Symbol.iterator] () {
for (let i=0, n=this._array.length; i<n; i++) {
yield this._array[i]
}
}
}
const c = new MyClass()
for (const element of [...c]) {
// do something with element
}
I am trying to write code to delete a property in an object. If it is inherited, it must go up the chain and delete itfrom the ancestor.
So far I have come up with this (doesnot work):
// My objects:
var pro = {'a':1};
var pro2 = Object.create(pro);
var pro3 = Object.create(pro2);
// -----------------------------------------------------
function deleteProp(obj, prop){
console.log(obj,prop)
//get own properties
var ownprop = Object.getOwnPropertyNames(obj);
for(var i=0 ;i <ownprop.length; i++){
if (prop === ownprop[i]){
delete obj.ownprop[i];
}
else{
//get the parent
var parent = Object.getPrototypeOf(obj);
console.log(parent);
while (Object.getPrototypeOf(parent)!== Object.prototype){
//recursive call
deleteProp(parent, prop);
}
}
}
}
You don't really need recursion here--a simple while loop will suffice.
function deleteProp(obj, prop) {
while (obj && obj !== Object.prototype) {
delete obj[prop];
obj = Object.getPrototypeOf(obj);
}
}
The check for obj is necessary because an object may have no prototype, for instance if it was created with Object.create(null).
Judging from the output, this should do what you want.
var pro = {'a':1};
var pro2 = Object.create(pro);
var pro3 = Object.create(pro2);
function deleteProp(obj, prop) {
do {
delete obj[prop];
}
while ((obj = Object.getPrototypeOf(obj)));
}
console.log('before', pro.a, pro2.a, pro3.a);
deleteProp(pro3, 'a');
console.log('after', pro.a, pro2.a, pro3.a);
Edit: will this do the trick?
I'm trying to write a linked list using the durable constructor method described in Nicholas Zakas' book but I am running into a conceptual problem.
I understand that the durable constructor pattern is used for security reasons and eschews the use of "this" and "new." However, I am unsure if that means I am unable to create methods that allow for appending nodes to the linked list.
All of the instances I've found have basically been taken straight from Douglas Crockford with really no variation. The example he uses only retrieves information from the object.
My questions are: Does this constructor pattern allow appending data to such data structures? If it doesn't and Crockford says we need to avoid "this" and "new" what options do I have?
edit (linked list code so far):
var linkedList = function (spec) {
var that = {};
that.addNode = function (newNode) {
if (spec.size && spec.root) {
// not sure
} else {
// not sure yet
}
};
that.getRoot = function () {
return spec.root; // for an idea of how to retrieve
};
return that;
};
// how I'd like to use the list
var firstNode = node({val: 25, next: null});
var myList = linkedList({root: firstNode, size: 1});
var secondNode = node({val: 33, next: null});
myList.add(secondNode); // I feel this isn't possible
I'm still quite not sure about the durable object, but I think what you want to do might be this:
var LinkedList = function(val) {
// A private object
var node = {
val: val,
next : null
};
// Method
var getVal = function() {
return node.val;
};
var setVal = function(val) {
node.val = val;
};
var getNext = function() {
return node.next;
};
var setNext = function(newNode) {
// You have to ensure its a node here
if (true) {
node.next = newNode;
}
};
// Append to tail
var appendVal = function(val) {
if (node.next === null) {
node.next = LinkedList(val);
} else {
node.next.appendVal(val);
}
};
var appendNode = function(newNode) {
if (node.next === null) {
node.next = newNode
} else {
node.next.appendNode(newNode);
}
};
var print = function() {
var str = '' + node.val;
if (node.next !== null) {
str += ' => ' + node.next.print();
}
return str;
}
// Only expose method to user.
return {
getVal : getVal,
setVal : setVal,
getNext: getNext,
appendVal: appendVal,
appendNode: appendNode,
print: print
};
};
When we want a root, call var root = LinkedList(val); Then the function will create a hidden object, set the value, and return methods that is able to get/set the object the value for you.
You can do things only with the exposed object, and in this way, the this and new is avoided, and you get a private object that can't be accessed directly.
And what you want to do may be write like this:
var first = LinkedList(25);
var myList = LinkedList(null);
myList.appendNode(first);
var second = LinkedList(33);
myList.appendNode(second);
console.log(myList.print()); // null => 25 => 33
The below :-
req.session.user = doc[0];
req.session.user.password = null;
Makes doc[0].password = null!
What do i do to prevent that?
Edit: Where doc[0] = { name: 'sangram', e-mail: 'abc.in', password : 'pwd' }
The only way to do so is to copy every property of an object:
req.session.user = {};
for (var key in user) {
if (key !== 'password') req.session.user[key] = user[key];
}
If user is a mongoose document, you probably should convert it to plain object first:
req.session.user = {};
var json = user.toObject();
for (var key in json) {
if (key !== 'password') req.session.user[key] = json[key];
}
But the easiest way to do what you want is to adopt some helpful library like underscore or lodash:
req.session.user = _.omit(user.toObject(), 'password');
For simple case of simple objects, I wrote a small helper function:
function ShallowClone(obj) {
var clone = {};
for (var key in obj)
clone[key] = obj[key];
return clone;
}
Usage in your case:
req.session.user = ShallowClone(doc[0]);
req.session.user.password = null; //won't affect doc[0] password
Live test case. (with dummy object)
I would recommend updating or changing your question in the future.
What is the most efficient way to deep clone an object in JavaScript?
How do I correctly clone a JavaScript object?
Crockford provides a very good answer here
You can't prevent this. The user property is set to that object doc[0]. Later you edit .password property of that newly assigned object. If you want to keep the password separately you have to assign doc[0] to another object.
req.session.user = {};
req.session.user.doc = doc[0];
req.session.user.password = null;
Here is a method for cloning/extending an object:
var extend = function() {
var process = function(destination, source) {
for (var key in source) {
if (hasOwnProperty.call(source, key)) {
destination[key] = source[key];
}
}
return destination;
};
var result = arguments[0];
for(var i=1; i<arguments.length; i++) {
result = process(result, arguments[i]);
}
return result;
};
And its usage:
var original = { value: "bla" };
var duplication = extend({}, original, { newProp: "test"});