This overrides console.log without issue and it makes sense to me:
(function(c) {
console.log = function() {
c.apply(console, arguments);
}
})(console.log);
This one does not work and I don't understand why:
(function(c) {
console.log = function() {
c(arguments);
}
})(console.log);
I just get a list of properties when I call console.log.
What's the difference?
I need to build the array with arguments in the second one for it to work.
It works if you modify your function like this:
(function(c) {
console.log = function() {
// c(...arguments) also works
c(...Object.values(arguments));
}
})(console.log);
console.log('hello world')
This is because arguments is not an array, but an array-like object.
function func(...args) {
console.log(arguments);
}
func(1, 2, "hello", "world")
The second code example doesn't works as you expect it to because you are passing the arguments object as it is to the console.log function whereas in the first code example, due to the usage of apply(), the properties in the arguments object are passed as a separate argument. In other words, array or an array-like object is spread in to distinct arguments.
The console.log call in the first code example is similar to the following:
console.log(arg1, arg2, arg3, ...)
whereas in the second one, it is as:
console.log({ 0: arg1, 1: arg2, 2: arg3, ... });
(function(c) {
console.log = function() {
const a = Array.from(arguments);
c(a.join(" "))
}
})(console.log);
This works.
arguments is an object-like so we need to create the string ourselves.
Related
Here are two callback function:
function callback_a(){
alert('a');
}
function callback_b(p){
alert('b says'+ p)'
}
If I want use callback_a
function test(callback){
if(condition){
callback();
}
}
test(callback_a);
But the function test isn't applicable to callback_b, So how to implement a common function that you can passing some callbacks function with multiple possible parameter lists.
There are three options:
The easiest way is to use spread operator:
function test(callback, ...callback_args) {
callback(...callback_args);
}
in this case the invocation of test for function callback_b would be like this:
test(callback_b,"b")
The second way is using arguments which are scoped to any function in JavaScript:
function test(callback) {
callback.apply(null, arguments.slice(1));
}
the invocation of test for function callback_b would be the same:
test(callback_b,"b")
Another options is to use partially applied functions. In this case you should define b_callback like this (ES6 syntax):
let callback_b = (p) => () => void{
alert('b says'+ p)'
}
or without ES6:
function callback_b(p) {
return function(){
alert('b says'+ p)'
}
}
and invoke it like this:
test(callback_b("b"))
There is a special object called arguments that gets created when a function is invoked. It's an array-like object that represents the arguments passed in to a function:
It can be used like this:
test();
// no arguments passed, but it still gets created:
// arguments.length = 0
// arguments >> []
test(a);
// ONE argument passed:
// arguments.length = 1
// arguments >> [a]
test(a,b,c,d);
// FOUR arguments passed:
// arguments.length = 4
// arguments >> [a,b,c,d]
Knowing this, one can call a callback with the rest of the arguments passed in from the parent function using apply like this:
function test(callback) {
callback.apply(null, Array.prototype.slice.call(arguments, 1));
}
// arguments passed into test are available in the function scope when
// .slice is used here to only pass the portion of the arguments
// array relevant to the callback (i.e. any arguments minus the
// first argument which is the callback itself.)
//
// N.B. The arguments object isn't an array but an array like object so
// .slice isn't available on it directly, hence .call was used here)
Might be worth reading up on:
The arguments object
Function.prototype.apply, Function.prototype.call and Function.prototype.bind as they are way to bind a context and arguments to a function (i.e. they'll work with the arguments object to call a function where you may not know how many arguments will be passed)
So how to implement a common function that you can passing some callbacks function with multiple possible parameter lists.
Basically, you don't. The function receiving the callback is in charge of what the callback receives as arguments. When you call Array#forEach, it's Array#forEach that decides what arguments your callback gets. Similarly, String#replace defines what it will call its callback with.
Your job is to say what test will do, what it will call its callback with. Then it's the job of the person using test to write their callback appropriately. For instance: You might document test as calling the callback with no arguments. If the caller wants to use callback_b, then it's up to them to handle the fact that callback_b expects a parameter. There are several ways they can do that:
The could wrap it in another function:
test(function() {
callback_b("appropriate value here");
});
...or use Function#bind
test(callback_b.bind(null, "appropriate value here"));
...but it's their problem, not yours.
Side note: If they pass you callback_b and you call it without any arguments, you won't get an error. JavaScript allows you to call a function with fewer arguments than it expects, or more. How the function handles that is up to the author of the function.
You can pass an anonymous function as the callback that will itself return your desired callback function with parameters.
test(function() { return callback_b(' how are you'); });
see this working snippet that will first use callback_a, then callback_b (with parameter) as the callback:
function callback_a(){
alert('a');
}
function callback_b(p){
alert('b says'+ p);
}
function test(callback){
if(true){
callback();
}
}
test(callback_a);
test(function() { return callback_b(' how are you'); });
You can pass the parameter while calling the callback
function test(callback){
if(condition){
callback();
}
else if(other condition){
callback("b");
}
}
test(callback_b);
You can write your callback function like
function callback_a_b(){
if(arguments.length){
var arg = [].slice.call(arguments);
alert('b says'+ arg[0])
}
else{
alert('a');
}
}
You can pass array of parameters as second param of test function or in ES6 use spread operator read more here
function test(callback, params){
if(condition){
if (params === undefined){
callback();
} else {
callback.apply(null, params); //params must be array
//ES6: callback(...params);
}
}
}
test(callback_a);
test(callback_b, [" whatever"]);
I've just checked in my browser (ffox 51.0.1) that the following works:
function test(callback,other_args){if(condition){callback(other_args);}}
results:
condition=true
test(callback_a)
=> shows the alert with 'a'
condition=false
test(callback_a)
=> doesn't show anything
condition=true
test(callback_b,"pepe")
=> shows the alert with 'b sayspepe'
condition=false
test(callback_b,"pepe")
=> doesn't show anything
I am trying to create a stub definition that the code below could use:
func.calc({
'divide': function(num1, num2) {
// do something
},
'add': function(num1, num2, num3) {
// do something
}
});
So far, I've been having trouble getting the parameters passed to the functions in the second argument. Here's what I've been trying to do:
var func = {
calc: function(operationsArray) {
if (div) {
operationsArray[0](args); /* get args */
}
else (add) {
operationsArray[1](args); /* get args */
}
}
}
Is it possible to get the whole function definition (with parameters and implementation) when it is passed as a parameter like in the first snippet?
You're declaring func.temp as method that accepts an Array as second argument. On the other hand, you call the method with an Object as the second argument.
JavaScript specification tells us that field order is not guaranteed:
4.3.3 Object
An object is a member of the type Object. It is an unordered collection of properties each of which contains a primitive value, object, or function. A function stored in a property of an object is called a method.
meaning you shouldn't access in_func & in_func2 using location index (i.e. [0]).
You should either call the function with an array like this:
func.temp(mainArg, [
function(arg1, arg2) {
// do something
},
function(arg1, arg2, arg3) {
// do something
}
]);
OR, implement the function so that it uses explicit names:
var func = {
temp: function(arg1, func_obj) {
// do something with arg1
if (trigger1) {
func_obj.in_func(/* get params */);
}
else (trigger2) {
func_obj.in_func2(/* get params */);
}
}
}
Here the code:
Function.prototype.curry = function() {
var slice = Array.prototype.slice,
args = slice.apply(arguments), // no thisArg ? arguments are the sec param [argsArray]
that = this;
return function() {
// thisArg: null
return that.apply(null, args.concat(slice.apply(arguments)));
}
}
Above is what I understand. So why does that.apply have a null param, while the slice.apply doesn't have one?
And when I changed it to args = slice.apply(null, arguments), It threw an error which said:
Uncaught TypeError: Array.prototype.slice called on null or undefined
Where am I wrong about Function.prototype.apply()?
.apply sets context and arguments for a function:
my_fn.apply({}, [1,2,3]);
function my_fn() {
console.log(this); // {}
console.log(arguments); // [1,2,3]
}
slice.apply(arguments); is a hack to convert an array like object to an actual array, actually it could also have been .call(arguments); since call works almost like .apply:
my_fn.call({}, 1,2,3); // <- no array but more arguments
function my_fn() {
console.log(this); // {}
console.log(arguments); // [1,2,3]
}
So that.apply(null, ... just doesn't set a context for the function that. While Array.prototype.slice expects to work on an array like object and will fail if it gets no context.
The slice.apply and that.apply calls in that function have different purposes.
Just a quick recap: Function#apply accepts up to two arguments: The value to use as this during the call to the original function, and any array-like object which has the arguments (if any) to pass to the function.
The slice.apply calls, such as this one:
args = slice.apply(arguments);
...are passing arguments as the first argument, so slice gets called with this referring to the arguments object and with no arguments at all. This is a fairly common idiom for converting the array-like arguments object into a true array. (In modern code with ES2015, we'd probably use args = Array.from(arguments); instead.)
The that.apply call doing something else entirely: It's calling the function object that curry was called on, passing it the arguments supplied to curry followed by the arguments supplied when the curried function was actually called. It passes null as the first argument, the value to use as this during the call, which means the original function will be called with this referring to the global object (if this is in loose mode) or null (in strict mode`).
Not to bikeshed it, but that's not a great implementation of curry if it's been quoted correctly:
(You've fixed this in the question.) It creates two implicit globals: args and that, which is a pretty bad idea. janje suggests it may be a misquoted version of Crockford's curry from The Good Parts; if so, the ; after Array.prototype.slice and after slice.apply(arguments) should be a , instead:
Function.prototype.curry = function() {
var slice = Array.prototype.slice, // <== comma here
args = slice.apply(arguments), // <== comma here
that = this;
return function() {
return that.apply(null, args.concat(slice.apply(arguments)));
}; // Crockford probably didn't leave this semicolon out
}; // Or this one
It blocks this when calling the original function; instead, it should use the same this that hte curried function was called with.
It's creating an enumerable property on Function.prototype; all the other methods on Function.prototype are non-enumerable, probably best to keep it that way.
Instead:
(function() {
var slice = Array.prototype.slice;
Object.defineProperty(Function.prototype, "curry", {
value: function() {
var originalFunction = this;
var args = slice.apply(arguments);
return function() {
return originalFunction.apply(this, args.concat(slice.apply(arguments)));
};
},
writable: true,
configurable: true
});
})();
Example:
"use strict";
// Define it
(function() {
var slice = Array.prototype.slice;
Object.defineProperty(Function.prototype, "curry", {
value: function() {
var originalFunction = this;
var args = slice.apply(arguments);
return function() {
return originalFunction.apply(this, args.concat(slice.apply(arguments)));
};
},
writable: true,
configurable: true
});
})();
// Demonstrate it
function foo() {
console.log("this.answer:", this && this.answer);
console.log("args:", arguments);
}
var obj = {answer: 42, foo: foo.curry("curried")};
obj.foo("supplied during call");
There are optimizations one could make (it's not strictly necessary to create a new array on each call to the curried function), but they don't really buy much.
If I have this ES6 function declaration and invocation:
function myFunction (arg1, arg2 = "bob") {
console.log("arguments", arguments);
}
myFunction(1);
...the console.log() statement shows only one argument with a value of "1". "bob" is nowhere to be seen. Is this expected and/or desired behavior? I would expect that default values would be available in the arguments object. If not, is there a way to dynamically get all arguments + defaults in some other manner?
Thanks in advance!
Yes, this is expected and desired. The arguments object is a list of the values that were passed into the function, nothing else.
It is not implicily linked to the parameter variables (that get assigned the default values), like it was in sloppy mode.
Is there a way to dynamically get all arguments + defaults in some other manner?
No. What parameters you have and whether they have default initialisers is static, you don't need to do anything here dynamically. You can do Object.assign([], arguments, [arg1, arg2]) for your example function.
As you know by now, there is no native method to get both "passed arguments AND defaults where arguments are not passed". But there is a workaround:
This function (that I found here) gets all parameters of a given function:
function getArgs(func) {
var args = func.toString().match(/function\s.*?\(([^)]*)\)/)[1];
return args.split(',').map(function(arg) {
return arg.replace(/\/\*.*\*\//, '').trim();
}).filter(function(arg) {
return arg;
});
};
So, combining this function with the arguments of your function myFunction, we can get an array that has what you want:
function myFunction (arg1, arg2 = "bob") {
var thisArguments = arguments;
console.log(getArgs(myFunction, thisArguments));
};
function getArgs(func, argums) {
var args = func.toString().match(/function\s.*?\(([^)]*)\)/)[1];
var argsArray = args.split(',').map(function(arg) {
return arg.replace(/\/\*.*\*\//, '').trim();
}).filter(function(arg) {
return arg;
});
for(var i = 0; i < argsArray.length; i++){
argsArray[i] += " (default)";
}
var defaults = argsArray.slice(argums.length);
argums = Array.prototype.slice.call(argums);
return argums.concat(defaults);
};
Now, we can see the information in the console calling myFunction:
1. Passing more arguments than parameters
This will return only the arguments.
myFunction("foo", "bar", "baz");
//returns: ["foo", "bar", "baz"]
2. Passing less arguments than parameters
Will return the arguments and the remainder parameters as default, as you want (I added "default" to each string).
myFunction("foo");
//returns ["foo", "arg2 = "bob" (default)"]
3. Passing no arguments
This will return all the parameters.
myFunction();
//returns ["arg1 (default)", "arg2 = "bob" (default)"]
This is the fiddle: https://jsfiddle.net/gerardofurtado/25jxrkm8/1/
Does jQuery provides a API to call functions binding the this variable to the jQuery object? E.g.:
function log() {
console.log(this);
}
$('body').execute(log); // prints the $('body') object in the console
I know that this could be solved by making the log a plugin, but I don't want to do that because the function I need to call is generic and I don't want to tie it to jQuery.
EDIT:
jQuery has no execute method, its just a snippet I added to demonstrate what I am trying to achieve.
EDIT 2:
I am not asking how to workaround this problem (underscore.js bind already got me covered), I am only asking if jQuery already provides a similar API.
You can do this "without" having jQuery to implement anything, just "flip" things around a bit and use function-name.apply (object, arguments)
function func (arg1, arg2) {
console.log ("ARG1: " + arg1);
console.log ("ARG2: " + arg2);
console.log ("using this: " + this.html ().length + "\n");
}
func.apply ($('body'), ['abc','123']);
func.apply ($('body')); // 2nd argument is optional
output
ARG1: abc
ARG2: 123
using this: 51645
ARG1: undefined
ARG2: undefined
using this: 51645
this isn't going to be what you want it to be in your scope. You can use an anonymous function with a parameter, though:
function log($this) {
console.log($this);
}
$('body').execute(function() { log($(this)); }); // prints the $('body') object in the console
Check this out:
$.fn.prototype.execute = function() {
var f = function(){ console.log(this)};
$.proxy( f , this )()
// that means something like
// f.call(this)
}
Assume , you have function:
var f= function( arg1, arg2){
console.log(this, arg1, arg2)
}
There you call f passing context and arguments
f.call(document.body, arg1,arg2);
or arguments as array by apply method
f.apply(document.body, [arg1,arg2]);
In both cases you will get
>> <body>...</body>, valOfarg1, varOfarg2