Object.create() confusion - how do you access/destroy the object? - javascript

If I have the following code:
const person = {
name: "Tom",
isHuman: true,
kill() {
// do stuff
}
};
Object.create(person);
How would I access the object or remove/destroy it? I understand I could do something like:
const me = Object.create(person);
me.name = "Bob";
..but that's not what I'm looking for. I don't want to assign it.
Likewise, how would I access the method kill() without first assigning it (e.g. me.kill();?

you can easily delete a created object without var let or const :
const person = {
name: "Tom",
isHuman: true,
};
me = Object.create(person);
delete me
// `me` is removed, and become not defined
// console.log(me) throws an error.
Javascript use a garbage collector, you can use it to delete objects. An object, or a data not referenced, will be deleted :
const person = {
name: "Tom",
isHuman: true,
};
let me = Object.create(person);
me = null
// { name: "Tom", ... } become unreachable, garbage remove it from the memory.
When you use const, you can't assign a new value, you can't delete this value/object/data. But you can clear it with something like this :
const person = {
name: "Tom",
isHuman: true,
kill() {
delete this.name
delete this.isHuman
}
};
const me = Object.create(person);
console.log(person.name) // "Tom"
// delete me // delete nothing
// me = null // throws an error, because you can't assign new value to a const
person.kill()
console.log(person.name) // undefined
console.log(person.isHuman) // undefined
An nice article about javascript's garbage collector
Var, Let ou Const

When you:
const person = {}
you are creating the object(and not a class) person. So to access the method kill you only need to
person.kill();
But i think you want create a class, in this case you can:
class Person {
name = "Tom";
isHuman = true;
kill(){
}
}
const me = new Person();
me.name = "Bob";
me.kill();
Or without using class you can
const createPerson = () => ({
name: "Tom",
isHuman: true,
kill: () => {
//do stuff here
}
});
const me = createPerson();
me.name = "Bob";
me.kill();

Related

Print getters when and object is printed in typescript

Is there an option in TypeScript/JavaScript to print an object who has private properties using their getters instead of printing the private properties names.
By example I have this class in TypeScript
class Vehicle {
constructor(private _brand: string, private _year: number) {}
get brand(): string {
return this._brand;
}
get year(): number {
return this._year;
}
set year(year: number) {
this._year = year;
}
set brand(brand: string) {
this._brand = brand;
}
}
const vehicle: Vehicle = new Vehicle('Toyota', 10);
console.log(vehicle);
I got this
[LOG]: Vehicle: {
"_brand": "Toyota",
"_year": 10
}
But I'm wondering if I can get something like this
[LOG]: Vehicle: {
"brand": "Toyota",
"year": 10
}
What console.log does varies by environment. If you want to do what you're describing, you'd have to write your own logger function instead, for instance (in JavaScript, but types are fairly easily added) see comments:
function log(obj) {
// Get the names of getter properties defined on the prototype
const ctor = obj.constructor;
const proto = ctor?.prototype;
const names = new Set(
proto
? Object.entries(Object.getOwnPropertyDescriptors(proto))
.filter(([_, {get}]) => !!get)
.map(([name]) => name)
: []
);
// Add in the names of "own" properties that don't start with "_"
for (const name of Object.keys(obj)) {
if (!name.startsWith("_")) {
names.add(name);
}
}
// Create a simple object with the values of those properties
const simple = {};
for (const name of names) {
simple[name] = obj[name];
}
// See if we can get a "constructor" name for it, apply it if so
let objName =
obj[Symbol.toStringTag]
|| ctor?.name;
if (objName) {
simple[Symbol.toStringTag] = objName;
}
// Log it
console.log(simple);
}
Live Example:
"use strict";
function log(obj) {
// Get the names of getter properties defined on the prototype
const ctor = obj.constructor;
const proto = ctor?.prototype;
const names = new Set(
proto
? Object.entries(Object.getOwnPropertyDescriptors(proto))
.filter(([_, {get}]) => !!get)
.map(([name]) => name)
: []
);
// Add in the names of "own" properties that don't start with "_"
for (const name of Object.keys(obj)) {
if (!name.startsWith("_")) {
names.add(name);
}
}
// Create a simple object with the values of those properties
const simple = {};
for (const name of names) {
simple[name] = obj[name];
}
// See if we can get a "constructor" name for it, apply it if so
let objName =
obj[Symbol.toStringTag]
|| ctor?.name;
if (objName) {
simple[Symbol.toStringTag] = objName;
}
// Log it
console.log(simple);
}
class Vehicle {
constructor(_brand, _year) {
this._brand = _brand;
this._year = _year;
}
get brand() {
return this._brand;
}
get year() {
return this._year;
}
set year(year) {
this._year = year;
}
set brand(brand) {
this._brand = brand;
}
}
const vehicle = new Vehicle('Toyota', 10);
log(vehicle);
Lots of room to tweak that how you like it, that's just a sketch of how you might go about it.
I don't think there is a way to do that, but you could create a log method in the class, like this:
log() {
console.log({
brand: this.brand,
year: this.year,
});
}
And then simply call vehicle.log();
You'd then get a log like this {brand: 'Toyota', year: 10}

Assign dynamically nested array of classes

I need to be able to receive data from an external API and map it dynamically to classes. When the data is plain object, a simple Object.assign do the job, but when there's nested objects you need to call Object.assign to all nested objects.
The approach which I used was to create a recursive function, but I stumble in this case where there's a nested array of objects.
Classes
class Organization {
id = 'org1';
admin = new User();
users: User[] = [];
}
class User {
id = 'user1';
name = 'name';
account = new Account();
getFullName() {
return `${this.name} surname`;
}
}
class Account {
id = 'account1';
money = 10;
calculate() {
return 10 * 2;
}
}
Function to initialize a class
function create(instance: object, data: any) {
for (const [key, value] of Object.entries(instance)) {
if (Array.isArray(value)) {
for (const element of data[key]) {
// get the type of the element in array dynamically
const newElement = new User();
create(newElement, element)
value.push(newElement);
}
} else if (typeof value === 'object') {
create(value, data[key]);
}
Object.assign(value, data);
}
}
const orgWithError = Object.assign(new Organization(), { admin: { id: 'admin-external' }});
console.log(orgWithError.admin.getFullName()); // orgWithError.admin.getFullName is not a function
const org = new Organization();
const data = { id: 'org2', admin: { id: 'admin2' }, users: [ { id: 'user-inside' }]}
create(org, data);
// this case works because I manually initialize the user in the create function
// but I need this function to be generic to any class
console.log(org.users[0].getFullName()); // "name surname"
Initially I was trying to first scan the classes and map it and then do the assign, but the problem with the array of object would happen anyway I think.
As far as I understand from your code, what you basically want to do is, given an object, determine, what class it is supposed to represent: Organization, Account or User.
So you need a way to distinguish between different kinds of objects in some way. One option may be to add a type field to the API response, but this will only work if you have access to the API code, which you apparently don't. Another option would be to check if an object has some fields that are unique to the class it represents, like admin for Organization or account for User. But it seems like your API response doesn't always contain all the fields that the class does, so this might also not work.
So why do you need this distinction in the first place? It seems like the only kind of array that your API may send is array of users, so you could just stick to what you have now, anyway there are no other arrays that may show up.
Also a solution that I find more logical is not to depend on Object.assign to just assign all properties somehow by itself, but to do it manually, maybe create a factory function, like I did in the code below. That approach gives you more control, also you can perform some validation in these factory methods, in case you will need it
class Organization {
id = 'org1';
admin = new User();
users: User[] = [];
static fromApiResponse(data: any) {
const org = new Organization()
if(data.id) org.id = data.id
if(data.admin) org.admin = User.fromApiResponse(data.admin)
if(data.users) {
this.users = org.users.map(user => User.fromApiResponse(user))
}
return org
}
}
class User {
id = 'user1';
name = 'name';
account = new Account();
getFullName() {
return `${this.name} surname`;
}
static fromApiResponse(data: any) {
const user = new User()
if(data.id) user.id = data.id
if(data.name) user.name = data.name
if(data.account)
user.account = Account.fromApiResponse(data.account)
return user
}
}
class Account {
id = 'account1';
money = 10;
calculate() {
return 10 * 2;
}
static fromApiResponse(data: any) {
const acc = new Account()
if(data.id) acc.id = data.id
if(data.money) acc.money = data.money
return acc
}
}
const data = { id: 'org2', admin: { id: 'admin2' }, users: [ { id: 'user-inside' }]}
const organization = Organization.fromApiResponse(data)
I can't conceive of a way to do this generically without any configuration. But I can come up with a way to do this using a configuration object that looks like this:
{
org: { _ctor: Organization, admin: 'usr', users: '[usr]' },
usr: { _ctor: User, account: 'acct' },
acct: { _ctor: Account }
}
and a pointer to the root node, 'org'.
The keys of this object are simple handles for your type/subtypes. Each one is mapped to an object that has a _ctor property pointing to a constructor function, and a collection of other properties that are the names of members of your object and matching properties of your input. Those then are references to other handles. For an array, the handle is [surrounded by square brackets].
Here's an implementation of this idea:
const create = (root, config) => (data, {_ctor, ...keys} = config [root]) =>
Object.assign (new _ctor (), Object .fromEntries (Object .entries (data) .map (
([k, v]) =>
k in keys
? [k, /^\[.*\]$/ .test (keys [k])
? v .map (o => create (keys [k] .slice (1, -1), config) (o))
: create (keys [k], config) (v)
]
: [k, v]
)))
class Organization {
constructor () { this.id = 'org1'; this.admin = new User(); this.users = [] }
}
class User {
constructor () { this.id = 'user1'; this.name = 'name'; this.account = new Account() }
getFullName () { return `${this.name} surname`}
}
class Account {
constructor () { this.id = 'account1'; this.money = 10 }
calculate () { return 10 * 2 }
}
const createOrganization = create ('org', {
org: { _ctor: Organization, admin: 'usr', users: '[usr]' },
usr: { _ctor: User, account: 'acct' },
acct: { _ctor: Account }
})
const orgWithoutError = createOrganization ({ admin: { id: 'admin-external' }});
console .log (orgWithoutError .admin .getFullName ()) // has the right properties
const data = { id: 'org2', admin: { id: 'admin2' }, users: [ { id: 'user-inside' }]}
const org = createOrganization (data)
console .log (org .users [0] .getFullName ()) // has the right properties
console .log ([
org .constructor .name,
org .admin .constructor.name, // has the correct hierarchy
org .users [0]. account. constructor .name
] .join (', '))
console .log (org) // entire object is correct
.as-console-wrapper {min-height: 100% !important; top: 0}
The main function, create, receives the name of the root node and such a configuration object. It returns a function which takes a plain JS object and hydrates it into your Object structure. Note that it doesn't require you to pre-construct the objects as does your attempt. All the calling of constructors is done internally to the function.
I'm not much of a Typescript user, and I don't have a clue about how to type such a function, or whether TS is even capable of doing so. (I think there's a reasonable chance that it is not.)
There are many ways that this might be expanded, if needed. We might want to allow for property names that vary between your input structure and the object member name, or we might want to allow other collection types besides arrays. If so, we probably would need a somewhat more sophisticated configuration structure, perhaps something like this:
{
org: { _ctor: Organization, admin: {type: 'usr'}, users: {type: Array, itemType: 'usr'} },
usr: { _ctor: User, account: {type: 'acct', renameTo: 'clientAcct'} },
acct: { _ctor: Account }
}
But that's for another day.
It's not clear whether this approach even comes close to meeting your needs, but it was an interesting problem to consider.

Modify JS Object in Pure Way

I have a function that transforms a JS object. It derives a new property from an existing one, then deletes the original property. Essentially something like this:
/** Derives "capName" property from "name" property, then deletes "name" */
function transform(person) {
person["capName"] = person["name"].toUpperCase();
delete person["name"];
return person;
}
var myPerson = {
name: "Joe",
age: 20
};
var newPerson = transform(myPerson);
console.log(myPerson, newPerson);
The function returns the desired newPerson object, but also modifies the original myPerson object. I would rather do this in a pure way, that doesn't modify the original myPerson object.
Note: I really need to be ES5 compatible, but I'd like to see the ES6 solution as well.
Quite easy with destructuring:
const transform = ({ name, ...rest }) => ({ capName: name.toUpperCase(), ...rest });
I really need to be ES5 compatible
Use BabelJS, it makes your life so much eaiser.
just use Object.assign which will create a new object with new reference
function transform(person) {
var obj = Object.assign({}, person);
obj["capName"] = obj["name"].toUpperCase();
delete obj["name"];
return obj;
}
var myPerson = {
name: "Joe",
age: 20
};
var newPerson = transform(myPerson);
console.log('newPerson:', newPerson);
console.log('myPerson:', myPerson);
For ES5 compatibility, you can use JSON.parse(JSON.stringify(person)). Be aware that methods attached to person are lost on the way because they cannot properly be JSON.stringifyed.
/** Derives "capName" property from "name" property, then deletes "name" */
function transform(person) {
var obj = JSON.parse(JSON.stringify(person));
obj["capName"] = obj["name"].toUpperCase();
delete obj["name"];
return obj;
}
var myPerson = {
name: "Joe",
age: 20
};
var newPerson = transform(myPerson);
console.log(myPerson, newPerson);
If you want to retain methods, just iterate over the object keys:
/** Derives "capName" property from "name" property, then deletes "name" */
function transform(person) {
var obj = {};
for (var key in person) {
obj[key] = person[key];
}
obj["capName"] = obj["name"].toUpperCase();
delete obj["name"];
return obj;
}
var myPerson = {
name: "Joe",
age: 20
};
var newPerson = transform(myPerson);
console.log(myPerson, newPerson);
Be aware that none of the methods presented does a deep clone. For that, I'd recommend you use something like lodash's _.clone(obj, { deep: true });
You could generate a new object without the unwanted and a new property.
function transform(person) {
return Object
.keys(person)
.reduce(function (r, k) {
if (k === 'name') {
r.capName = person.name.toUpperCase();
} else {
r[k] = person[k];
}
return r;
}, {});
}
var myPerson = { name: "Joe", age: 20 },
newPerson = transform(myPerson);
console.log(myPerson);
console.log(newPerson);

How to add element key & value to object?

I am trying to add key value from array to person object, i mocked below code similar approach it is coming undefined object when we assign key/value pair to object. What would be right approach to achieve this task ?
main.js
const person = {
Name: "John klmeni"
age: 29
}
const address = [{address: '111 main st"}]
for (let obj in person) {
address.forEach(element ,==> {
obj[key] = element.key
}
}
I think you want to do the following?
const person = {
Name: "John klmeni",
age: 29
}
const address = [{address: '111 main st'}];
const newPerson = address.reduce(
(result,item)=>
Object.assign({},result,item),
person
);
console.log(newPerson);

A more aesthetic way of copying an object literal?

This is client side. Webpack, Babel and Babel Imports.
My project has a folder called "models" which contains object literals as definitions of the expected JSON results from endpoints.
The objects only contain strings, ints, booleans and arrays/objects which contain those data types
eg:
{
name: "String"
age: 35,
active: true,
permissions: [
{ news: true }
]
}
When I want to use a model definition, in order to ensure I don't have issues with references, I must use:
let newObject1 = Object.assign({}, originalObj )
or
let newObject2 = JSON.parse( JSON.stringify( originalObj ))
I find this a bit ugly and it pollutes my code a bit.
I would love the ability to use the new keyword on object literals, but of course that's not a thing.
let clone = new targetObj
What's the most aesthetic way to handle the cloning of an object literal without creating a reference?
The JavaScript way of implementing such object "templates" are constructors:
function Original() {
this.name = "String";
this.age = 18;
this.active = true;
this.permissions = [
{ news: true }
];
}
var obj = new Original();
console.log(obj);
Or, in ES6 class syntax:
class Original {
constructor() {
this.name = "String";
this.age = 18;
this.active = true;
this.permissions = [
{ news: true }
];
}
}
let obj = new Original();
console.log(obj);
Be aware that Object.assign will only create a shallow copy, so it would not copy the permissions array, but provide a reference to the same array as in the original object.
const originalObject = {
name: "String",
age: 35,
active: true,
permissions: [
{ news: true }
]
};
let obj1 = Object.assign({}, originalObject);
let obj2 = Object.assign({}, originalObject);
// change a permission:
obj1.permissions[0].news = false;
// See what permissions are in obj2:
console.log(obj1.permissions);
const model = () => ({
name: 'string',
age: 20,
array: [ 1, 2, 3 ]
});
let newObject = model();
You won't have the pleasure of using new - see trincot's answer for that - but you don't have to worry about nested objects (assign) or feel gross (stringify + parse).

Categories