Vanilla Javascript use of bind() with REST parameter - javascript

Why bind() passes even the type of event with rest parameters. How to avoid passing the event 'click'?
function say(...numbers) {
for (const number of numbers) {
console.log(number);
}
}
window.addEventListener('DOMContentLoaded', () => {
const el = document.getElementById('input_10_5');
el.addEventListener('click', say.bind(null, 1, 2, 3));
});
Console.log result:
1
2
3
click { target: input#input_10_5.medium, buttons: 0, clientX: 1062, clientY: 732, layerX: 96, layerY: 24
}

You can't. The event handling system always passes the event.
The callback function itself has the same parameters and return value as the handleEvent() method; that is, the callback accepts a single parameter: an object based on Event describing the event that has occurred, and it returns nothing.
https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#the_event_listener_callback
If your goal is to iterate the arguments and treat them all the same then how about:
function say(numbers, event)
say.bind(null, [1, 2, 3])

This behavior doesn't have to do with rest parameters .... It is more to do with the behavior of .bind(). When you bind a function with arguments (in this case you've used 1, 2, 3), calling that bound function causes those bound arguments to be passed to the function first, then followed by any additional arguments you call the bound function with, eg:
function foo(one, two, three, evt) {
console.log(one, two, three, evt);
}
const bar = foo.bind(null, 1, 2, 3);
bar({type: "click"}); // becomes the 4th argument (after 1, 2, 3)
Under the hood, JavaScript is calling your bound function that you pass to .addEventListener() with the event object, which ends up becoming the fourth argument to your say function. This is then included as part of the numbers array. You can instead create a wrapper function that gets passed event (which you ignore), and then use .call() to call your function (using .call() here instead of just () allows you to explicitly define the this value like you're doing with .bind()):
window.addEventListener('DOMContentLoaded', () => {
const el = document.getElementById('input_10_5');
el.addEventListener('click', () => say.call(null, 1, 2, 3));
});

Look at the documentation:
The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.
You can't prevent bind from doing that.
If you don't want that, don't use bind.
Just create a new function the traditional way instead.
el.addEventListener('click', function () { say(1, 2, 3) });

Related

Understand arrow function based on event

I have the following codes:
class Autocomplete {
constructor(ele, options) {
this.ele = ele;
this.options = options;
this.input = this.ele.querySelector('.input');
// with keyup we want to fire it right away
// this.input.addEventListener('keyup', (e) => this.display(e);
}
}
The display function will then display the user's input onto the page. However, I wonder what the difference is between these 3 functions:
1)
this.input.addEventListener('keyup', (e) => this.display(e));
this.input.addEventListener('keyup', this.display);
this.input.addEventListener('keyup', () => this.display());
Only function 1 and 2 work, and to my understanding, function 1 will put e as argument into the display function whereas function 2 will execute immediately on keyup. However, for 3, why does it not work, because I thought the e argument is passed in implicitly by the DOM API?
Out of the first 2 functions, which one is more performant and why?
Arrow function is not bound to the class where it is declared when it is called, you should use function only if you want to use the this in the function because in the arrow function, it's the global this that is get called

Event with named function [duplicate]

This question already has answers here:
javascript addEventListener not working more than once
(2 answers)
Closed 3 years ago.
I try to add an event listener on a button element. A named function with one argument is triggered on click.
My problem is that the function is automatically triggered on page loading and is not working on click. What is the problem?
Is it possible to use an anonymous function while passing an argument to it?
I tried with an named function simply declared and stored in a variable, both don't work.
I tried with an anonymous function but I didn't find out how to pass an argument to it.
var tab = [-2, 1, 4];
function additionne(x){
return x + 2;
}
function affiche(tab){
alert(additionne(tab[0]));
alert(additionne(tab[tab.length - 1]));
}
var bouton = document.getElementById('bouton');
bouton.addEventListener('click', affiche(tab));
I expect the event to be triggered on click and only then.
It is actually triggered only once, on page loading and not on click.
By writing as affiche(tab) you call the function immediately. In order to pass it as a callback with arguments, you can use bind() to bind scope and arguments.
var tab = [-2, 1, 4];
function additionne(x){
return x + 2;
}
function affiche(tab){
alert(additionne(tab[0]));
alert(additionne(tab[tab.length - 1]));
}
var bouton = document.getElementById('bouton');
bouton.addEventListener('click', affiche.bind(this, tab));
// ^^^^ ^^^
// scope argument(s)
<button id="bouton">Click me</button>
Hope this helps
You need to wrap it in a function.
var tab = [-2, 1, 4];
function additionne(x){
return x + 2;
}
function affiche(tab){
alert(additionne(tab[0]));
alert(additionne(tab[tab.length - 1]));
}
var bouton = document.getElementById('bouton');
bouton.addEventListener('click', function() { affiche(tab);});
<button id="bouton">Alert</button>
addEventListener takes a callback function as the 2nd argument.
You should use
function () { ... }
Instead of
affiche(tab)
Which is a function call (and not a function)

Why is the wrapper function required for the predicate function in reduce?

