I've been using dean edwards base.js (http://dean.edwards.name/weblog/2006/03/base/) to organise my program into objects ( base.js is amazing btw, if you havent used it before !).Anyway, my question is generic and you don't have to know base.js to know my answer.
I have a property in one of my objects called ref which is a reference to a DOM element, and this object is meant to be saved as JSON using JSON.stringify, but as you can imagine since DOM elements are circular structure, I won't be able to convert the object into JSON.
Now to get around this problem I have a method called html() which is meant to return the ref property, but I need to have ref as a private property which is only accessible from within the object, and hence won't be sent to stringify.
What's the best way to do that?
You probably know that you cannot have private properties in JavaScript.
Interestingly, if you pass an object to JSON.stringify which has a method toJSON, JSON.stringify will automatically call that method to get a JSONable representation of that object. So all you have to do is implement this method.
For example you can create a shallow copy of the object which only contains the properties you want to copy:
MyConstructor.prototype.toJSON = function() {
var copy = {},
exclude = {ref: 1};
for (var prop in this) {
if (!exclude[prop]) {
copy[prop] = this[prop];
}
}
return copy;
};
DEMO
Another way would be to use a custom replacer function, but it might be more difficult to control which ref to exclude and which one to keep (if different objects have ref properties):
JSON.stringify(someInstance, function(key, value) {
if(key !== 'ref') {
return value;
}
});
DEMO
here is sample to to set variable visibility
function Obj(){
this.ref = 'public property'; // this property is public from within the object
var ref = 'private proerty'; // this property is private.
var self = this;
this.showRef = function(){
alert(ref);
alert(self.ref);
};
}
var obj = new Obj();
obj.showRef();
Related
In my component i have declarated some data like this:
data() {
return {
defaultValue: {json object with some structure},
activeValue: {}
...
And in component methods a make copy this value:
this.activeValue = this.defaultValue
But problem is, after change this.activeValue value a have changes in this.defaultValue too.
If i use Object.freeze(this.defaultValue) and trying change this.activeValue i have get error - object is not writable.
How i can make copy of data but without reference?
If you have simple object, quickest and easiest way is to just use JSON.parse and JSON.stringify;
const obj = {};
const objNoReference = JSON.parse(JSON.stringify(obj));
this.activeValue = { ...this.defaultValue }
Using an ES6 spread operator will help you to do a copy if you do not have a nested object. If you equate using equal = sign, it will not create a new object, it will just create a variable with the reference to the current object (like a shallow copy).
To do a complete deep copy, even it is nested object, go for this:
const objNoReference = JSON.parse(JSON.stringify(obj));
as suggested by Owl.
Click to read more for better understanding of the concept
A nicer way rather than using JSON.parse, JSON.stringify is:
this.activeValue = {...this.defaultValue}
but this is not natively supported by some browser (IE), unless used with a transpiler (babel)
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
Update
Considering your originial question is about a way in Vue, there is also a native method in vue:
this.activeValue = Vue.util.extend({}, this.defaultValue)
as for this answer.
Hope this helps!
Objects are assigned and copied by reference.
All operations via copied references (like adding/removing properties) are performed on the same single object.
To make a “real copy” (a clone) we can use Object.assign for the so-called “shallow copy” (nested objects are copied by reference).
For “deep cloning” use _.cloneDeep(obj) from loadash library.
JSON stringify&parse method have some issues like converting date objects to strings. It also cannot handle special data types like Map,Set,function etc... This is prone to future bugs.
I use the following method to deep copy an object.
REMEMBER! this is not a complete application of cloning. There are more data types to handle like Blob, RegExp etc...
const deepClone = (inObject) => {
let outObject, value, key
if (typeof inObject !== "object" || inObject === null)
return inObject
if (inObject instanceof Map) {
outObject = new Map(inObject);
for ([key, value] of outObject)
outObject.set(key, deepClone(value))
} else if (inObject instanceof Set) {
outObject = new Set();
for (value of inObject)
outObject.add(deepClone(value))
} else if (inObject instanceof Date) {
outObject = new Date(+inObject)
} else {
outObject = Array.isArray(inObject) ? [] : {}
for (key in inObject) {
value = inObject[key]
outObject[key] = deepClone(value)
}
}
return outObject
}
You can use 'JSON.parse and stringify' or using some clone function in the libs like lodash (underscore, ramda...)
Also a simple solution is to store defaultValue: {json object with some structure} with JSON.stringify(defaultValue) in a string variable:
var x = JSON.stringify(this.defaultValue);
If you need it as JSON object again you can get it with JSON.parse():
var newObject = JSON.parse(x);
The object reference is also broken doing it this way, x will stay unchanged if the content of the object defaultValue is altered.
So I have been looking at Object.freeze() and Object.seal().
Object.freeze() - will make all existing properties non-writable, and will not allow any new properties to be added.
Object.seal() - "Sealing an object prevents new properties from being added and marks all existing properties as non-configurable."
I am looking for a way to make all existing properties "frozen" (non-writable), but allow new properties to be added.
Is there shorthand for doing that?
The manually way of doing what I want is:
let freezeExistingProps = obj => {
Object.keys(obj).forEach(k => {
Object.defineProperty(obj, k, {
writable: false
});
});
};
The above function works surprisingly well to freeze existing top-level properties on an object (it doesn't overwrite them, just changes them to non-writable), but I am hoping there might be a more official/quicker way to do the above.
You might do the following:
instance -> frozen static proto -> dynamic proto
Some sample:
function freeze(stat,dyn){
Object.setPrototypeOf(stat,dyn);
Object.freeze(stat);
}
var a={unchangeable:1};
var b={changeable:2}
freeze(a,b);
Now have a look at a and change some b props.
Well, if you want to do it in the manner of freeze, then freezing it immediately, and setting up to a prototype of another object might help, but it will return a copy (pointing to the original object as prototype), exactly in the form how you want. there are obviously some pros and cons, as the properties will not be the immediate properties, but we can find it out by its __proto__ if we need all the keys (assuming you have a dedicated use case)
So, just another try
function freezeExistingProps (obj){
var OBJECT = function(){};
Object.freeze(obj)
OBJECT.prototype = obj;
return new OBJECT();
}
You may want to consider cloning your object into a new one with extra attribute. It's also a very good practice (look for immutability).
An example:
const setAge = (person, age) => ({ ...person, age });
const person = {
firstName: 'Luke',
lastName: 'Skywalker',
};
const personWithAge = setAge(person, 24);
This question already has answers here:
Get property of object in JavaScript
(3 answers)
Closed 6 years ago.
I am trying to access a property within an object and return it.
Note the name of the object can change, so accessing it using title_can_change.property will not work.
Take the following object:
{
"title_can_change":{
property: 1
}
}
How do I return the value of 'property'?
MORE INFO:
This object is being returned from a search which contains an array of results from several API's. This object is returned to detail the API the result has come from (the search goes through several API's).
So this would be more like:
apiData:{
"apiName":{
price: 500
}
}
"apiName" is not fixed and changes to say the name of the API. So it cannot be referenced by "apiName' in any of the code.
You could create a function to return the value of the property of whatever object you pass to it:
var getProperty = function(obj) {
return obj.property;
};
If you have an object and you want to access it's first key's value you can go with something like this (dirty example):
var myObj = {
"title_can_change"{
property: 1
}
}
myObj[Object.keys(myObj)[0]];
I home this helps.
So I think I know what you are wanting and here is a low-level solution that may prove valuable and show you some neat things about the language.
https://jsfiddle.net/s10pg3sh/1/
function CLASS(property) {
// Set the property on the class
this.property = property || null;
}
// Add a getter for that property to the prototype
CLASS.prototype.getProperty = function() {
return this['property'];
}
var obj = new CLASS(9);
alert(obj.getProperty());
What you are doing in the above is creating a "class" for your new variable. When you create your object with the new keyword as show you still get your normal JS object (everything is an object anyways), but you get the convenience of the getProperty method. This being on the prototype insures that any new CLASS variables you create will have that method. You can change the variable names and you will always have access to that property by having access to the instance (variable). This is a very common OO paradigm and is the strength of a prototypical language so while it may be over the top for your needs I figured I would add it as an answer here...
If this object is global, then you can use window['title_can_change']['property']. If it's just another property of another object (called another_one) you can access it with another_one['title_can_change']['property'].
Furthermore: You can have a variable (let's call it title_name) to ease the call:
....
var title_name = "title_can_change";
var myObj = window[title_name]['property'];
title_name = "title_has_changed";
var anotherObj = window[title_name]['property'];
.....
OR
....
var title_name = "title_can_change";
var myObj = another_one[title_name]['property'];
title_name = "title_has_changed";
var anotherObj = another_one[title_name]['property'];
.....
Kind of a guess here since the question is still somewhat unclear. I am assuming that you want to iterate over an objects properties whose names are unknown.
This might be a solution.
for (var property in object) {
if (object.hasOwnProperty(property)) {
// check objects properties for existence
// do whatever you want to do
}
}
Use for...in to iterate over the keys of the object and check if your inner object contains your property
var o = {
"title_can_change":{
property: 1
}
};
for (var k in o) {
if (o.hasOwnProperty(k) && o[k].property) {
return o[k].property;
}
}
In one repo I saw a line.
var foo = JSON.parse(JSON.stringify(foo));
I think this is trying to strip any methods off the object. I can't really see it doing anything else. Is there a more efficient way to attempt this? Does node optimize this?
In the code context you have now disclosed, this technique is being used to make a copy of an object passed to a function so that modifications to that object will not modify the original. Here's the context from your link:
// route reply/error
this.connection.on('message', function(msg) {
var msg = JSON.parse(JSON.stringify(msg));
var handler;
if (msg.type == constants.messageType.methodReturn || msg.type == constants.messageType.error) {
handler = self.cookies[msg.replySerial];
if (msg.type == constants.messageType.methodReturn && msg.body)
msg.body.unshift(null); // first argument - no errors, null
if (handler) {
delete self.cookies[msg.replySerial];
var props = {
connection: self.connection,
bus: self,
message: msg,
signature: msg.signature
};
if (msg.type == constants.messageType.methodReturn)
handler.apply(props, msg.body); // body as array of arguments
else
handler.call(props, msg.body); // body as first argument
}
Note: the line in this clip that contains msg.body.unshift(null). That would be modifying the original object if this copy was not made.
Also, note that redeclaring var msg is not actually defining a new variable. Since msg is already defined in this scope as the function argument, it is not redeclared by the var msg (this is technically a mistake in the code to use the var).
The usual reason for using this type of code is to clone an object (make a deep copy of the object where all properties including embedded objects and arrays are copied).
var obj = {
list: [1,2,3],
items: [{language: "English", greeting: "hello"},
{language: "Spanish", greeting: "hola"},
{language: "French", greeting: "bonjour"}]
}
// make a completely independent copy of obj
var copy = JSON.parse(JSON.stringify(obj));
copy.items[0].greeting = "Yo";
console.log(obj.items[0].greeting); // "hello"
console.log(copy.items[0].greeting); // "Yo"
Note: this only works as a full copy with objects that are a plain Object type and do not have custom properties that are functions. And, because JSON.stringify() does not support circular references or self references, you can't have any of those. And, if you have multiple references to the same object, each reference will be copied to a new separate object. And, the combination of JSON.stringify() and JSON.parse() don't support objects other than a plain Object such as RegExp, Date or any of your own custom objects (they turn them into plain Objects). So, there are some limitations of this procedure, but it works quite simply for the majority of cases.
Per Matt (in comments), a custom function to create a clone of an object that does support circular references and does support some types of custom objects can be seen here.
In case anyone reading this doesn't realize, assigning an object to another variable does not make a copy in Javascript. The assignment in Javascript is like setting a pointer reference to the same object. Each variable then points to the same underlying object so modifying the object through either variable ends up modifying the same object in both cases like this:
var obj = {
list: [1,2,3],
items: [{language: "English", greeting: "hello"},
{language: "Spanish", greeting: "hola"},
{language: "French", greeting: "bonjour"}]
}
var copy = obj;
// modify copy
copy.items[0].greeting = "Yo";
// both obj and copy refer to the exact same object
console.log(obj.items[0].greeting); // "Yo"
console.log(copy.items[0].greeting); // "Yo"
Thus, the occasional need to make an actual deep copy of an object.
If removing methods from the prototype is what you want, consider creating a new object and transferring over all of the old object's properties.
If stripping out properties that are a function is what you want, loop through the object and check if the property is a function:
for(key in ob)
{
if(typeof ob[key] === 'function')
{
delete ob[key];
}
}
Or perhaps what you hope to accomplish is the combination of them both.
I'm working on an AngularJS SPA and I'm using prototypes in order to add behavior to objects that are incoming through AJAX as JSON. Let's say I just got a timetable x from an AJAX call.
I've defined Timetable.prototype.SomeMethod = function() and I use https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf in order to set the prototype of x to TimeTable.prototype. I have the polyfill in place too.
If I call x.SomeMethod() this works in IE > 9, FF, Chrome etc. However, IE 9 gives me a headache and says throws an error stating 'x does not have property or member SomeMethod'.
Debugging in IE shows me that the _proto_ of x has SomeMethod() in the list of functions, however, calling x.SomeMethod() gives the same error as described.
How can I make this work in IE9 ?
More comment than answer
The main problem with "extending" a random object retrieved from some other environment is that javascript doesn't really allow random property names, e.g. the random object may have a property name that shadows an inherited property. You might consider the following.
Use the random object purely as data and pass it to methods that access the data and do what you want, e.g.
function getName(obj) {
return obj.name;
}
So when calling methods you pass the object to a function that acts on the object and you are free to add and modify properties directly on the object.
Another is to create an instance with the methods you want and copy the object's properties to it, but then you still have the issue of not allowing random property names. But that can be mitigated by using names for inherited properties that are unlikely to clash, e.g. prefixed with _ or __ (which is a bit ugly), or use a naming convention like getSomething, setSomething, calcLength and so on.
So if obj represents data for a person, you might do:
// Setup
function Person(obj){
for (var p in obj) {
if (obj.hasOwnProperty(p)) {
this[p] = obj[p];
}
}
}
Person.prototype.getName = function(){
return this.name;
};
// Object generated from JSON
var dataFred = {name:'fred'};
// Create a new Person based on data
var p = new Person(dataFred);
You might even use the data object to create instances from various consructors, e.g. a data object might represent multiple people, or a person and their address, which might create two related objects.
This is how I solved it at the end:
Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
if (!isIE9()) {
obj.__proto__ = proto;
} else {
/** IE9 fix - copy object methods from the protype to the new object **/
for (var prop in proto) {
obj[prop] = proto[prop];
}
}
return obj;
};
var isIE9 = function() {
return navigator.appVersion.indexOf("MSIE 9") > 0;
};