Issue with javascript array? - javascript

I have a issue with javascript. Please take a look:
function Component(data)
{
var self = this;
self.Name = data.name;
}
function Testing() {
var self = this;
self.Components = [
{
A: new Component({
name: 'test1'
})
}, {
B: new Component({
name: 'test2'
})
}, {
C: new Component({
name: 'test3'
})
}];
}
Now what i am trying to do is, I want to access each component from Components array by its property name (A, B ,C ...). So for this i did and getting error:
var t = new Testing();
t.Components['A'].Name; ==> //Error: Cannot read property 'Name' of undefined
Whats the issue ?

To access the properties of Component using a key self.Components must be an object (associative array). The code attempts to access the array using a key, which is fine for associative arrays, but will not work for a regular array as declared in the code. Switching self.Components to an object literal will allow the properties to be accessed by key.
function Testing() {
var self = this;
self.Components = {
A: new Component({
name: 'test1'
}),
B: new Component({
name: 'test2'
}),
C: new Component({
name: 'test3'
})
};
}
JS FIDDLE: http://jsfiddle.net/fxfbe/

The problem is that Components defined in the Testing is an array, remove array and create simply as object..
function Testing() {
....
self.Components = {
A: new Component({
name: 'test1'
}),
B: new Component({
name: 'test2'
}),
C: new Component({
name: 'test3'
})
};
}

Related

Setting this inside the functions Javascript [duplicate]

This question already has answers here:
How does the "this" keyword work, and when should it be used?
(22 answers)
Closed 3 months ago.
I have the next code:
const obj = {
name: 'Hi',
first: () => {
return this
},
other: {
name: 'last',
sec: function() {
this.c = '2'
return function() {
this.s = '3'
return this; // expect {s:3}
}
}
}
}
const t = obj.other.sec()
console.log(t())
In console.log i expect {s:3} but there is not that object. Question: Why there is not {s:3}? Or function() should have its own context
It's not there because this in JS is dynamic and depends on the scope.
Arrow functions don't have their own scope, so obj.first() will not return you obj but instead returns the global context (in a browser, usually window) or undefined when running in strict mode!
Functions via function do have a dedicated scope. However, when you call obj.other.sec(), the context of this in the sec function points to obj.other, not obj.
The function returned by obj.other.sec() does (or rather: can) create a new scope, since it's called without a context. But since it's also called without new, this points to the global context (see point 1).
Depending on what you want to achieve, the simplest solution is to replace this with the correct context. For example, if you want all function to run in the context of obj, just replace every this with obj:
const obj = {
name: 'Hi',
first: () => {
return obj;
},
other: {
name: 'last',
sec: function() {
obj.c = '2'
return function() {
obj.s = '3'
return obj;
}
}
}
}
const t = obj.other.sec()
console.log(t()) // <-- now logs `obj` with a `c` and an `s` property added
Or maybe you want varying context's:
const obj = {
name: 'Hi',
first: () => {
return obj;
},
other: {
name: 'last',
sec: function() {
obj.other.c = '2'
return function() {
obj.other.s = '3'
return obj.other;
}
}
}
}
const t = obj.other.sec()
console.log(t()) // <-- now logs `obj.other` with a `c` and an `s` property added
And in case obj.other.sec() should return a new object that is not related to obj or obj.other, then... well... just return a new object:
const obj = {
name: 'Hi',
first: () => {
return obj;
},
other: {
name: 'last',
sec: function() {
obj.other.c = '2'
return function() {
return { s: 3 };
}
}
}
}
const t = obj.other.sec()
console.log(t()) // <-- now logs a new object with an `s` property
You can read more about the dynamic nature of this on MDN

Get value of parent Object

