How to change a vue data variable using this and (multiple) dynamic bracket notations - javascript

I am trying to achieve the following:
I start of with a p element that contains the data variable "reportText.Scope_1_T_1", this variable contains the string: "Text to change";
On creation of this component, created() gets called and it fires off a call to the method createObject. The method createObject requires multiple arguments, but the only relevant argument is the second one, which includes the location of the data variable I want to change (in this case: "reportText.Scope_1_T_1");
The method createObject splits this argument/location based on the dots and returns an array. So the string "reportText.Scope_1_T_1" returns the array ["reportText", "Scope_1_T_1"];
Following that this array gets looped through and combined with the context (=this). First loop results in context = this["reportText"], second loop returns in context = this["reportText"]["Scope_1_T_1"].
After this I assign a new String to context (context = reply.fields)
My expectation was that this code would result in a change of the data variable this.reportText.Scope_1_T_1, but unfortunately nothing happens to this variable.
I have tried playing around with dot notation and bracket notation, but nothing really worked. For example if I try to change the code in my createObject method to this:
this.reportText.Scope_1_T_1 = "New String"; or
this["reportText"]["Scope_1_T_1"] = "New String";
It suddenly does work? I don't understand why. I even tried to see if I somehow make a copy of 'this' so it doesn't reference the same object, but as far as I see it doesn't make a copy. It does seems to be a reference problem, because it somehow points to a different location when I use my dynamic brackets.
Here is my relevant code(if you need more, please let me know):
<template>
<p>{{ reportText.Scope_1_T_1 }}</p>
</template>
<script>
export default {
data: function() {
return {
reportText: {
Scope_1_T_1: 'Text to change'
}
}
},
created() {
this.$store.getters.getAppPromise.then(app => {
this.createObject(app, 'reportText.Scope_1_T_1', 'String', '=irrelevantExpression');
})
},
methods: {
createObject(app, location, type, expression) {
if (type === 'String') {
app.createGenericOjbect(
{
fields: {
qStringExpression: expression
}
},
reply => {
let context = this;
location = location.split('.');
location.forEach(item => {
context = context[item];
});
context = reply.fields;
}
)
}
}
}
}
</script>
I would greatly appreciate it if anyone could help me figure out what the difference is between using my dynamically created context and a static context (like this: this["reportText"]["Scope_1_T_1"]). I think that's the key in solving this problem.
My code is based on this stackoverflow question:
Javascript Square Bracket Notation Multiple Dynamic Properties

It's just the final step that won't work. Assigning a new value to context at the end will just update that local variable, not the property of the object.
Instead what you need to do is grab a reference to the relevant object and then update the property. To grab the object you need to drop the final section from the location path. That final section is then the property name that needs to be updated:
let context = this;
const path = location.split('.');
const property = path.pop()
path.forEach(item => {
context = context[item];
});
context[property] = reply.fields;
The syntax used for property access hides some asymmetry in how the parts of the path are interpreted.
Consider this example:
const a = b.c.d.e
What happens is:
Start with b.
Grab the value in property c.
Grab the value in property d.
Grab the value in property e.
Assign that value to a.
All nice and symmetric, c, d and e all seems to work the same way.
Now consider flipping that example:
b.c.d.e = a
This is very different.
Start with b.
Grab the value in property c.
Grab the value in property d.
Assign a to the property e.
In this scenario the c and d properties are still just read operations but the e is handled totally differently. That final part is a write operation instead.
The key thing to appreciate here is that the final part of a 'path' like this is special when you want to set the value. Normally this hides behind the syntax but when you want to break it down like in your example you need to be conscious of what is actually going on.
Whether you use . or [] notation makes no difference to this behaviour, though to access properties dynamically you have to use [].

Related

Babel: Determine the variable value in the current scope

