Hi can someone explain why in backbone example app (http://backbonejs.org/examples/todos/index.html) in remaining() function, is called using apply (this.without.apply(this, this.done());) and not this.without(this.done())
// Filter down the list of all todo items that are finished.
done: function() {
return this.where({done: true});
},
// Filter down the list to only todo items that are still not finished.
remaining: function() {
return this.without.apply(this, this.done());
},
Thank You !
#Update
Debugger output
this.without(this.done())
[child, child, child, child]
this.without.apply(this, this.done());
[child, child, child]
Variable list of arguments
The key is in the way without is written:
function () {
var args = slice.call(arguments);
args.unshift(this.models);
return _[method].apply(_, args);
}
It's anticipating a variable list of arguments, and one way to do that is to use apply:
...
return this.without.apply(this, ['pass', 'these', 'arguments']);
There's more about apply in the MDN documentation.
You asked what is the difference between these two calls:
this.without( this.done() )
vs.
this.without.apply( this, this.done() );
To clarify, let's remove the nested this.done() call. Now the first one is:
var value = this.done();
this.without( value );
That code obviously calls this.without() and passes it a single argument, whatever value was returned by this.done(). If value happens to be an array, the entire array is passed as a single argument.
The second version becomes:
var array = this.done();
this.without.apply( this, array );
That calls this.without() with a variable number of arguments, one argument for each element of array. (And I called it array instead of value this time, because for this code to make sense it has to be an array.)
.apply() also sets this in the called function, so passing this as the first argument just passes this along to that function in the same manner as a regular this.without() method call.
apply also lets you specify the "this" object for the function.
That matters sometimes, like when it is being called in a closure, where "this" might be different than you intended.
Related
I was breaking down some code I found. I got stuck on a specific issue and managed to break it down into a smaller piece. Just keep in mind that this code is part of a much bigger piece of code.
function foo(string, num) {
console.log("calling foo ---", string, num);
}
This following bit looks like it should work. I pass my two arguments into the array that's passed into apply.
But as you can see by the comment I added. The num parameter is undefined. And the string parameter has the second value in the array I passed in, which was intended to be the for num.
foo.bind.apply(foo, ["fdsf", 432])(); // calling foo --- 432 undefined
But if I add an additional value at the beginning of the array (it could be anything), Then everything works smoothly.
foo.bind.apply(foo, [0, "fdsf", 432])(); // calling foo --- fdsf 432
Can anyone please shed some light on what's happening here? If I call bind or apply separately, the array padding is not needed. It only seems to be needed when combining them.
Why is the additional value at the beginning of the array needed?
Also, here is the original code for reference. It's from one of Kyle Simpsons books.
function asyncify(fn) {
var orig_fn = fn,
intv = setTimeout( function(){
intv = null;
if (fn) fn();
}, 0 );
fn = null;
return function() {
if (intv) {
fn = orig_fn.bind.apply(orig_fn, [this].concat([].slice.call(arguments)));
} else {
orig_fn.apply( this, arguments );
}
};
}
There's a lot more going on here. But inside the if statement about 2/3's though the code, you can see it.
The second argument for apply is the array, with the first value (the padding) being the global object (I'm assuming it's the global object, it's not important, it could be anything). followed by any of the arguments that were passed in.
Some of that extra bit is just turning the arguments array-like object into an array.
The first argument that bind accepts is the this value to be used inside the function. So, if you use
fn.bind.apply(fn, arr)
the first item of the arr becomes the first argument of bind, eg, it's equivalent to:
fn.bind(arr[0], arr[1], arr[2] /* ... */ )
So the first item of the array becomes the this value, the second item of the array becomes the first argument, the third item of the array becomes the second argument, etc.
Here's a live example of how "padding" the array with a value at the start becomes the new this value when the function is called:
const obj = { prop: 'val' };
function foo(string, num) {
console.log("calling foo ---", string, num);
console.log('this:', this);
}
const fn = foo.bind.apply(foo, [obj, 'str', 55]);
fn();
As for the repetitive nature of
foo.bind.apply(foo, ...
It's necessary to repeat the function name when calling apply so that when bind is called, it's called with the right calling context - the this value used when .bind is called is how .bind knows which function to bind. (The first argument passed to .apply, just like .call, is the this value to be used in the function being applied)
I would like to understand the meaning of that code fragment. "saveTo" is a array, the programmer assigned a function() to the splice method. I don't understand what does it mean. Is that a override? What is the meaning of the return argument?, and why the function takes no argument while splice requires 2 or more arguments?
saveTo.splice = function() {
if (saveTo.length == 1) {
$("#send").prop("disabled", true);
}
return Array.prototype.splice.apply(this, arguments);
};
Javascript lets you re-assign methods at runtime. In this case, what the programmer was doing is reassigning splice on this specific instance of an array in order to call a jQuery method. Beyond that, it works in exactly the same way as the existing splice as they are calling return Array.prototype.splice.apply(this, arguments); - meaning that this method just passes on whatever arguments are passed to it.
Here's a demo:
var myArray = [1,2,3,4];
console.log("Splice before re-assing: ", myArray.splice(1,1));
// reset it.
myArray = [1,2,3,4];
myArray.splice = function(){
console.log("From inside new splice function");
return Array.prototype.splice.apply(this, arguments);
}
console.log("Splice after re-assiging: ", myArray.splice(1,1));
Whether this is a good thing to do is debatable. It breaks a few principles of programming.
The programmer that wrote this code knew that some other part of the program is calling splice on this array, and he wanted to attach an event to that, in order to update the user interface (hence the call to jQuery).
This is commonly called "Monkey Patching". You can read about it at https://www.audero.it/blog/2016/12/05/monkey-patching-javascript/
This is not a good pratice as it obfuscate what is happening: no programmer would expect that calling a data manipulation function has side-effects somewhere else.
You can run this sample to understand how it works:
const myArray = [];
// Patch push method only for this instance of array.
myArray.push = function() {
// log event
console.log('myArray.push was called with the following arguments', arguments);
// Call the original push function with the provided arguments.
return Array.prototype.push.apply(this, arguments);
}
myArray.push(1);
You can also patch methods for all instances of a given class:
// Patch push method on all arrays
const originalPush = Array.prototype.push;
Array.prototype.push = function() {
// log event
console.log('.push was called with the following arguments', arguments);
// Call the original push function with the provided arguments.
return originalPush.apply(this, arguments);
}
const myArray = [];
myArray.push(1);
As for your question about the arguments, in javascript all functions can access the arguments array-like object that contains the arguments the function was called with, which does not depend on which arguments are specified in the original declaration.
function doSomething(arg1) {
console.log(arguments[2]);
}
doSomething(1, 2, 3); // outputs "3"
Here is the MDN documentation about it: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments
Note that there is a better way to extend arrays in ES6:
class CustomArray extends Array {
splice(...args) {
if(this.length === 1) {
$("#send").prop("disabled", true);
}
super.splice(...args);
}
}
Now that there are other ways to change the arrays length, .length, .pop, .shift, etc. so those should be overriden as well. However then it is still questionable wether the code calling those methods should not just cause the side effect.
What this does is it adds some checks for specifically saveTo.splice. If you call anyOtherArray.splice, then it'll just be evaluated as per normal. The reason it takes no arguments is because Array.prototype.splice takes arguments, and also the calling context of saveTo, as well as the array-like objects arguments, representing all the arguments passed to saveTo.splice. So it's just adding a little bit of extra code based on a specific condition - other than that, there's no difference to the native splice.
1) Yes, the programmer has overridden splice method, its not recommended
2) return statement is nothing but calls Array.prototype.splice(the original method).
3) Yes, splice requires arguments, but in JS, you may not define them as function params. You get the passed parameters as an array like object arguments inside your functions,
if you look closely, they call Array.prototype.splice with this and arguments object.
Okay, let's dissect this piece by piece.
saveTo.splice = function() {
if (saveTo.length == 1) {
$("#send").prop("disabled", true);
}
return Array.prototype.splice.apply(this, arguments);
};
As we all know that in JavaScript functions are first class objects, so if we have an object let's say saveTo something like this:
const saveTo = {};
Then we can assign a function to one of its properties like :
saveTo.splice = function() {
};
or something like this to:
const saveTo = {
splice: function() {
}
};
With that out of the way, you are just calling the Array#prototype#splice method to create a shallow copy out of the array and passing it an iterable to it.
So in total you have overridden the native Array#prototype#splice to fit your requirement.
I am trying to check every element within the target object is an array object by using the every()function, the code as follows:
let targetObj = [[1,2],[3],4];
let result = targetObj.every((val,index,arr)=>{
return Array.isArray(val);
});
the resultis false because 4 is not an array object, but i found if i replace the callback function with Array.isArray which is still work:
let result = targetObj.every(Array.isArray);
So my questions are:
1.In this way, since the callback function is not taking any parameters.Why the result still return false?
2.How the Array.isArraycallback function can automatically check every element from targetObjwithout gave any parameters?
Array.isArray has one param.
.every( ... ) gives 3 params.
A callback in .every (without calling it directly) is going to give params.
If the callback has more than 3 params, the next params are gonna be undefined.
Since you're not calling the callback, .every is calling it for you and it's gonna fill its params.
I am using the backbone library to perform the following:
var Persons = Backbone.Collection.extend({
defaults: {
name: 'unknown',
age: 18
},
over_18: function () {
return this.filter(function (model) {
return model.get('age') > 18
});
},
under_18: function () {
var persons_over_18 = this.over_18;
return this.without(this, persons_over_18); // it does not work!! why?
}
});
persons = new Persons([{age: 17}, {age: 27}, {age:31} ]);
persons.under_18().length; // 3 instead of 1
As you can see the method under_18 is not working properly because it returns all the models
instead of giving me just the models which age attribute is under 18.
So in order to debug my code, I decided to see the the Backbone.js Annotated Source,in particular the following code:
var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl', ... ]; // and more
_.each(methods, function(method) {
Collection.prototype[method] = function() {
var args = slice.call(arguments);
args.unshift(this.models);
return _[method].apply(_, args);
};
});
But the above piece of code is not clear to me and I still cannot make the first one work as I wish.
So my question is how can I fix the first code in relation to the second one?
Here is my code into jsfiddle.net http://jsfiddle.net/tVmTM/176/
Lenghty answer to better understand the Backbone code:
In javascript a "member" for an object can be referenced in two ways:
the usual . notation: foo.say('hello');
the [...] notation, with the string as an argument... a bit like an associative array:
foo["say"]('hello')
What happens in backbone is that each string in the methods array, defined just above this method, is iterated and added to the Collection prototype, so added to all classes that inherit (or extend) Collection.
In the function, the arguments keyword simply references all the arguments passed into the function, even if the signature is empty:
Collection.prototype[method] = function() { // <- empty signature here!
The slice with no arguments will transform the passed arguments into an array. Notice the use of slice.call(...) here (and refer to this SO question).
var args = slice.call(arguments);
unshift then adds the Collection models array to the beginning of the array.
args.unshift(this.models);
Then we are actually calling the Underscore method (using the [...] notation) on the new array of arguments. Using apply, we pass the _ as the first argument which will become the this scope (more info here)
return _[method].apply(_, args);
This allows you to do stuff like:
MyCollection.each(function (model) { ... });
instead of
_.each(MyCollection.models, function (model) { ... });
The resulting effect is identical! The former will just call the latter. :)
To answer your question, the problem in your case is that the _.without method does not accept two arrays but an array followed by a list of arguments; the method you are looking for is called difference (look at this SO question), but it is not mapped into the Collection, so you either map it yourself (replicating the code found in the Backbone source) or just use it directly:
return _.difference(this.models, this.over_18());
Working fiddle: http://jsfiddle.net/tVmTM/177/
In my opinion, better just keep using filter as you did for the over_18 method... Even better, put the over_18 and under_18 as methods in the Model (where they belong) and from the collection just use those.
1) Each string in the methods array corresponds to an underscore method, like _.forEach, _.map, _.reduce etc. For every string in methods a function is added to the Collection prototype that calls that underscore method, but passes the models in the collection as the first argument, followed by any options you pass in.
For example, say you have a collection called Dogs which contains a bunch of Dog models. Calling Dogs.forEach(options) will call a function which calls _.forEach(Dogs.models, options). It's a convenience thing.
2) On line 2 you use that when I think you mean this. On line 3 you have an extra . after without.
In going through some exercises about functional programming, and returning a closure, came across this example, and cannot figure out how it works:
function invoker (NAME, METHOD) {
return function(target) {
var targetMethod = target[NAME];
var args = _.rest(arguments);
return function() { return targetMethod.apply(target, args);});
};
};
The test:
var rev = invoker('reverse', Array.prototype.reverse);
var result= _.map([[1,2,3]], rev);
The output should be:
[[3,2,1]]
but i cannot prove it.
Specific questions:
Why is args:
0,1,2,3
Why is console.log(arguments):
[object Arguments]
How to print out the result, to see [[3,2,1]].
When I print out "result", I get:
[object Object]
After correcting the issues in the code (missing semicolons or too many) there appear to be a few closures that get created throughout the script's execution.
First:
function invoker (NAME, METHOD) {
return function(target) { // In the example this is rev
// Begin Scope1 (closure)
var targetMethod = target[NAME]; // NAME is trapped here in Scope1 (defined outside of this function) when the function is executed!
var args = _.rest(arguments);
return function() { //
// Begin Scope2 (closure)
return targetMethod.apply(target, args); // target and args are trapped here in Scope2 (defined in Scope1's function) when the function is executed!
};
};
}
The invoker does nothing until it is called and the first closure (Scope1's that is) does not get created until invoker is actually invoked:
var rev = invoker('reverse', Array.prototype.reverse);
After rev has been initialized by invoker, it has become a variable that contains a function that accepts a single named argument (called target) that when called will look for a function called NAME (in this case 'reverse') on whatever object target is.
When rev is actually called it also returns a function that, when called, will call the target method (reverse in this case) on target (the array passed in). But rev hasn't been called until the next line is run:
var result= _.map([[1,2,3]], rev);
Here is what is happening here: _.map's purpose is to take a list of items and apply a function to each item in this list. The final result will be a new array with the transformed values in it. The line above passes to _.map a list with exactly one item in it; an array. It also passes to _.map a function to transform each item in that list; in this case rev.
So _.map is called and it calls rev on the only item in the list, our [1,2,3] array. The function rev has the effect of returning yet another function that, when called, will remember NAME, target and args. This function now resides inside the first element of the array variable 'result'.
Now for your questions:
The output should be:
[[3,2,1]]
but i cannot prove it.
In the test code there is no output, only a final variable called result. What I think you are looking for is that somewhere in the end there should be a reversed array. You can verify that the original array has been reversed with the following after the call to initialize result:
alert(result[0]().join());
Why is args:
0,1,2,3
args is really a red herring here; _.map passes 3 arguments to its iterator function. The current value (in this case the array [1,2,3], the key or index (in this case 0) and the original list itself. When _.rest is called on arguments, it slices off the first item leaving us with an array containing 0 and [[1,2,3]]. When reverse is called on this array (args) it ends up returning an array that looks like [1,2,3,0] which is these two items reversed. My tests never showed 0,1,2,3. Why is args a red herring here? Because when reverse is called reverse doesn't take any arguments and so args is ignored. If this was a different function on a different object you would probably encounter issues because of the call to _.rest.
Why is console.log(arguments):
[object Arguments]
Because you are basically calling toString() which will print the object's type.
How to print out the result, to see [[3,2,1]]. When I print out
"result", I get:
[object Object]
You can use:
console.log(result[0]().join());
UPDATE: Here is the jsbin link with working code: http://jsbin.com/aYECIDo/5/edit?html,css,js,output
Use console.log and a modern browser with actual development tools.. like chrome, or firefox+firebug. Then you should be able to see in the console the result. Or, when you print it out in HTML, serialize it using JSON.stringify. Example: document.write(JSON.stringify(result)).
That should write out a JSON representation of what you're seeing.
Regarding the [1,2,3] look at the .apply function in the MDN.
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/apply
Apply takes a first argument of scope, and an array of arguments to cause the function to be called with.