Coming from an old-school way of handling my defaults, I am trying to wrap my head around how to allow default values within an object literal for a constructor, while calling the constructor with a partial object literal. Note: I am still not a fan of "class" syntax for constructors, but I intend to use it, so please indulge me while I learn!
Code speaks louder than words. Here was my first attempt:
class ProxyManager {
constructor(
proxy = {
proxyID: 12345,
proxyHost: '',
proxyPort: 8080,
proxySSL: false,
proxyPath: ''
}
) {
this.proxy = proxy;
}
getProxy() {
return this.proxy;
}
}
const foo = new ProxyManager();
foo.getProxy(); // returns the full default object defined in the constructor
const bar = new ProxyManager({proxyID: 67890});
foo.getProxy(); // returns {proxyID: 67890}
None of this is a surprise; you can see right from the syntax that as long as something is passed in as the first paramater, it becomes "proxy". So, while I wasn't expecting it to work, it was my starting point.
Out of familiarity, I fell back to an older-school way of doing it, which is something like this:
class ProxyManager {
constructor(proxy) {
this.proxy = proxy || {};
const defaults = {
proxyID: 12345,
proxyHost: '',
proxyPort: 8080,
proxySSL: false,
proxyPath: ''
}
// swap in more thorough sanity-check if needed
if (Object.prototype.toString.call(this.proxy) === '[object Object]') {
this.proxy = Object.assign(defaults, this.proxy)
}
}
getProxy() {
return this.proxy;
}
}
const foo = new ProxyManager();
foo.getProxy(); // returns the full default object defined in the constructor
const bar = new ProxyManager({proxyID: 67890, proxyPort: 16500});
foo.getProxy(); // returns full object with updated proxyID and proxyPort
It works, and I guess I could move on... but I am interested to see if there is a pattern I'm missing. I did some searching, and kept coming up short.
Based on comments, option #2 in the original question isn't such a bad solution. It's probably better than the following. But just to be thorough, here's what we arrived at:
class ProxyManager {
constructor(
{
proxyID = 12345,
proxyHost = '',
proxyPort = 8080,
proxySSL = false,
proxyPath = ''
} = {}
) {
this.proxy = {
proxyID,
proxyHost,
proxyPort,
proxySSL,
proxyPath
}
}
getProxy() {
return this.proxy;
}
}
It's possibly harder to grok for older-school JS users like me, and it is certainly needlessly repetitive for the end result. But it fits my original question criteria, so here it is as an answer. As a side benefit, you don't have to do a bunch of sanity-checks to ensure the defaults work. If the constructor is called with invalid paramaters (for example, passing in a simple string or int), the defaults just apply. On the other hand, that's also the drawback... there's no warning that you have used the constructor incorrectly.
Related
I wish I could post my project verbatim for this question, but I cannot.
Essentially, I have the following classes:
class Lowest {
someValue: string
constructor(someValue: string) {
this.someValue = someValue
}
}
class Middle {
lowest: Lowest
constructor(someValue: string) {
this.lowest = new Lowest(someValue)
}
}
class Highest {
private _middle: Middle
otherValue: SomeOtherClass
// normal getter
public get middle(): Middle {
return this._middle
}
public set middle(next: Middle) {
this._middle = next
// notice: when `_middle` is set, I update another property!
otherValue.doSomething(this._middle)
}
constructor(config: { someValue: string }) {
this.middle = new Middle(config.somevalue)
}
}
In some places in my program, I have a reference to a Highest instance and need to alter its middle.lowest.someValue sub-property. Now, for architectural reasons that I cannot really describe here, I need to update the Highest.otherValue property whenever Highest.middle. As I am using TypeScript, I simply perform this operation in the setter for Highest.middle. As such, I cannot just straight up set Highest.middle.lowest to some value.
My first approach was:
const nextMiddle = Object.assign({}, highestInstance.middle)
nextMiddle.lowest.someValue = "some other thing"
highestInstance.middle = nextMiddle
However, this ended up causing some very strange issues. Now, I had no real technical need for performing a deep clone of nextMiddle, so I overcame it with the following code:
const nextMiddle = highestInstance.middle
nextMiddle.lowest.someValue = "some other thing"
highestInstance.middle = nextMiddle
While I was experimenting with the best fix, I implemented a Middle.copy() method that basically just calls new Middle() and new Lowest() with the values from the previous instance. This also solved my technical issues, but left me even more confused.
I understand that there's a big difference between simply re-assigning highestInstance.middle and using Object.assign() to clone it, but I don't understand why there does not seem to be a difference between Object.assign() and new Middle()
What are the real differences with these three methods of cloning/re-assignment?
had no real technical need for performing a deep clone of nextMiddle,
so I overcame it with the following code:
Object.assign({}, highestInstance.middle) is creating a shallow copy and not a deep copy.
The issue here is using Middle Setter is forcing yourself to perform otherValue: SomeOtherClass only when middle is updated.
it will update it even if you just do just :
high.middle.lowest.someValue = 'new value'
high.middle = high.middle; // setter triggered
A possible solution is to create a call-back chain rather than using a setter:
type voidFun = () => void;
class Lowest {
private _someValue: string
public get someValue(): string {
return this._someValue
}
private update?: voidFun; // create an optional callback
public set someValue(next: string) {
this._someValue = next
this.update?.call(undefined);
}
constructor(someValue: string, fun?: voidFun) {
this._someValue = someValue
this.update = fun;// pass down
}
}
class Middle {
lowest: Lowest;
constructor(someValue: string, fun?: voidFun) {
this.lowest = new Lowest(someValue, fun)
}
}
class Highest {
private _middle: Middle
otherValue: any = {};
// normal getter
public get middle(): Middle {
return this._middle
}
// now you dont need middle setting so it cant be updated by anyone directly
// if you still want to have you can have it too
private callBack = () => {
console.log('lower was update');
this.otherValue.myMiddle = this._middle.lowest.someValue;
}
constructor(config: { someValue: string } = { someValue : 'constr' }) {
this._middle = new Middle(config.someValue, this.callBack)
}
}
let high = new Highest()
high.middle.lowest.someValue = 'new value'
// high.middle = high.middle; // now not possible unless you create setter
console.log('updated middle high',high)
The problem is rather simple. We need to imbue a function with a parameter, and then simply extract that parameter from the body of the function. I'll present the outline in typescript...
abstract class Puzzle {
abstract assign(param, fn): any;
abstract getAssignedValue(): any;
async test() {
const wrapped = this.assign(222, async () => {
return 555 + this.getAssignedValue();
});
console.log("Expecting", await wrapped(), "to be", 777);
}
}
Let's set the scene:
Assume strict mode, no arguments or callee. Should work reasonably well on the recent-ish version of v8.
The function passed to assign() must be an anonymous arrow function that doesn't take any parameters.
... and it's alsoasync. The assigned value could just be stored somewhere for the duration of the invocation, but because the function is async and can have awaits, you can't rely on the value keeping through multiple interleaved invocations.
this.getAssignedValue() takes no parameters, returning whatever we assigned with the assign() method.
Would be great to find a more elegant solution that those I've presented below.
Edit
Okay, we seem to have found a good solid solution inspired by zone.js. The same type of problem is solved there, and the solution is to override the meaning of some system-level primitives, such as SetTimeout and Promise. The only headache above was the async statement, which meant that the body of the function could be effectively reordered. Asyncs are ultimately triggered by promises, so you'll have to override your Promise with something that is context aware. It's quite involved, and because my use case is outside of browser or even node, I won't bore you with details. For most people hitting this kind of problem - just use zone.js.
Hacky Solution 2
class HackySolution2 extends Puzzle {
assign(param: any, fn: AnyFunction): AnyFunction {
const sub = Object(this);
sub["getAssignedValue"] = () => param;
return function () { return eval(fn.toString()); }.call(sub);
}
getAssignedValue() {
return undefined;
}
}
In this solution, I'm making an object that overrides the getAssignedValue() method, and re-evaluates the source code of the passed function, effectively changing the meaning of this. Still not quite production grade...
Edit.
Oops, this breaks closures.
I don't know typescript so possibly this isn't useful, but what about something like:
const build_assign_hooks = () => {
let assignment;
const get_value = () => assignment;
const assign = (param, fn) => {
assignment = param;
return fn;
}
return [assign, get_value];
};
class Puzzle {
constructor() {
const [assign, getAssignedValue] = build_assign_hooks();
this.assign = assign;
this.getAssignedValue = getAssignedValue;
}
async test() {
const wrapped = this.assign(222, async () => {
return 555 + this.getAssignedValue();
});
console.log("Expecting", await wrapped(), "to be", 777);
}
}
const puzzle = new Puzzle();
puzzle.test();
Hacky Solution 1
We actually have a working implementation. It's such a painful hack, but proves that this should be possible. Somehow. Maybe there's even a super simple solution that I'm missing just because I've been staring at this for too long.
class HackySolution extends Puzzle {
private readonly repo = {};
assign(param: any, fn) {
// code is a random field for repo. It must also be a valid JS fn name.
const code = 'd' + Math.floor(Math.random() * 1000001);
// Store the parameter with this code.
this.repo[code] = param;
// Create a function that has code as part of the name.
const name = `FN_TOKEN_${code}_END_TOKEN`;
const wrapper = new Function(`return function ${name}(){ return this(); }`)();
// Proceed with normal invocation, sending fn as the this argument.
return () => wrapper.call(fn);
}
getAssignedValue() {
// Comb through the stack trace for our FN_TOKEN / END_TOKEN pair, and extract the code.
const regex = /FN_TOKEN_(.*)_END_TOKEN/gm;
const code = regexGetFirstGroup(regex, new Error().stack);
return this.repo[code];
}
}
So the idea in our solution is to examine the stack trace of the new Error().stack, and wrap something we can extract as a token, which in turn we'll put into a repo. Hacky? Very hacky.
Notes
Testing shows that this is actually quite workable, but requires a more modern execution environment than we have - i.e. ES2017+.
Okay, so I have a problem.
SHORT VERSION:
I want to do this:
const createThing = function( behaviours(intensities) ){
return {
behaviour1: behaviour1(intensity1),
behaviour2: behaviour2(intensity2)
and so on for each behaviour(intensity) passed as an argument
}
}
//so when doing:
const wtf = createThing( cat(loud), dog(whereistheball), bird(dee) );
// wtf should be:
{
cat: cat(loud),
dog: dog(whereistheball),
bird: bird(dee)
}
I've tryied among other things something like this:
const createThing = function( behaviours(intensities) ){
return {
for(a in arguments){
[a.name]: a;
}
}
}
I've been trying a lot of different ways to do this on the past week with no success. Can anyone help me?
LONG VERSION:
Okay so I have a magnet behaviour factory function and a particle factory function, it looks like this
const magnet = funtion(intensity) {
// returns a magnetic object with that intensity
}
const createParticle = function( location, static or dynamic, behaviours ){
// returns a particle object with the properties and behaviours listed above
}
The problem is I can't get the behaviours part to work. By now I have a magnetic behaviour factory, but I also want to have an eletrical, gravitacional, random etc. I want the particle object to get the behavior name as a new property key and the object it creates as this property value when this behaviour is passed as an argument to the particle factory function, something like this:
const particle1 = createParticle ( location, dynamic, magnet(intensity) )
//should return
{
location,
dynamic,
magnet: magnet(intensity)
}
or even
const particle2 = createParticle ( location, dynamic, magnet(intensity), eletric(intensity) )
//should return
{
location,
dynamic,
magnet: magnet(intensity),
eletric: eletric(intensity)
}
and so on.
I tried using the method function.name, but it is not possible since when I pass the behaviour function as a parameter to the particle, it evaluates into an object. I tried using a callback function and then using function.name but it doesn't do anything since I still need to pass the behavior function along with its parameters to the particle factory.
Is it even possible??? How???
No, this is not possible. Not unless cat/dog/bird/magnet/eletric all return an object that contains the name of the respective factory.
In particular:
function createParticle(...args) {
const particle = { kind: 'particle' };
for (const element of args)
particle[element.kind] = element;
return particle;
}
If you are using classes/constructors+prototypes, you can of course use the implicit .constructor.name instead of the .kind property that I chose in the example above.
JS-Interpreter is a somewhat well-known JavaScript Interpreter. It has security advantages in that it can completely isolate your code from document and allows you to detect attacks such as infinite loops and memory bombs. This allows you to run externally defined code safely.
I have an object, say o like this:
let o = {
hidden: null,
regex: null,
process: [
"this.hidden = !this.visible;",
"this.regex = new RegExp(this.validate, 'i');"
],
visible: true,
validate: "^[a-z]+$"
};
I'd like to be able to run the code in process through JS-Interpreter:
for (let i = 0; i < o.process.length; i++)
interpretWithinContext(o, o.process[i]);
Where interpretWithinContext will create an interpreter using the first argument as the context, i.e. o becomes this, and the second argument is the line of code to run. After running the above code, I would expect o to be:
{
hidden: false,
regex: /^[a-z]+$/i,
process: [
"this.hidden = !this.visible;",
"this.regex = new RegExp(this.validate, 'i');"
],
visible: true,
validate: '^[a-z]+$'
}
That is, hidden and regex are now set.
Does anyone know if this is possible in JS-Interpreter?
I’ve spent a while messing around with the JS-Interpreter now, trying to figure out from the source how to place an object into the interpreter’s scope that can be both read and modified.
Unfortunately, the way this library is built, all the useful internal things are minified so we cannot really utilize the internal things and just put an object inside. Attempts to add a proxy object also failed failed since the object just wasn’t used in a “normal” way.
So my original approach to this was to just fall back to providing simple utility functions to access the outside object. This is fully supported by the library and probably the safest way of interacting with it. It does require you to change the process code though, in order to use those functions. But as a benefit, it does provide a very clean interface to communicate with “the outside world”. You can find the solution for this in the following hidden snippet:
function createInterpreter (dataObj) {
function initialize (intp, scope) {
intp.setProperty(scope, 'get', intp.createNativeFunction(function (prop) {
return intp.nativeToPseudo(dataObj[prop]);
}), intp.READONLY_DESCRIPTOR);
intp.setProperty(scope, 'set', intp.createNativeFunction(function (prop, value) {
dataObj[prop] = intp.pseudoToNative(value);
}), intp.READONLY_DESCRIPTOR);
}
return function (code) {
const interpreter = new Interpreter(code, initialize);
interpreter.run();
return interpreter.value;
};
}
let o = {
hidden: null,
regex: null,
process: [
"set('hidden', !get('visible'));",
"set('regex', new RegExp(get('validate'), 'i'));"
],
visible: true,
validate: "^[a-z]+$"
};
const interprete = createInterpreter(o);
for (const process of o.process) {
interprete(process);
}
console.log(o.hidden); // false
console.log(o.regex); // /^[a-z]+$/i
<script src="https://neil.fraser.name/software/JS-Interpreter/acorn_interpreter.js"></script>
However, after posting above solution, I just couldn’t stop thinking about this, so I dug deeper. As I learned, the methods getProperty and setProperty are not just used to set up the initial sandbox scope, but also as the code is being interpreted. So we can use this to create a proxy-like behavior for our object.
My solution here is based on code I found in an issue comment about doing this by modifying the Interpreter type. Unfortunately, the code is written in CoffeeScript and also based on some older versions, so we cannot use it exactly as it is. There’s also still the problem of the internals being minified, which we’ll get to in a moment.
The overall idea is to introduce a “connected object” into the scope which we will handle as a special case inside the getProperty and setProperty to map to our actual object.
But for that, we need to overwrite those two methods which is a problem because they are minified and received different internal names. Fortunately, the end of the source contains the following:
// Preserve top-level API functions from being pruned/renamed by JS compilers.
// …
Interpreter.prototype['getProperty'] = Interpreter.prototype.getProperty;
Interpreter.prototype['setProperty'] = Interpreter.prototype.setProperty;
So even if a minifier mangles the names on the right, it won’t touch the ones on the left. So that’s how the author made particular functions available for public use. But we want to overwrite them, so we cannot just overwrite the friendly names, we also need to replace the minified copies! But since we have a way to access the functions, we can also search for any other copy of them with a mangled name.
So that’s what I’m doing in my solution at the beginning in patchInterpreter: Define the new methods we’ll overwrite the existing ones with. Then, look for all the names (mangled or not) that refer to those functions, and replace them all with the new definition.
In the end, after patching the Interpreter, we just need to add a connected object into the scope. We cannot use the name this since that’s already used, but we can just choose something else, for example o:
function patchInterpreter (Interpreter) {
const originalGetProperty = Interpreter.prototype.getProperty;
const originalSetProperty = Interpreter.prototype.setProperty;
function newGetProperty(obj, name) {
if (obj == null || !obj._connected) {
return originalGetProperty.call(this, obj, name);
}
const value = obj._connected[name];
if (typeof value === 'object') {
// if the value is an object itself, create another connected object
return this.createConnectedObject(value);
}
return value;
}
function newSetProperty(obj, name, value, opt_descriptor) {
if (obj == null || !obj._connected) {
return originalSetProperty.call(this, obj, name, value, opt_descriptor);
}
obj._connected[name] = this.pseudoToNative(value);
}
let getKeys = [];
let setKeys = [];
for (const key of Object.keys(Interpreter.prototype)) {
if (Interpreter.prototype[key] === originalGetProperty) {
getKeys.push(key);
}
if (Interpreter.prototype[key] === originalSetProperty) {
setKeys.push(key);
}
}
for (const key of getKeys) {
Interpreter.prototype[key] = newGetProperty;
}
for (const key of setKeys) {
Interpreter.prototype[key] = newSetProperty;
}
Interpreter.prototype.createConnectedObject = function (obj) {
const connectedObject = this.createObject(this.OBJECT);
connectedObject._connected = obj;
return connectedObject;
};
}
patchInterpreter(Interpreter);
// actual application code
function createInterpreter (dataObj) {
function initialize (intp, scope) {
// add a connected object for `dataObj`
intp.setProperty(scope, 'o', intp.createConnectedObject(dataObj), intp.READONLY_DESCRIPTOR);
}
return function (code) {
const interpreter = new Interpreter(code, initialize);
interpreter.run();
return interpreter.value;
};
}
let o = {
hidden: null,
regex: null,
process: [
"o.hidden = !o.visible;",
"o.regex = new RegExp(o.validate, 'i');"
],
visible: true,
validate: "^[a-z]+$"
};
const interprete = createInterpreter(o);
for (const process of o.process) {
interprete(process);
}
console.log(o.hidden); // false
console.log(o.regex); // /^[a-z]+$/i
<script src="https://neil.fraser.name/software/JS-Interpreter/acorn_interpreter.js"></script>
And that’s it! Note that while that new implementation does already work with nested objects, it may not work with every type. So you should probably be careful what kind of objects you pass into the sandbox. It’s probably a good idea to create separate and explicitly safe objects with only basic or primitive types.
Have not tried JS-Interpreter. You can use new Function() and Function.prototype.call() to achieve requirement
let o = {
hidden: null,
regex: null,
process: [
"this.hidden = !this.visible;",
"this.regex = new RegExp(this.validate, 'i');"
],
visible: true,
validate: "^[a-z]+$"
};
for (let i = 0; i < o.process.length; i++)
console.log(new Function(`return ${o.process[i]}`).call(o));
Hi may be interpretWithinContext look like something like that ?
let interpretWithinContext = (function(o, p){
//in dunno for what you use p because all is on object o
o.hidden = (o.hidden === null) ? false : o.hidden;
o.regex = (o.regex === null) ? '/^[a-z]+$/i' : o.regex;
console.log(o);
return o;
});
https://codepen.io/anon/pen/oGwyra?editors=1111
I am creating a form and I am trying to find a simple, elegant way of handling to see if all inputs exist.
Form = Ember.Object.extend({
// section 1
name: null,
age: null,
isABoolean: null,
// section 2
job: null,
numberOfSiblings: null,
isComplete: Ember.computed.and('_isSection1Complete', '_isSection2Complete'),
_isSection1Complete: function() {
var isPresent = Ember.isPresent;
return isPresent(this.get('name')) && isPresent(this.get('age')) && isPresent(this.get('isABoolean'));
}.property('name', 'age', 'isABoolean'),
_isSection2Complete: function() {
var isPresent = Ember.isPresent;
return isPresent(this.get('job')) && isPresent(this.get('numberOfSiblings'));
}.property('job', 'numberOfSiblings')
});
However, this doesn't seem to scale. My actual application will have many sections (over 20 sections).
I am looking into trying to create a re-usable computed property that fits my needs. Take for example the code of what I am going for:
Form = Ember.Object.extend({
// properties...
isComplete: Ember.computed.and('_isSection1Complete', '_isSection2Complete'),
_isSection1Complete: Ember.computed.allPresent('name', 'age', 'isABoolean'),
_isSection2Complete: Ember.computed.allPresent('job', 'numberOfSiblings')
});
I feel that this is a common case, but I'm failing to find the correct computed properties on how to execute this, so I would like to make my own.
Two questions:
Where's the best place to define the custom computed property? Can I just attach a function to Ember.computed?
Is there an easier way to solve this? I feel like I'm overlooking something simple.
As for Question #1,
You can define a custom computed helper in the App namespace. In this example, I created a new computed helper called allPresent that checks each property passed in against Ember.isPresent.
App.computed = {
allPresent: function (propertyNames) {
// copy the array
var computedArgs = propertyNames.slice(0);
computedArgs.push(function () {
return propertyNames.map(function (propertyName) {
// get the value for each property name
return this.get(propertyName);
}, this).every(Ember.isPresent);
});
return Ember.computed.apply(Ember.computed, computedArgs);
}
};
It can be used like this, per your example code:
_isSection2Complete: App.computed.allPresent(['job', 'numberOfSiblings'])
I adapted this from the approach here: http://robots.thoughtbot.com/custom-ember-computed-properties
As for Question #2, I can't think of a simpler solution.
I had to make a minor adjustment to Evan's solution, but this works perfectly for anyone else that needs it:
App.computed = {
allPresent: function () {
var propertyNames = Array.prototype.slice.call(arguments, 0);
var computedArgs = propertyNames.slice(0); // copy the array
computedArgs.push(function () {
return propertyNames.map(function (propertyName) {
// get the value for each property name
return this.get(propertyName);
}, this).every(Ember.isPresent);
});
return Ember.computed.apply(Ember.computed, computedArgs);
}
};
This can now be used as such:
_isSection2Complete: App.computed.allPresent('job', 'numberOfSiblings')