Let's say I have the following code I want to transform using a custom Babel plugin:
let c;
c = document;
console.log(c.readyState);
The goal would be to replace all occurrences of document.readyState with a custom function, e.g. window.getDocumentReadyState().
So the output should look something like this:
let c;
c = document;
console.log(window.getDocumentReadyState());
The difficulty here is to determine which value the object c actually has when the MemberExpression Visitor is called, as I just want to replace MemberExpressions for document. That's why I need to find out if the current value of c is document.
Here's an implementation which just replaces every <obj>.readyState MemberExpression:
/**
* Replace document.readyState with
* window.getDocumentReadyState();
*/
MemberExpression(path) {
const { node, parent } = path;
const objName = node.object.name;
const propName = node.property.name;
if (t.isAssignmentExpression(parent)) {
return;
}
if (t.isCallExpression(parent)) {
const isCallee = parent.callee === node;
if (isCallee) return;
}
if (propName === 'readyState') {
const customReadyStateFn = t.callExpression(
t.memberExpression(
t.identifier('window'),
t.identifier('getDocumentReadyState'),
),
[t.identifier(objName)],
);
path.replaceWith(customReadyStateFn);
}
},
Using this implementation, I perform a runtime check to determine if the object is of type document (HTMLDocument) inside window.getDocumentReadyState, because I wasn't able to do the same using Babel.
But there must be a way to reliably tell if this variable has the value document using static analysis, right?
Essentially, I need to find the last AssignmentExpression of this variable in the current scope.
I already tried looking the variable up in the scope using path.scope.getBinding(<variableName>), but the problem is that the last AssignmentExpression doesn't show up in binding.references. If the value was assigned during declaration (let c = document), it would be no problem, because that reference can be accessed using the binding.
I also tried traversing through the scope, but the AssignmentExpression visitor wasn't invoked.
I am fairly new to Babel and ASTs and reached a point where I don't know what to do next, and I would really like to get rid of that runtime check.
How would you solve such a problem?
Maybe a bit late, but I think it can be done.
You need to traverse through all left-side Identifiers and check if their right-side value is document. While doing so you keep an array with all the variable names that value is equal to document, and delete them from the array if their value changes and it is not document anymore.
Then when you traverse the MemberExpression check if the object name is in the array.

Sanitizing `eval` to prevent it from changing any values

