I've created a console Proxy which right now doesn't do much more than adding a prefix on each line written to the console, and it works perfectly, even keeps line numbers intact (that is, the messages have the proper origin shown by the browser). Here is the code:
const disabledMethodsInProduction = ['debug', 'log', 'trace'];
logger = new Proxy(console, {
get (target, method) {
if (method in target) {
if (!developmentMode && disabledMethodsInProduction.includes(method)) {
return () => { /* NOP */ };
}
// 'loggingID' is a string, defined elsewhere.
return target[method].bind(console, `${loggingID}:`);
}
throw new Error(`Method '${method}()' not implemented in console.`);
}
});
The problem with the above code is the same that other solutions that I've investigated (and commented) here in Stack Overflow. To wit, the prefix (in this case, the loggingID string) is now the FIRST argument of the console object method, so when calling the proxy object above with a message containing string substitutions (%c for example), they won't be processed because they are now the SECOND argument instead.
The fix is easy: instead of returning the console method, a new function can be returned which traps the arguments provided and if the first one is a string and contains string substitutions, the prefix is inserted in that argument, instead of being a different argument:
return function () {
// Do things with 'arguments' and pass it to the proper console method.
const args = Array.from(arguments);
// Yes, I know, first I should check args[0] to see
// if it is a string and contains string substitutions.
args[0] = `${loggingID}: ` + args[0];
target[method](...args); // THIS line number will appear in console messages, obviously.
}
Unfortunately, doing that clobbers the original line numbers, obviously.
Any solution to have both features, that is, keeping line numbers and trapping the arguments at the same time?
I can live with losing the ability of using string substitutions, as keeping line numbers is far more important for me, but I'm just curious about how to solve this, and I would love to have both features.
I found a way of achieving what I need but requires a set of extra parens:
const disabledMethodsInProduction = ['debug', 'log', 'trace'];
logger = new Proxy(console, {
get (target, method) {
if (method in target) {
if (!developmentMode && disabledMethodsInProduction.includes(method)) {
return () => { /* NOP */ };
}
return function (...args) {
// An additional check for string subtitution chars is needed.
if (typeof args[0] === 'string') {
// 'loggingID' is a string, defined elsewhere.
args[0] = `${loggingID}: ${args[0]}`;
}
return target[method].bind(console, ...args);
};
}
throw new Error(`Method '${method}()' not implemented in console.`);
}
});
// Should be used like this:
// logger.log('%cThis is bold', 'font-weight:bold')();
Notice the extra pair of parentheses when calling the proxied object method.
This solution:
Preserves line numbers, showing those of the caller.
Can selectively disabled in production.
Honors string substitutions in the first argument.
If anyone has any idea about how getting rid of the ugly extra pair of parentheses, I'm all ears.
Related
This question already has answers here:
Messing with this in prototype's method
(2 answers)
Closed 1 year ago.
I'm manipulating the prototype of Object so that I can add some extension methods.
I've found out that typeof operator always returns object in case the operand is this:
Object.prototype.logType = function () { console.log(typeof this); }
"Hello".logType()
output of code above is object instead of string. I know that in JavaScript everything is indeed an object, However I need to know the exact type of the value of this. How can I achieve that?
When you call a method on a primitive, JS automatically wraps that primitive in its associative object wrapper, so:
"Hello".logType()
becomes:
new String("Hello").logType()
hence, this inside of your logType function refers to the wrapped primitive value, giving you an object. You can call .valueOf() to grab the primitive value wrapped in the object:
Object.prototype.logType = function () { console.log(typeof this.valueOf()); }
"Hello".logType(); // string
(1).logType(); // number
true.logType(); // boolean
1n.logType(); // bigint
Symbol().logType(); // symbol
(() => {}).logType(); // function
({}).logType(); // object
Or, you can use strict mode as suggested in the comments, as that keeps this as the original primitve:
Object.prototype.logType = function () { "use strict"; console.log(typeof this); }
"Hello".logType(); // string
(1).logType(); // number
true.logType(); // boolean
1n.logType(); // bigint
Symbol().logType(); // symbol
(() => {}).logType(); // function
({}).logType(); // object
When passed as this outside of strict mode, you have the very rare case that you encounter a primitive in their wrapped object form ("boxed"), as instance of String:
You can therefore check if your method was called on a string using this instanceof String instead of typeof this === 'string'. If you want to differentiate between strings and objects that inherit from String, you could use this.constructor === String instead.
To get a "regular" string back (or number for Number, or boolean from Boolean, etc.), you can call this.valueOf()
(This means you could also write typeof this.valueOf() - but note that this may be misleading because any object could return, say, a string from its valueOf method without actually having been a string originally.)
Note: You cannot differentiate between 'abc'.yourMethod() and new String('abc').yourMethod() this way because you get an instance of String either way.
(Also interesting: Seems like for newer types like BigInt or Symbol you cannot manually create such a wrapper instance, new BigInt(1n) will fail. But (function () { return this }).call(1n) would achieve the same, although I have no idea why anybody would want that...)
All that said: The easiest way to get the exact behavior you want (this being the actual string) is by defining your function in a context that is in strict mode:
(function {
'use strict'
Object.prototype.logType = function () { console.log(typeof this); }
})()
Now it will work as intended.
let ar = [];
let p = new Proxy(new Map(), {
get: (o, k) => {
ar.push(1)
return Reflect.get(o, k).bind(o)
},
set: (o, k, v) => {
ar.push(2)
return Reflect.set(o, k, v)
}
});
p.set(1, 2)
p.get(1)
console.log(ar) //Outputs [1,1]
I am trying to intercept both set and get operations on a Map object. I am in no way trying to extend/subclass a Map.
In the process of proxying said Map object, I came across this weird unexpected behavior, the set trap isn't being fired in the above code, but instead the get trap gets fired twice!
I further proceeded to log the k(key) values from the get trap in the following way;
//same code above
get: (o, k) => {
console.log(k) //Logs set and then get!
return Reflect.get(o, k).bind(o)
}
//same code below
The behavior I expect is for the array to be [2,1] and console.log(k) at the get trap to actually output the value of the key.
I wish to know why this happens as so, I've gone through a couple problems like this in here related to proxyifying maps, none of them lead to any sensible reasoning as to why this is happening.
My end goal is to fire an event at the set trap. Am I using Proxy for something it is meant to be used? If not, what approach should I take? Should I abandon using a Map to an Object Literal even though it will bring all the cons of using one? ex: no length property, string-only properties, no forced-unique keys etc.
UPDATE: The more and more I dig into this proxified Map, the more issues I keep coming across. It now seems to me as if the ES6 Proxy API treats Maps the same way as it does an ordinary object. The answer by Vill and my digging backs this up. Why do I say this? Read the following;
//same code above
get: (o, k) => {
if(k === 'tray') return ']'
return Reflect.get(o, k).bind(o)
}
//same code below
p.tray //returns ]
The above code shouldn't theoretically succeed, right? It is as if Proxy is using the same logic to intercept object operations when intercepting Maps as well! As;
///--While the following---//
let m = new Map();
m.set('tray', ']')
m.tray //undefined
Vill's answer says that the Proxy identifies Map.prototype.set as first reading set and invoking it as a function next.
Doesn't that mean in the set trap I've written in the original code(on the very top) doesn't intercept the modification/setting a property of the Map and in fact the implicit/native to Map-Map.prototype.set is used instead of the Reflect.set which we grant through the Proxy?
Doesn't all this further enforce the fact that Proxy and Maps don't mix together well? Am I heading down the wrong way? What am I misunderstanding if so? Are Proxies supposed to treat Maps like just any other object?
It is not bug it is feature (joke).
You should understand what exactly proxy's .get and .set do. Get will intercept any reading try of the object. Lets take your example:
p.set(1,2)
p.get(1)
On the first line we: read from object property with name set and then try to invoke it as function
On the second line we read from object property with name get and then try to invoke it as a function.
If you will try this code:
p.val = 5;
then you will try to set 5 to the target with name val and setter will fire.
This how proxy's setter and getter work.
So, to achive desired behavior, check the property name and return the function with some additional implementation. And do not forget to call original function.
Somth like this:
get: (o, k) => {
if (k==='get') return Reflect.get(o, k).bind(o);
if (k==='set') return function(v){Reflect.set(o, k, v)}
}
Hope this helps.
Update:
let obj = {
take: (v) => v
};
let handler = {
get: (target, key) => {
if (key === 'take') {
return function(v) {
console.log(`logger says: function ${key} was called with argument: ${v}`);
}
return target[key];
}
}
};
let proxy = new Proxy(obj, handler);
proxy.take(5);
proxy.take(3);
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.
The following code is a simple proxy that logs out the "gets" that were trapped:
var p = new Proxy({}, {
get: function(target, property, receiver) {
console.log("getting: ", property);
return target[property];
}
});
When I coerce this into a String with "hello " + p, I get the following output in the console:
getting: Symbol(Symbol.toPrimitive)
getting: valueOf
getting: toString
getting: Symbol(Symbol.toStringTag)
"hello [object Object]"
Everything is fine so far, but let's do something a little sneaky and proxy a function, but actually still use it as a proxy to our plain object we used in the last example. The reason I want this is because I'd like to be able to capture both gets and applys on this obj.
Notice the return target.obj part - we're really using this to proxy obj still - it's just that we're doing it via fn:
var fn = function(){};
fn.obj = {};
var p = new Proxy(fn, {
get: function(target, property, receiver) {
console.log("getting: ", property);
return target.obj[property];
}
});
Now, I'd have thought this would produce exactly the same output as the last example for "hello " + p, but I was wrong:
getting: Symbol(Symbol.toPrimitive)
getting: valueOf
getting: toString
getting: Symbol(Symbol.toStringTag)
"hello [object Function]"
Notice that it has resulted in a Function string tag rather than an Object one. What's going on here? It's as if toString is being called on fn rather than obj. (Edit: But we can add fn.toString = function(){ return "fn"; } and it doesn't change the output, so maybe it's not fn that is being stringified here?)
If you pop a debugger statement in there, you'll see it's actually returning fn.obj.toString as you'd expect, but for some reason the final output is a function rather than an object (though I'm not entirely sure which function). Thanks for your help!
P.S. I haven't explained the full context of my use case (short version: it's for a DSL, so bending "good practice" is fine), and so suggesting alternative patterns to achieve both get and apply traps on an object (in effect) may not be relevant to my particular case. I'd really just like to understand why the above approach isn't working like I expect it to, but would also like to ensure the question is broad enough to help future readers in a similar situation.
I think I've found the bug. When we return a function, it looks like we need to bind it to target.obj, otherwise it's being bound to some function somewhere. I'm not completely up to scratch on this stuff, but I think it makes sense. So here's the updated, working code:
var fn = function(){};
fn.obj = {};
fn.toString = function(){ return "fn"; }
var p = new Proxy(fn, {
get: function(target, property, receiver) {
console.log("getting: ", property);
let result = target.obj[property];
if(typeof result === 'function') {
result = result.bind(target.obj);
}
return result;
}
});
I have now seen 2 methods for determining if an argument has been passed to a JavaScript function. I'm wondering if one method is better than the other or if one is just bad to use?
function Test(argument1, argument2) {
if (Test.arguments.length == 1) argument2 = 'blah';
alert(argument2);
}
Test('test');
Or
function Test(argument1, argument2) {
argument2 = argument2 || 'blah';
alert(argument2);
}
Test('test');
As far as I can tell, they both result in the same thing, but I've only used the first one before in production.
Another Option as mentioned by Tom:
function Test(argument1, argument2) {
if(argument2 === null) {
argument2 = 'blah';
}
alert(argument2);
}
As per Juan's comment, it would be better to change Tom's suggestion to:
function Test(argument1, argument2) {
if(argument2 === undefined) {
argument2 = 'blah';
}
alert(argument2);
}
There are several different ways to check if an argument was passed to a function. In addition to the two you mentioned in your (original) question - checking arguments.length or using the || operator to provide default values - one can also explicitly check the arguments for undefined via argument2 === undefined or typeof argument2 === 'undefined' if one is paranoid (see comments).
Using the || operator has become standard practice - all the cool kids do it - but be careful: The default value will be triggered if the argument evaluates to false, which means it might actually be undefined, null, false, 0, '' (or anything else for which Boolean(...) returns false).
So the question is when to use which check, as they all yield slightly different results.
Checking arguments.length exhibits the 'most correct' behaviour, but it might not be feasible if there's more than one optional argument.
The test for undefined is next 'best' - it only 'fails' if the function is explicitly called with an undefined value, which in all likelyhood should be treated the same way as omitting the argument.
The use of the || operator might trigger usage of the default value even if a valid argument is provided. On the other hand, its behaviour might actually be desired.
To summarize: Only use it if you know what you're doing!
In my opinion, using || is also the way to go if there's more than one optional argument and one doesn't want to pass an object literal as a workaround for named parameters.
Another nice way to provide default values using arguments.length is possible by falling through the labels of a switch statement:
function test(requiredArg, optionalArg1, optionalArg2, optionalArg3) {
switch(arguments.length) {
case 1: optionalArg1 = 'default1';
case 2: optionalArg2 = 'default2';
case 3: optionalArg3 = 'default3';
case 4: break;
default: throw new Error('illegal argument count')
}
// do stuff
}
This has the downside that the programmer's intention is not (visually) obvious and uses 'magic numbers'; it is therefore possibly error prone.
If you are using jQuery, one option that is nice (especially for complicated situations) is to use jQuery's extend method.
function foo(options) {
default_options = {
timeout : 1000,
callback : function(){},
some_number : 50,
some_text : "hello world"
};
options = $.extend({}, default_options, options);
}
If you call the function then like this:
foo({timeout : 500});
The options variable would then be:
{
timeout : 500,
callback : function(){},
some_number : 50,
some_text : "hello world"
};
This is one of the few cases where I find the test:
if(! argument2) {
}
works quite nicely and carries the correct implication syntactically.
(With the simultaneous restriction that I wouldn't allow a legitimate null value for argument2 which has some other meaning; but that would be really confusing.)
EDIT:
This is a really good example of a stylistic difference between loosely-typed and strongly-typed languages; and a stylistic option that javascript affords in spades.
My personal preference (with no criticism meant for other preferences) is minimalism. The less the code has to say, as long as I'm consistent and concise, the less someone else has to comprehend to correctly infer my meaning.
One implication of that preference is that I don't want to - don't find it useful to - pile up a bunch of type-dependency tests. Instead, I try to make the code mean what it looks like it means; and test only for what I really will need to test for.
One of the aggravations I find in some other peoples' code is needing to figure out whether or not they expect, in the larger context, to actually run into the cases they are testing for. Or if they are trying to test for everything possible, on the chance that they don't anticipate the context completely enough. Which means I end up needing to track them down exhaustively in both directions before I can confidently refactor or modify anything. I figure that there's a good chance they might have put those various tests in place because they foresaw circumstances where they would be needed (and which usually aren't apparent to me).
(I consider that a serious downside in the way these folks use dynamic languages. Too often people don't want to give up all the static tests, and end up faking it.)
I've seen this most glaringly in comparing comprehensive ActionScript 3 code with elegant javascript code. The AS3 can be 3 or 4 times the bulk of the js, and the reliability I suspect is at least no better, just because of the number (3-4X) of coding decisions that were made.
As you say, Shog9, YMMV. :D
In ES6 (ES2015) you can use Default parameters
function Test(arg1 = 'Hello', arg2 = 'World!'){
alert(arg1 + ' ' +arg2);
}
Test('Hello', 'World!'); // Hello World!
Test('Hello'); // Hello World!
Test(); // Hello World!
url = url === undefined ? location.href : url;
There are significant differences. Let's set up some test cases:
var unused; // value will be undefined
Test("test1", "some value");
Test("test2");
Test("test3", unused);
Test("test4", null);
Test("test5", 0);
Test("test6", "");
With the first method you describe, only the second test will use the default value. The second method will default all but the first (as JS will convert undefined, null, 0, and "" into the boolean false. And if you were to use Tom's method, only the fourth test will use the default!
Which method you choose really depends on your intended behavior. If values other than undefined are allowable for argument2, then you'll probably want some variation on the first; if a non-zero, non-null, non-empty value is desired, then the second method is ideal - indeed, it is often used to quickly eliminate such a wide range of values from consideration.
I'm sorry, I still yet cant comment, so to answer Tom's answer...
In javascript (undefined != null) == false
In fact that function wont work with "null", you should use "undefined"
There is a tricky way as well to find, whether a parameter is passed to a function or not. Have a look at the below example:
this.setCurrent = function(value) {
this.current = value || 0;
};
This necessary means that if the value of value is not present/passed - set it to 0.
Pretty cool huh!
Why not using the !! operator? This operator, placed before the variable, turn it to a boolean (if I've understood well), so !!undefined and !!null (and even !!NaN, which can be quite interesting) will return false.
Here is an exemple:
function foo(bar){
console.log(!!bar);
}
foo("hey") //=> will log true
foo() //=> will log false
Sometimes you want undefined as a possible argument but you still have situations where the argument may not be passed. In that case you can use arguments.length to check how many arguments were passed.
// Throw error if the field is not matching our expectations
function testField(label, fieldValue, expectedValue) {
console.log(arguments) // Gives: [Arguments] { '0': 'id', '1': 1, '2': undefined }
if(arguments.length === 2) {
if(!fieldValue) {
throw new Error(`Field "${label}" must have a value`)
}
}
else if(expectedValue === undefined) {
if(fieldValue !== undefined) {
throw Error(`Field "${label}" must NOT have a value`)
}
}
// We stringify so our check works for objects as well
else {
if(JSON.stringify(fieldValue) !== JSON.stringify(expectedValue)) {
throw Error(`Field "${label}" must equal ${expectedValue} but was ${fieldValue}`)
}
}
}
testField('id', 12) -> Passes, we don't want id to be blank
testField('id', undefined, undefined) -> Passes, we want id to be undefined
testField('id', 12, undefined) -> Errors, we wanted id to be undefined
It can be convenient to approach argument detection by evoking your function with an Object of optional properties:
function foo(options) {
var config = { // defaults
list: 'string value',
of: [a, b, c],
optional: {x: y},
objects: function(param){
// do stuff here
}
};
if(options !== undefined){
for (i in config) {
if (config.hasOwnProperty(i)){
if (options[i] !== undefined) { config[i] = options[i]; }
}
}
}
}
Some times you may also want to check for type, specially if you are using the function as getter and setter. The following code is ES6 (will not run in EcmaScript 5 or older):
class PrivateTest {
constructor(aNumber) {
let _aNumber = aNumber;
//Privileged setter/getter with access to private _number:
this.aNumber = function(value) {
if (value !== undefined && (typeof value === typeof _aNumber)) {
_aNumber = value;
}
else {
return _aNumber;
}
}
}
}
function example(arg) {
var argumentID = '0'; //1,2,3,4...whatever
if (argumentID in arguments === false) {
console.log(`the argument with id ${argumentID} was not passed to the function`);
}
}
Because arrays inherit from Object.prototype. Consider ⇑ to make the world better.
fnCalledFunction(Param1,Param2, window.YourOptionalParameter)
If above function is called from many places and you are sure first 2 parameters are passed from every where but not sure about 3rd parameter then you can use window.
window.param3 will handle if it is not defined from the caller method.