Is there any way to get a key-value from an object's parent object? In the example below, I want to combine urlParent with section:
const linkItems = [
{
id: 1,
name: 'Home Page',
urlParent: '/home',
subItems: [
{
subId: 1,
name: 'Project 1',
section: '#project1',
get url() {
//output /home#project1
}
}
]
}
];
console.log(linkItems[0].subItems[0].url) // /home#project1;
You cannot reference an Object parent that way (Object(value) ← Array ← Object), and not even from an Object's parent Object.
What you can do instead is:
Create two Classes, one for the parent and one for the child.
When adding a child to the parent, just make a "linked list", by referencing the parent's this to the created child item parent property
class Child {
constructor(data) {
Object.assign(this, data);
}
get url() {
return this.parent.urlParent + this.section
}
}
class Parent {
constructor(data) {
Object.assign(this, data);
this.subItems = [];
}
addChild(item) {
this.subItems.push(new Child({...item, parent: this}));
}
}
// Example:
const parent = new Parent({id:1, name:"Home", urlParent:"/home"});
parent.addChild({subId:1, name:"Project 1", section:"#project1"});
console.log(parent.subItems[0].url) // /home#project1;
But hey! Nodes and trees
Your original idea and the above use too much complexity.
What I'd suggest is to treat all parent, child, whatever, as Page Nodes.
class Page {
constructor(data) {
Object.assign(this, data);
this.children = {};
}
addChild(page) {
page.parent = this; // Linked to parent!
this.children[page.id] = page;
}
get url() {
// Generate full URI by recursing the parents tree
return this.parent ? `${this.parent.url}/${this.slug}` : this.slug;
}
}
// Example:
// 1. Create pages:
const pageRoot = new Page({id:1, name:"Home page", slug:""});
const pageProj = new Page({id:3, name:"All projects", slug:"projects"});
const pageWebs = new Page({id:4, name:"Websites", slug:"websites"});
const pageStOv = new Page({id:6, name:"Stack Overflow", slug:"so"});
const pageSpec = new Page({id:9, name:"Stack Overflow Specs", slug:"specs"});
// 2. Create Tree:
pageRoot.addChild(pageProj);
pageProj.addChild(pageWebs);
pageWebs.addChild(pageStOv);
pageStOv.addChild(pageSpec);
// 3. Test
console.log(pageRoot.url); // "";
console.log(pageProj.url); // "/projects";
console.log(pageSpec.url); // "/projects/websites/so/specs";
console.log(pageRoot);
const linkItems = [
{
id: 1,
name: 'Home Page',
urlParent: '/home',
get subItems(){
console.log(this.name);
return ([
(() => {
console.log(this);
return {
subId: 1,
name: 'Project 1',
section: '#project1',
urlParentFromOuterScope: () => {
return this.urlParent;
},
sectionString(){
return this.section;
},
url(){
console.log('url', this);
return this.urlParentFromOuterScope() + this.sectionString();
}
}
})()
])
}
}
];
const subItems = linkItems[0].subitems;
console.log(linkItems[0].subItems[0].url());
Please feel free to remove the unnecessary 'console.log's after you understand the approach.
I took the liberty of adding a few methods.
This is a tricky one and has to do with the scope of this in array functions.
P.S.: I guess this can be simplified.

Is There a Object pollution while initializing values in a constructor using Object.keys?

I have an sample class defined as below
class Constants {
constructor(keysObj) {
this.keysObj = keysObj
Object.keys(this.keysObj).forEach((k) => this[k] = keysObj[k])
}
function getInformation() {
console.log("This is default function")
}
}
So when I create a object of above class - I can access the values as below
var newConsts = new Constants({
a: 1,
b: 2,
c: 3
})
// so I can access above values in this way
console.log(newConsts.c) // outputs 3
console.log(newConsts.getInformation) // outputs "This is default function"
Is code is vulnerable to Object Pollution ?
what I observed so far is - if I create a new object like this
var newConsts1 = new Constants({
a: 1,
b: 2,
getInformation: function() {
console.log('This is modifed')
}
})
// so I can access above values in this way
console.log(newConsts1.b) // outputs 2
console.log(newConsts1.getInformation) // outputs "This is modifed"
is Object pollution can be done on this class? if so please let me know what are the different ways to pollute it.
will this have any effect on the code
var newConsts3 = new Constants({
a: 1,
b: 2,
__Proto__: {
toString: function() {
console.log('faulty toString Executed')
}
}
})
var newConsts3 = new Constants({
a: 1,
b: 2,
prototype: {
construtor: undefined
}
}
})
the short answer is YES.
and this is because you are not checking in a safe mode that the keys that you are "initializing" are good or not, therefore, you can send anything to your object and make it vulnerable.

Return object with subset of its attributes