This is front-end only, and not back-end. I also acknowledge that this is a bad idea. At this point I'm just curious.
I have a table of records. I would like the user to be able to enter a JavaScript conditional statement, which is then applied to the table to filter the records.
For example, to filter out records with a name that's less than 6 characters, I might enter:
record.name.length < 6
Without using an external library, the easiest way I've found to do this is with eval. However, in using eval, I of course introduce the risk of the user breaking the code (not a huge concern since this is front-end only, but still a user experience issue).
I would like to sanitize the user input so that it cannot change any values. So far, I believe I only need to do these two things to make eval "safe":
Turn any single equals signs = into double or triple equals signs
Remove or escape parentheses ( )
With these two items taken care of, is there anything else I need to do to prevent the user input from changing values?
One way of doing this which is safer than eval is using the Function constructor. As far as I know, this answer is totally safe, but it's quite possible there's some caveat I don't know or have forgotten, so everyone feel free to reply if I'm wrong.
The Function constructor allows you to construct a function from its string and a list of argument names. For example, the function
function(x, y) {
return x + y;
}
could be written as
new Function('x', 'y', 'return x + y;')
or simply
Function('x', 'y', 'return x + y;')
Note that although the function body has access to variables declared in the function definition, it cannot access variables from the local scope where the Function constructor was called; in this respect it is safer than eval.
The exception is global variables; these are accessible to the function body. Perhaps you want some of them to be accessible; for many of them, you probably don't. However, there is a way round this: declare the names of globals as arguments to the function, then call the function overriding them with fake values. For example, note that this expression returns the global Object:
(function() { return Object; })()
but this one returns 'not Object':
(function(Object) { return Object; })('not Object')
So, to create a function which does not have access to any of the globals, all you have to do is call the Function constructor on the javascript string, with arguments named after all the globals, then call the function with some innocuous value for all the globals.
Of course, there are variables (such as record) which you do want the javascript code to be able to access. The argument-name arguments to Function can be used for this too. I'll assume you have an object called myArguments which contains them, for example:
var myArguments = {
record: record
};
(Incidentally, don't call it arguments because that's a reserved word.) Now we need the list of names of arguments to the function. There are two kinds: arguments from myArguments, and globals we want to overwrite. Conveniently, in client-side javascript, all global variables are properties in a single object, window. I believe it's sufficient to use its own properties, without prototype properties.
var myArgumentNames = Object.keys(myArguments);
var globalNames = Object.keys(window);
var allArgumentNames = myArgumentNames.concat(globalNames);
Next we want the values of the arguments:
var myArgumentValues = myArgumentNames.map(function(key) {
return myArguments[key];
};
We don't need to do the values part for the globals; if we don't they'll just all be set to undefined. (Oh, and don't do Object.keys(myArguments).map(...), because there's a (small) chance that the array will come out in the wrong order, because Object.keys doesn't make any guarantees about the order of its return value. You have to use the same array, myArgumentNames.) Then call the Function constructor. Because of the large number of arguments to Function it's not practical to list them all explicitly, but we can get round this using the apply method on functions:
var myFn = Function.apply(null, allArgumentNames.concat([jsString]))
and now we just call this function with the argument list we've generated, again using the apply method. For this part, bear in mind that the jsString may contain references to this; we want to make sure this doesn't help the user to do something malicious. The value of this inside the script is the first argument to apply. Actually that's not quite true - if jsString doesn't use strict mode, then trying to set this to undefined or null will fail, and this will be the global object. You can get round this by forcing the script into strict mode (using '"use strict";\n' + jsString), or alternatively just set this to an empty object. Like this:
myFn.apply({}, myArgumentValues)
I am sharing my implementation (based on #David's answer).
Some of the keys of the Window object might break the Function.apply. This is why I've filtered the ones that break. Explanations in the code below as a comment.
// Why is windowKeys not inside function scope? No need. It won't
// be changing on each call. Creating array with +270 items for each eval
// might effect performance.
const windowKeys = Object.keys(window).filter((key) => {
// Why is window filtered?
// There are some cases that parameters given here might break the Function.apply.
// Eg. window keys as numbers: '0', (if there is iframe in the page)
// the ones that starts with numbers '0asdf',
// the ones that has dash and special characters etc.
try {
Function.apply(null, [key, "return;"]);
return true;
} catch (e) {
return false;
}
});
/**
* evaluates
* #param {string} code
* #param {object} context
* #returns
*/
const safeEval = (code, context) => {
const keys = Object.keys(context);
const allParams = keys.concat(windowKeys, [`"use strict"; return ${code}`]);
try {
const fn = Function.apply(null, allParams);
const params = keys.map((key) => context[key]);
return fn(...params);
} catch (e) {
console.log(e);
}
};
// simple expression evaluation
const res = safeEval("a + b", { a: 1, b: 2 });
console.log(res);
// try to access window
const res1 = safeEval("{a, b, window, document, this: this}", { a: 1, b: 2 });
console.log(res1);
Idk. if this approach can be exploited, if it does. I think another approach can be running eval on cross-domain iframe and get the result with window messages.

Symbol in Javascript

I saw following code in a project. can anyone explain what is going on here? What will be value of Attributes? What is happening this[Attributes] = attrs line?
const Attributes = Symbol('User#attrs');
class User {
constructor (attrs) {
this[Attributes] = attrs;
}
}
Symbol creates an un-collidable key for any object:
const first = Symbol('debug-name');
const second = Symbol('debug-name');
first !== second // true;
const anObj = {};
anObj[first] = 123;
anObj[second] = 456;
console.log(anObj) // {Symbol('debug-name'): 123, Symbol('debug-name'): 456}
Note that even though the first and second variables have the same debugging string they create different keys in anObj. Anyone who has access to first can add that key to any object and it will not collide with any other key in that object.
This can be used instead of magic strings to manage protocols:
// ES5
someObject.MY_LIB_attributes = [1, 2, 3];
// Only safe if no other library uses the key
// "MY_LIB_attributes"
// ES2015+
export const Attributes = Symbol('myLib#attributes');
import { Attributes } from 'my-lib';
someObj[Attributes] = [1, 2, 3];
// Safe as long as no other library uses
// *this* Symbol instance for some other purpose.
Edit
Since you've now clarified the question to be only about the line of code this[Attributes] = attrs, see the second part of my answer for discussion of that.
Original Answer
This is a couple of the new ES6 Javascript features.
const Attributes = Symbol('User#attrs'); creates a new Symbol object. The Symbol function and object is described here. It creates a unique identifier object that can then be used for many other uses, one of which is as a property name. There are many other references on the new Symbol feature so I won't repeat all of that here.
The class definition is the ES6 method for declaring prototyped classes. Again, there are many other references on this new syntax so there is no point in repeating all that here. There's an example below of what the equivalent ES5 code is.
This line this[Attributes] = attrs; uses the Symbol generated above to set a property on the newly created object.
The class definition is equivalent to the regular constructor
declaration like this:
function User(attrs) {
this[Attributes] = attrs;
}
Discussion of this[Attributes] = attrs
Attributes is a symbol which can be used as a property name on an object. It's a way of generating a unique key that can be used as a property name. So, this[Attributes] = attrs is setting a property on the newly constructed object and it is using the Attributes symbol as the property name. This Attributes symbol is a unique value that will not match any known string (in fact it won't even match other Symbol objects) so it's a way of making a unique property name.
It is unclear why the code does this:
this[Attributes] = attrs;
instead of just something like this:
this.attrs = attrs;
We would have to see a bit more context for how that is being used and why a plain string property could not also be used in place of the Symbol as you haven't provided enough context for us to know.
One possible use is for privacy. If Attributes is not public, then this is a way of creating a property on the object that the outside world doesn't know how to access because you have to have the current value of Attributes in order to access that properly. As you've shown the code with User and Attributes in the same scope that does not seem like it is private, but perhaps only User is exported to a public scope.
Another possible use is for uniqueness. If the User object may have lots of other properties added to it by other code, then Attributes creates a unique property name that cannot collide with other property names. This seems less likely in this case, but it is one possible use of Symbols.