I was playing around with the interaction between Array.reduce and Set and I noticed the following strange behavior.
Normally this works:
console.log(
Set.prototype.add.call(new Set(), 1, 0, [])
);
// Set { 1 }
But if I were to combine that with reduce, the following does not work:
console.log(
[1,2,3].reduce(Set.prototype.add.call, new Set())
);
// TypeError: undefined is not a function
// at Array.reduce (<anonymous>)
However if I were to wrap the predicate function in a wrapper, this will work:
console.log(
[1,2,3].reduce((...args) => Set.prototype.add.call(...args), new Set())
);
// Set { 1, 2, 3 }
I tried this on different JS engines (Chrome and Safari) and got the same result so its probably not an engine specific behavior. The same applies to a Map object as well. What I can't figure out is why that is the case.
Without wrapping, your Set.prototype.add.call lose its this value (wich should be Set.prototype.add function, but instead is set to undefined).
Try this:
[1,2,3].reduce(Set.prototype.add.call.bind(Set.prototype.add), new Set());
See http://speakingjs.com/es5/ch01.html#_extracting_methods
There are actually two parts of the script that need the proper calling context (or this value) in order to work properly. The first part, which you've already figured out, is that you need to call Set.prototype.add with a calling context of the newly created Set, by passing that Set as the first argument to .call:
// works:
Set.prototype.add.call(new Set(), 1, 0, []);
// works, args[0] is the new Set:
[1,2,3].reduce((..args) => Set.prototype.add.call(..args), new Set());
But the other issue is that the .call needs to be called with the approprite calling context. Set.prototype.add.call refers to the same function as Function.prototype.call:
console.log(Set.prototype.add.call === Function.prototype.call);
The function that Function.prototype.call calls is based on its calling context. For example
someObject.someMethod.call(< args >)
The calling context of a function is everything that comes before the final . in the function call. So, for the above, the calling context for .call is someObject.someMethod. That's how .call knows which function to run. Without a calling context, .call won't work:
const obj = {
method(arg) {
console.log('method running ' + arg);
}
};
// Works, because `.call` has a calling context of `obj.method`:
obj.method.call(['foo'], 'bar');
const methodCall = obj.method.call;
// Doesn't work, because methodCall is being called without a calling context:
methodCall(['foo'], 'bar');
The error in the snippet above is somewhat misleading. methodCall is a function - specifically Function.prototype.call - it just doesn't have a calling context, so an error is thrown. This behavior is identical to the below snippet, where Function.prototype.call is being called without a calling context:
console.log(typeof Function.prototype.call.call);
Function.prototype.call.call(
undefined,
);
Hopefully this should make it clear that when using .call, you need to use it with the proper calling context, or it'll fail. So, to get back to the original question:
[1,2,3].reduce(Set.prototype.add.call, new Set());
fails because the internals of reduce calls Set.prototype.add.call without a calling context. It's similar to the second snippet in this answer - it's like if Set.prototype.add.call is put into a standalone variable, and then called.
// essential behavior of the below function is identical to Array.prototype.reduce:
Array.prototype.customReduce = function(callback, initialValue) {
let accum = initialValue;
for (let i = 0; i < this.length; i++) {
accum = callback(accum, this[i]);
// note: "callback" above is being called without a calling context
}
return accum;
};
// demonstration that the function works like reduce:
// sum:
console.log(
[1, 2, 3].customReduce((a, b) => a + b, 0)
);
// multiply:
console.log(
[1, 2, 3, 4].customReduce((a, b) => a * b, 1)
);
// your working Set code:
console.log(
[1,2,3].customReduce((...args) => Set.prototype.add.call(...args), new Set())
);
// but because "callback" isn't being called with a calling context, the following fails
// for the same reason that your original code with "reduce" fails:
[1,2,3].customReduce(Set.prototype.add.call, new Set());
In contrast, the
(..args) => Set.prototype.add.call(..args)
works (in both .reduce and .customReduce) because the .call is being called with a calling context of Set.prototype.add, rather than being saved in a variable first (which would lose the calling context).

How to pass a callback function with multiple possible parameter lists

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

Callback function - use of parentheses

I'm new to jQuery and am bit confused about the use (or not) of parentheses with a callback function. Say I have a function:
function cb() {
// do something
}
Now what is the difference between:
$("p").hide(1000, cb);
and
$("p").hide(1000, cb());
Is it to do with when the cb function is executed? It would be great if someone could explain this to me in the simplest of terms.
cb() means give me the result of executing the function cb.
cb IS the function cb or, more accurately a pointer (reference) to it.
Is it to do with when the cb function is executed?
Essentially, yes, though the difference does run a little deeper than that.
cb is a reference of sorts to the function. You're passing the function along as a parameter to be invoked somewhere down the line.
cb() is a function call; the function will be invoked, and the result passed as an argument to .hide.
The difference is that in javascript functions are first class objects and can be passed to other functions so that they may executed at a later stage or depending on some logic.
Consider the following:
function add(a, b) {
return a + b;
}
function minus(a, b) {
return a - b;
}
function apply(func, a, b) {
return func(a,b);
}
apply(add, 3, 4); // returns 7
apply(minus, 3, 4); // returns -1
apply(add(), 3, 4); // error: invalid number of arguments for add
apply(add(0,0), 3, 4); // error: add returns 0, but 0 is not a function and
// so apply crashes when it tried to call zero as a function
$("p").hide(1000, cb); passes the function referenced by cb, as a callback.
$("p").hide(1000, cb()); passes the value returned when the function cb is called.
Given:
function cb(){ return true; }
The former is passing the callback for later calling. The latter passes the returned value true, and is essentially $("p").hide(1000, true);

Categories

Resources