I've got a flat JavaScript object like this:
{
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas',
...
a lot more attributes
}
I'd like to create a new object which only has a subset of the attributes of the original object.
Something like
var newObject = oldObject.fields(['id', 'username']);
newObject would be
{
id: 3726492,
username: 'Nicholas'
}
Is there already something like this?
Try this
function pick(data, keys) {
var result = {};
keys.forEach(function (key) {
if (data.hasOwnProperty(key)) {
result[key] = data[key];
}
});
return result;
}
var data = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas'
}
var newData = pick(data, ['id', 'kind']);
console.log(newData);
In underscorejs or lodash there is method .pick
var data = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas',
};
var newObject = _.pick(data, 'id', 'username');
console.log(newObject);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore.js"></script>
You can use Array.prototype.reduce to reduce one object to another using the list of properties:
function subset(obj, propList) {
return propList.reduce(function(newObj, prop) {
obj.hasOwnProperty(prop) && (newObj[prop] = obj[prop]);
return newObj;
}, {});
}
var obj = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas'
};
var newObj = subset(obj, ['id', 'username']);
console.log(newObj);
document.getElementById('json').innerText = JSON.stringify(newObj);
<pre id="json"></pre>
Not built-in, but you can sure define a simple function that does the job:
var original = {a:1112, b:434, c:666, d:222};
function fieldSubset(obj, fields) {
var subsetClone = {};
for( var i=0,l=fields.length; i<l; i++) {
// This can prevent filling undefined as properties
if(obj.hasOwnProperty(fields[i])) {
subsetClone[fields[i]] = obj[fields[i]];
}
}
return subsetClone;
}
fieldSubset(original, ["a", "c"]);
You can also use this in Object.prototype, but be aware that this might happen to conflict with native API in the future versions of JavaScript:
var original = {a:1112, b:434, c:666, d:222};
Object.defineProperty(Object.prototype, "fieldSubset", {
value: function(fields) {
var subsetClone = {};
for( var i=0,l=fields.length; i<l; i++) {
// This can prevent filling undefined as properties
if(this.hasOwnProperty(fields[i])) {
subsetClone[fields[i]] = this[fields[i]];
}
}
return subsetClone;
},
enumerable: false,
configurable: true}
);
original.fieldSubset(["a", "c"]);
One liner using Array.prototype.reduce. We are also using Object.assign. The idea is to keep extending a blank object with the keys found in the filters array. If you see, the reduce function takes a callback function with arg1,arg2,arg3 params as the first argument and an empty object as the second argument. This object will be cloned and extended with the help of the keys specified in the filters array.
var a = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas',
};
var filters = ["id","username","permalink"];
var sub = Object.keys(a).reduce((arg1,arg2,arg3)=>{ var res = {}; if(filters.indexOf(arg2)>=0){ res[arg2] = a[arg2]; } return Object.assign(arg1,res);},{})
console.log(sub);
You haven't specifically mentioned what is the type of values behind your object's keys. Your current answers cover the shallow copy and deep copy.
Another alternative would be to create a view of the original object. This would be helpful if you have very large data objects and you do not want them copy in the memory.
function View(obj, properties) {
var view = {};
properties.forEach(function(prop) {
Object.defineProperty(view, prop, {
get: function() {
return obj[prop];
},
set: function(val) {
obj[prop] = val;
},
enumerable: true,
configurable: true
});
});
return view;
}
then with your data you can do:
var data = {
id: 3726492,
kind: 'user',
permalink: 'nicholas',
username: 'Nicholas',
},
view = new View(data, ['id', 'username']);
view.id; // 3736492
view.username; // Nicholas
of course you have to be aware that you can change your original object just by view.id = 'something else'. However it is easily preventable.

Integrating native JavaScript classes in an Angular app

I have a native JavaScript class:
var Holder = new function(elements) {
this.elements = elements;
this.anyFunction() {
// use of this.elements
};
};
How to use it in an Angular-way? For example, if I would like to use:
.controller('AnyController', ['Holder',
function (Holder) {
var elements = [
{id: 1, label: 'foo'},
{id: 2, label: 'bar'}
];
$scope.holder = new Holder(elements);
}])
How should I register my Holder class then? What are the options (if any)?
In parallel, is it that bad to use native JavaScript classes in an Angular app (i.e. without integrating it within the framework)?
You could return a class with a factory
.factory('Holder', function() {
return (function (){
this.foo = foo;
this.bar = bar;
});
});
Now to use it
.controller('AnyController', ['Holder', function (Holder) {
var holder = new Holder();
}]);
EDIT
Use a factory instead of a service, as suggested in the comments
As I understand it, a factory is a singleton, but a factory can generate a class that can create instances. So the factory would return a reference to the constructor when you inject it, or a wrapper function around the constructor to use it without using new:
.factory('Holder', function() {
function Holder(elements) {
this.elements = elements;
}
Holder.prototype.get = function() {
return this.elements;
};
return function(elements) {
return new Holder(elements);
};
})
.controller('Main', function($scope, Holder) {
var elements = [
{id: 1, label: 'foo'},
{id: 2, label: 'bar'}
];
$scope.elements = Holder(elements).get();
});

Categories

Resources