Change value by reference

I want to pass a value to a Change function. But i want to pass only the reference inside the array. This works:
var test = ["Hello World","Hello You"];
HelloCar(test,0);
function HelloCar(myarray,key)
{
myarray[key] = "Hello Car";
}
But this will fail:
var test = ["Hello World","Hello You"];
HelloCar(test[0]);
function HelloCar(myvalue)
{
myvalue = "Hello Car";
}
Is there a way to pass only the real reference, without the complete data?
How about using a prototype:
Array.prototype.HelloCar= function ( idx ) {
this[idx] = 'Hello Car'
}
test.helloCar(0)
Primitive types (such as Strings, Numbers and Booleans) are always passed as values, not references.
The closest you will get to emulating referencing variables is by wrapping them in an Object.
However, it might be comforting for you to know that you'll be able to achieve whatever you need to do without trying to bend JavaScript syntax to your will.
If you're anxious about a method having access to data it shouldn't (not that I can see any reason for this, in your instance), then I would write it like this:
var test = ["Hello World","Hello You"];
test[0] = HelloCar(test[0]);
function HelloCar(string){
// any logic to change string in any way
string = "HelloCar";
return string;
}
This may seem a bit pointless, but let's pretend (for argument's sake) that you could pass a reference to a variable of a primitive type... If so, the only information the method would have access to would be it's value, because there is little more to a string variable than a memory address and a value. Being a string, it doesn't have a great deal of information, other than a bunch of characters. Ergo, any logic we need to apply to the variable in the way of contorting its value is actually going to be based on the same principals as the method above.

What is the name of the technique the example uses to pass a variable to an object in javascript?

What is the name of the technique the example uses below to pass a variable to an object in javascript? And furthermore, why is it not working ("large" is not outputted to the console log as expected)?
var thumbnailBlock = new ThumbnailBlock();
thumbnailBlock.thumbnailSize = "large";
function ThumbnailBlock() {
this.thumbnailSize;
console.log("DEBUG");
console.log(this.thumbnailSize);
}
The explanation what is going wrong in your code from Willem Mulder's answer:
There is no specific name. You simply set an object property.
The 'large' is not outputted because you first create an object using the ThumbnailBlock constructur function (where it logs the this.thumbnailSize) and only then set the .thumbnailSize to "large".
You could pass the size as function argument.
function ThumbnailBlock(size) {
this.thumbnailSize = size;
}
var thumbnailBlock = new ThumbnailBlock("large");
console.log(thumbnailBlock.thumbnailSize);
Also, have a look at the The Constructor Pattern and other nice patterns on that page. I also recommend the chapter on OOP in the free book Eloquent JavaScript.
There is no specific name. You simply set an object property.
The 'large' is not outputted because you first create an object using the ThumbnailBlock constructur function (where it logs the this.thumbnailSize) and only then set the .thumbnailSize to "large".
The proper answers are already given, I just want to point out for the record that adding dynamic property won't work if your object is sealed or frozen.
function ThumbnailBlock(size) {
Object.seal(this);
}
var thumbnailBlock = new ThumbnailBlock("large");
thumbnailBlock.thumbnailSize = 10;
console.log(thumbnailBlock.thumbnailSize); // undefined
In strict mode you will raise an exception instead.

Categories

Resources