Wrap a console.log with infinite optional params - javascript

I'm trying to create a custom logger which just wraps a console.log but always adds something to the start which makes it absolutely clear that the log is coming from my plugin.
I have come up with the following code:
var log = function(param1, param2) {
if (typeof param3 !== 'undefined') {
console.log('[MyPlugin]', param1, param2, param3);
} else if (typeof param2 !== 'undefined') {
console.log('[MyPlugin]', param1, param2);
}
};
This allows the developer to run the following:
log('foo', 'bar');
// Outputs '[MyPlugin] foo bar'
log('foo');
// Outputs '[MyPlugin] foo'
But I hope that this can be improved upon.
The issues with this implementation are:
The logging function only allows two parameters. It would be better if it could accept many.
There's lots of repetition (multiple console.log calls).
What I have tried.
I thought maybe the ES6 spread-operator would work:
var log = function(...params) {
console.log('[MyPlugin]', params);
};
Which allows the developer to run:
log('foo', 'bar');
// Outputs '[MyPlugin] Array [ "foo", "bar" ]'
log('foo');
// Outputs '[MyPlugin] Array [ "foo" ]'
You can see that the output is not the same as in the original function.
Is there a better solution?

Spread notation (it's not an operator) would indeed do it, if you were using it. You're using rest notation (to gather arguments into an array parameter), but not spread notation (to spread them out when passing them to console.log). Here's how you'd use spread:
var log = function(...params) {
// Rest -----------^^^
console.log('[MyPlugin]', ...params);
// Spread ----------------^^^
};
Example:
var log = function(...params) {
// Rest -----------^^^
console.log('[MyPlugin]', ...params);
// Spread ---------------^^^
};
log("testing", 1, 2, 3);
If you need to support pre-ES2015 environments without transpiling, you can do the same thing with arguments and Function#apply:
var log = function() {
var args = Array.prototype.slice.call(arguments);
args.unshift('[MyPlugin]');
console.log.apply(console, args);
};
Live Example:
var log = function() {
var args = Array.prototype.slice.call(arguments);
args.unshift('[MyPlugin]');
console.log.apply(console, args);
};
log("testing", 1, 2, 3);

Related

How to add a function as a method to a returned array?

I have a function intended to be used with the new constructor. It should return an array with a value of ["Hello World"] and include functions as methods. Here is a sample/demo (sorry if it's a little ugly):
var my_object = function () {
this.foo = function (data) { // alert and add new item
alert(data);
this.push(data);
};
this.bar = function () { // alert and remove last item
alert(this.pop());
};
this.baz = function (stuff) { // replace last item by running previous methods
this.bar();
this.foo(stuff);
};
var a = "Hello World";
return [a];
};
var arr = new my_object(); // ["Hello World"]
As expected, arr has a value of ["Hello World"]. However, the following code produces an error:
arr.foo('some-text'); // "TypeError: arr.foo is not a function"
The same type of error appears with the other two functions. What can I do to make this work, without altering Array.prototype, creating the methods outside the function, or removing the ablity to initialize with new my_object()?
Note: If possible, please do not include answers using jQuery or other external libraries.
As mentioned in the comments, this link has some valuable information -> https://github.com/wesbos/es6-articles/blob/master/54%20-%20Extending%20Arrays%20with%20Classes%20for%20Custom%20Collections.md
Using this, your code could be modified like so,. ->
Of course this is ES6, extending arrays was not possible in ES5 days..
In Chrome instanceof when mapped works as expected, but not sure transpiled code would work here, if this was a requirement. For example if you click the use Babel preset on this snippet you will see it fail, so if you want this to work on old browsers even if trans-piled it may be an issue.
class my_object extends Array {
foo(data) {
alert(data);
this.push(data);
}
bar() {
alert(this.pop());
}
baz(stuff) {
this.bar();
this.foo(stuff);
}
constructor () {
super("Hello World");
}
};
var arr = new my_object(); // ["Hello World"]
arr.foo("some-text");
console.log(arr);
If you don't use any Babel or something what can transpile your code secure is to use inheritance and the prototype chain. JavaScript defaults is not object oriented programming but it pretends OOP.
In your case on construct you return ["Hello World"] which is array, not an instance of my_object. You can return this instead of [a].
Besides, you do not have any array you can reference. You work at a local object when you use this and you have a probably TypeError: this.push is not a function.
I improved your case. See what is return, where I use this and reference to data array.
Greetings, plum!
Improved code:
var my_object = function () {
this.data = [];
this.foo = function (data) { // alert and add new item
alert(data);
this.data.push(data);
};
this.bar = function () { // alert and remove last item
alert(this.data.pop());
};
this.baz = function (stuff) { // replace last item by running previous methods
this.bar();
this.foo(stuff);
};
var a = "Hello World";
return this;
};
var arr = new my_object(); // [object Object] - a my_object instance
console.log(arr)
arr.foo('some-text');

Deleting an object-property passed as a function argument

I'm trying to delete an object property:
var test = {abc: 1};
console.log(delete test.abc); //works fine and returns 'true'
The following construct drives me crazy
function deleteNode(_node) {
console.log(delete _node);
}
deleteNode(test.abc); // does not work and returns 'false'
Why? I read many questions on stackoverflow regarding trouble with delete and read most of the typical links provided. But I can't find a solution to this simple problem...
Deleting dynamic props of an object could be achieved like this:
function deleteNode(obj, key) {
delete obj[key]
}
See the delete docs for more details.
When having a "path" like "a.b.c" you can use lodash unset which deletes nested entries:
var object = { 'a': { 'b': { 'c': 7 } } };
_.unset(object, 'a.b.c');
Use of eval is discouraged .
var test = {abc: 1};
function deleteNode(_node) {
console.log(delete _node);
}
deleteNode(test.abc);
When you invoked deleteNode above, test.abc already resolved to 1 before being passed to function. deleteNode is technically invoking delete 1. And according to ES standard, deleting a function argument directly results in false or gives an error in strict mode.
"use strict";
var test = {a: 1};
function del(a) {
console.log(delete a);
}
del(test.a);
You will need to pass the object and key as different arguments to method.
var test = {abc: {a: 1, b: 2, c: {d: 1}}};
function deleteNode(obj, key) {
console.log(eval('delete obj.' +key));
}
deleteNode(test, 'abc.c.d');
console.log('test is', test)

About dynamic dispatch in the book-[Programming JavaScript Applications]

I meet a problem about dynamic dispatch. The following is the code snippet which from the book [Programming JavaScript Applications], and I put it on https://jsfiddle.net/abramhum/bbfxxwok/1/
function equals(a, b, c) {
console.log("a[=]" + a);
if (a == b) {
console.log(c);
}
}
function test(a, fn) {
console.log(a + " ---start function[=]");
fn.apply(this);
console.log(a + " ---Fnished");
}
var methods = {
init: function(args) {
return 'initializing...';
},
hello: function(args) {
return 'Hello, ' + args;
},
goodbye: function(args) {
return 'Goodbye, cruel ' + args;
}
},
greet = function greet(options) {
var args = [].slice.call(arguments, 0),
initialized = false,
action = 'init'; // init will run by default
if (typeof options === 'string' &&
typeof methods[options] === 'function') {
action = options;
args.shift();
}
return methods[action](args);
};
test('Dynamic dispatch', function() {
var test1 = greet(),
test2 = greet('hello', 'world!'),
test3 = greet('goodbye', 'world!');
equals(test2, 'Hello, world!',
'Dispatched to hello method.');
equals(test3, 'Goodbye, cruel world!',
'Dispatched to goodbye method.');
});
There exist two subjects in this problem, one is when greet("goodbye", "world") is executed, why it called greet(options), and the value about options is indeed the fist parameter, like "goodbye", and the "world" can be get via arguments; The second is var methods ={...}, it get the arguments like init, and return the value if matching the declare, like init:function(args){...}, but it indeed not code style of switch, and why we can use that in javascript.
This is much unlike C codes, I don't know why, is any one know the reason?
thanks.
one is when greet("goodbye", "world") is executed, why it called greet(options), and the value about options is indeed the fist parameter, like "goodbye", and the "world" can be get via arguments
Because in a JavaScript non-arrow function, arguments is a predefined identifier referring to a pseudo-array of all of the arguments passed to the function. It has nothing to do with dynamic dispatch. It's just a feature of JavaScript functions that was useful back before JavaScript got proper variable parameter lists:
function foo() {
console.log("foo called, total arguments: " + arguments.length);
for (var n = 0; n < arguments.length; ++n) {
console.log("Arg #" + n + ":", arguments[n]);
}
}
foo();
foo("bar");
foo("biz", 42);
the second problem is var methods ={...}, it get the arguments like init
Those aren't arguments, those are properties being defined for the object assigned to methods. Just like a, b, and c here:
var obj = {
a: 42,
b: "Whatever",
c: "just cuz"
};
...and return the value if matching the declare, like init:function(args){...}, but it indeed not code style of switch, and why we can use that in javascript.
Because functions are objects, and so like any other object, you can refer to them from variables, arguments, and object properties. methods's properties init, hello, and goodbye refer to functions. You can call them via the properties: method.init().
So say we have the variable name containing "init". We can look up the property with that name on methods: methods[name]. And since that gives us a reference to a function, we can call that function.
var methods = {
init: function() {
console.log("init called");
}
};
var name = "init";
methods[name](); // "init called"
More: Dynamically access object property using variable
This is much unlike C codes, I don't know why, is any one know the reason?
Because C and JavaScript are fundamentally different languages, created with different design constraints, at different times, by different people, with different priorities and limits.

Javascript recursion arguments

I'm having an issue with how my arguments are being passed to a new recursion level. Here's a simplified version of what I'm doing:
var newFunction = function(obj) {
var result = "";
var args = [];
Array.prototype.push.apply(args, arguments);
var thisArg = args.shift()
//do stuff to add onto result with thisArg. This block works, so I'm skipping.
if (args.length !== 0) {
result += newFunction(args);
};
return result;
};
The issue I'm having is related to how 'args' is getting passed into newFunction to cycle back through. When the recursive callback is made, args is passed into the new function scope as a single array argument:
original arguments = ("string", true, 9, "string 2")
new arguments in recursion = ([true, 9, string 2])
It NEEDS to be:
original arguments = ("string", true, 9, "string 2")
new arguments in recursion = (true, 9, "string 2")
I'm pretty sure it's related to how I'm using .apply for the args variable. I'm just not sure how to get around that, since you can't .shift() the 'arguments' object. The way I'm doing it is setting args to be an array; so when it gets passed in, it is passing it as a single array. This is the problem with my code...
Do you see what I'm doing wrong? Thanks in advance.
You can use .apply():
result += newFunction.apply(undefined, args);
The .apply() function is like .call, but it expands the array elements out so that (effectively) the array is copied, element by element, into the arguments object in the newly-called function.
In ECMAScript 5, using apply:
var newFunction = function(thisArg) {
var result = "",
args = [].slice.call(arguments, 1);
// ...
if (args.length) result += newFunction.apply(void 0, args);
return result;
};
In ECMAScript 6, using rest parameters and the spread operator,
var newFunction = function(thisArg, ...args) {
var result = "";
// ...
if (args.length) result += newFunction(...args);
return result;
};

Is it possible to call function.apply without changing the context?

In some Javascript code (node.js specifically), I need to call a function with an unknown set of arguments without changing the context. For example:
function fn() {
var args = Array.prototype.slice.call(arguments);
otherFn.apply(this, args);
}
The problem in the above is that when I call apply, I'm change the context by passing this as the first argument. I'd like to pass args to the function being called without changing the context of the function being called. I essentially want to do this:
function fn() {
var args = Array.prototype.slice.call(arguments);
otherFn.apply(<otherFn's original context>, args);
}
Edit: Adding more detail regarding my specific question. I am creating a Client class that contains a socket (socket.io) object among other info pertaining to a connection. I am exposing the socket's event listeners via the client object itself.
class Client
constructor: (socket) ->
#socket = socket
#avatar = socket.handshake.avatar
#listeners = {}
addListener: (name, handler) ->
#listeners[name] ||= {}
#listeners[name][handler.clientListenerId] = wrapper = =>
# append client object as the first argument before passing to handler
args = Array.prototype.slice.call(arguments)
args.unshift(this)
handler.apply(this, args) # <---- HANDLER'S CONTEXT IS CHANGING HERE :(
#socket.addListener(name, wrapper)
removeListener: (name, handler) ->
try
obj = #listeners[name]
#socket.removeListener(obj[handler.clientListenerId])
delete obj[handler.clientListenerId]
Note that clientListenerId is a custom unique identifier property that is essentially the same as the answer found here.
If I understand you correctly:
changes context
| n | y |
accepts array n | func() | func.call() |
of arguments y | ???????? | func.apply() |
PHP has a function for this, call_user_func_array. Unfortunately, JavaScript is lacking in this regard. It looks like you simulate this behavior using eval().
Function.prototype.invoke = function(args) {
var i, code = 'this(';
for (i=0; i<args.length; i++) {
if (i) { code += ',' }
code += 'args[' + i + ']';
}
eval(code + ');');
}
Yes, I know. Nobody likes eval(). It's slow and dangerous. However, in this situation you probably don't have to worry about cross-site scripting, at least, as all variables are contained within the function. Really, it's too bad that JavaScript doesn't have a native function for this, but I suppose that it's for situations like this that we have eval.
Proof that it works:
function showArgs() {
for (x in arguments) {console.log(arguments[x]);}
}
showArgs.invoke(['foo',/bar/g]);
showArgs.invoke([window,[1,2,3]]);
Firefox console output:
--
[12:31:05.778] "foo"
[12:31:05.778] [object RegExp]
[12:31:05.778] [object Window]
[12:31:05.778] [object Array]
Simply put, just assign the this to what you want it to be, which is otherFn:
function fn() {
var args = Array.prototype.slice.call(arguments);
otherFn.apply(otherFn, args);
}
'this' is a reference to your function's context. That's really the point.
If you mean to call it in the context of a different object like this:
otherObj.otherFn(args)
then simply substitute that object in for the context:
otherObj.otherFn.apply(otherObj, args);
That should be it.
If you bind the function to an object and you use everywhere the bound function, you can call apply with null, but still get the correct context
var Person = function(name){
this.name = name;
}
Person.prototype.printName = function(){
console.log("Name: " + this.name);
}
var bob = new Person("Bob");
bob.printName.apply(null); //window.name
bob.printName.bind(bob).apply(null); //"Bob"
One way that you can work around the change of context that can happen in JavaScript when functions are called, is to use methods that are part of the object's constructor if you need them to be able to operate in a context where this is not going to mean the parent object, by effectively creating a local private variable to store the original this identifier.
I concede that - like most discussions of scope in JavaScript - this is not entirely clear, so here is an example of how I have done this:
function CounterType()
{
var counter=1;
var self=this; // 'self' will now be visible to all
var incrementCount = function()
{
// it doesn't matter that 'this' has changed because 'self' now points to CounterType()
self.counter++;
};
}
function SecondaryType()
{
var myCounter = new CounterType();
console.log("First Counter : "+myCounter.counter); // 0
myCounter.incrementCount.apply(this);
console.log("Second Counter: "+myCounter.counter); // 1
}
These days you can use rest parameters:
function fn(...args) {
otherFn(...args);
}
The only downside is, if you want to use some specific params in fn, you have to extract it from args:
function fn(...args) {
let importantParam = args[2]; //third param
// ...
otherFn(...args);
}
Here's an example to try (ES next version to keep it short):
// a one-line "sum any number of arguments" function
const sum = (...args) => args.reduce((sum, value) => sum + value);
// a "proxy" function to test:
var pass = (...args) => sum(...args);
console.log(pass(1, 2, 15));
I'm not going to accept this as an answer, as I'm still hoping for something more suitable. But here's the approach I'm using right now based upon the feedback on this question so far.
For any class that will be calling Client.prototype.addListener or Client.prototype.removeListener, I did added the following code to their constructor:
class ExampleClass
constructor: ->
# ...
for name, fn of this
this[name] = fn.bind(this) if typeof(fn) == 'function'
message: (recipient, body) ->
# ...
broadcast: (body) ->
# ...
In the above example, message and broadcast will always be bound to the new ExampleClass prototype object when it's instantiated, allowing the addListener code in my original question to work.
I'm sure some of you are wondering why I didn't just do something like the following:
example = new ExampleClass
client.addListener('message', example.bind(example))
# ...
client.removeListener('message', example.bind(example))
The problem is that every time .bind( ) is called, it's a new object. So that means that the following is true:
example.bind(example) != example.bind(example)
As such, the removeListener would never work successfully, thus my binding the method once when the object is instantiated.
Since you seem to want to be using the bind function as it is defined in Javascript 1.8.5, and be able to retrieve the original this object you pass the bind function, I recommend redefining the Function.prototype.bind function:
Function.prototype.bind = function (oThis) {
if (typeof this !== "function") {
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () {},
fBound = function () {
return fToBind.apply(this instanceof fNOP && oThis
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
/** here's the additional code **/
fBound.getContext = function() {
return oThis;
};
/**/
return fBound;
};
Now you can retrieve the original context that you called the bind function with:
function A() {
return this.foo+' '+this.bar;
}
var HelloWorld = A.bind({
foo: 'hello',
bar: 'world',
});
HelloWorld(); // returns "hello world";
HelloWorld.getContext(); // returns {foo:"hello", bar:"world"};
I was just reminded of this question after a long time. Looking back now, I think what I was really trying to accomplish here was something similar to how the React library works with its automatic binding.
Essentially, each function is a wrapped bound function being called:
function SomeClass() {
};
SomeClass.prototype.whoami = function () {
return this;
};
SomeClass.createInstance = function () {
var obj = new SomeClass();
for (var fn in obj) {
if (typeof obj[fn] == 'function') {
var original = obj[fn];
obj[fn] = function () {
return original.apply(obj, arguments);
};
}
}
return obj;
};
var instance = SomeClass.createInstance();
instance.whoami() == instance; // true
instance.whoami.apply(null) == instance; // true
Just push properties directly to the function's object and call it with it's own "context".
function otherFn() {
console.log(this.foo+' '+this.bar); // prints: "hello world" when called from rootFn()
}
otherFn.foo = 'hello';
otherFn.bar = 'world';
function rootFn() {
// by the way, unless you are removing or adding elements to 'arguments',
// just pass the arguments object directly instead of casting it to Array
otherFn.apply(otherFn, arguments);
}

Categories

Resources