I'm reading "JavaScripts The Good Parts"(by Douglas Crockford). It's good.
I can not understand following code. It's working correctly. But I can not understand how it works.
There are 5 questions about this code.
values of arguments (I checked them as [A], [B] in comment)
reason that document.writeln does not work(i.e. show nothing) in function (I checked test position as [C], [D] in comment)
result of slice in this code (in [A], [B])
values of variable args (in [A], [B])
reason that document.writeln shows 'undefined' (in comment [E] )
After showing that code, I will explain more.
var myTest = {};
Function.prototype.method = function(name, func) {
if(!this.prototype[name])
{
this.prototype[name]=func;
}
};
Function.method('bind', function(that) {
var method = this,
slice = Array.prototype.slice,
args = slice.apply(arguments, [1]); //[A] value of arguments and args
//-------------------------------
//[C] following document.writeln do not work. why?
document.writeln("<C>arguments[0]:", arguments[0]);
document.writeln("<C>arguments[1]:", arguments[1]);
document.writeln("<C>args:",args);
myTest.str1 = 1;
myTest.str2 = "ABC";
document.writeln("<C>str1:", myTest.str1);
document.writeln("<C>str2:", myTest.str2);
//-------------------------------
return function(){
//-------------------------------
//[D] following document.writeln do not work. why?
document.writeln("<D>arguments[0]:", arguments[0]);
document.writeln("<D>arguments[1]:", arguments[1]);
document.writeln("<D>args:",args);
myTest.str3 = 2;
myTest.str4 = "DEF";
document.writeln("<D>str3:", myTest.str3);
document.writeln("<D>str4:", myTest.str4);
//-------------------------------
return method.apply(that, args.concat(slice.apply(arguments, [0]))); //[B] value of arguments and args
};
});
var x= function(){
return this.value;
}.bind({value:666});
//-------------------------------
document.writeln("str1:", myTest.str1, "<br/>"); //[E]show undefined. why?
document.writeln("str2:", myTest.str2, "<br/>"); //[E]show undefined. why?
document.writeln("str3:", myTest.str3, "<br/>"); //[E]show undefined. why?
document.writeln("str4:", myTest.str4, "<br/>"); //[E]show undefined. why?
//-------------------------------
alert(x()); //666 -> no problem
document.writeln in [C], [D], [E] was for testing. But they don't work as I expected.
Alerting '666' operates well. (No problem)
Help me, please...
----------- after removing if-confition in Function.prototype.method I got following result
[C]arguments[0]:[object Object]
[C]arguments[1]:undefined
[C]args:
[C]str1:1
[C]str2:ABC
str0C:[object Object]
str1C:undefined
str0D:undefined
str1D:undefined
str1:1
str2:ABC
str3:undefined
str4:undefined
[D]arguments[0]:undefined
[D]arguments[1]:undefined
[D]args:
[D]str1:2
[D]str2:DEF
str0C:[object Object]
str1C:undefined
str0D:undefined
str1D:undefined
str1:1
str2:ABC
str3:2
str4:DEF
Now, I wonder real value of arguments in [C], [D], the contents of array...
Using JSON.stringify(arguments[0])), I finally got the real value of arguments.
[C]arguments[0]:{"value":666}
This is what I really want.
[C]arguments[1], [D]arguments[0], [D]arguments[1] do not have values. So they are 'undefined', right.
Now, I want to know what "args = slice.apply(arguments, [1]);" does in [A].
args = slice.apply(arguments, [1]);
As I know, slice returns copy of array. Because we use apply, slice works using arguments as this, and [1] as array of argument. At this time, arguments have only one item {"value":666"}. slice(start, end) returns copy of array which starts from 'start' and ends with 'end'. Then, in this case slice.apply(arguments, [1]) must return copy of arguments which starts from 1. But as we know arguments have only 1 item, so arguments[1] does not exist.(arguments[0], {value:666} exists.) Then how this work? args is undefined. What does this do? Is there anything that I overlooked?
In [B] it returns "method.apply(that, args.concat(slice.apply(arguments, [0])));".
return method.apply(that, args.concat(slice.apply(arguments, [0])));
In this, [0] is array of argument. 0.. Why zero? And arguments is undefined. Um... Then this returns arguments[0]? And that is {value:666}? And alert '666'?
I got it..
arguments in [A] is arguments of bind function
arguments in [B] is arguments of x function
Right?
Now there is one (maybe last) question .. ^^
When I call document.writeln.bind(document,"TEST") , what is 'that' in Function.method('bind', function(that)?
'that' is document? or 'that' is [document, "TEST"]
Function.prototype.method = function(name, func) {
if(!this.prototype[name])
…is your problem. You're not overwriting existing functions - and Function.prototype.bind does already exist (and has the same result as the one you defined, but without logging). That way, you don't overwrite it with your custom implementation, and none of your document.writeln calls or myTest assignments will be executed.
You can remove the if-condition for test purposes, and it will work. You should be able to answer the other questions yourself, once you see in the output what happens.
Now, I wonder real value of arguments in [C], [D], the contents of array...
There's only one object in question: {value:666} :-) You can output that by using JSON.stringify:
document.writeln("<C>arguments[0]:", JSON.stringify(arguments[0]));
…
[slicing] Then, in this case slice.apply(arguments, [1]) must return copy of arguments which starts from 1.
Yes. Btw it would've been easier to write slice.call(arguments, 1) - they're the same.
But as we know arguments have only 1 item, so arguments[1] does not exist.(arguments[0], {value:666} exists.) Then how this work? args is undefined.
Not undefined, but the empty array [].
What does this do? Is there anything that I overlooked?
It takes further, optional arguments to bind. You didn't pass any.
In [B] it returns "method.apply(that, args.concat(slice.apply(arguments, [0])));". In this, [0] is array of argument. 0.. Why zero?
Because you want to get all of the (optional) arguments to the bound function - slicing them from the beginning.
And arguments is undefined. Um... Then this returns arguments[0]? And that is {value:666}? And alert '666'?
arguments is not undefined, but has length 0, so the whole args.concat(…) evaluates to an empty array. that still points to the {value: 666}, yes - it is what will be passed for the this keyword.
So what are all these optional arguments about?
In your x example you've only used this, your function did not really have any arguments. But it could have - let's try one that has more arguments, like console.log:
var logger = console.log.bind(console, "Here:");
logger(); // Here:
logger(5); // Here: 5
logger({value:666}); // Here: {value: 666}
If you're not (yet) comfortable with the console, you could equally use document.writeln.bind(document, ….
Related
I recently watched a video where the teacher demonstrated that the javascript code
alert.call.apply(function(a) {return a}, [1,2])
results always in "2". So here is my question: Why??
I could not find any explination why this result is returned.
This code is just too clever!
But wait, the following code will produce the same result:
console.log.call.apply(function(a) {return a}, [1,2])
Here is what's happening: apply()'s first parameter is a 'this', the second is a list of argument. It then calls the function console.log.call with those parameters, which logically is equivalent to:
(function(a) {return a}).call(1,2)
This code produces the same result and is a little bit easier to understand - we are using call() on the unnamed function. call()'s parameters are a 'this' object followed by arguments. In this case 'this' is 1, and the argument is 2, so the function gets called with a assigned to 2 (the 1 is not used in the function which is unbound). So this will always return 2, since it is simply returning the second item in the list.
But what is the role of alert, and why can we substitute any function name there? Well, it seems that there is a single call() function that is shared between all prototypes. You can verify that hypothesis by running
alert.call === console.log.call
true
So it doesn't matter if we use alert, console.log, or nothing, we are always using the same call() function.
I was experimenting with JavaScript and I ended up with this snippet:
var num = new Number(20);
function f(n) {
n["foo"] = "bar";
}
// the printed object has a "foo" property.
console.log(num, f(num));
I never thought the printed object would have the foo property added to it in the f(num) call, because f(num) comes after the num in my console.log (I am using Chrome).
But that happened and my immediate thought was that console.log was sort of running all the arguments and printing out the results or something, so I went over to MDN's documentation for console.log, but there wasn't anything about that.
Then I stumbled upon this StackOverflow question.
The second and third questions say that console.log is a little late when used with objects, but in that case, the printed object in the following snippet should have had a foo property as well (since it is a Number object), it doesn't:
var num = new Number(20);
function f(n) {
n["foo"] = "bar";
}
// no "foo" property
console.log(num);
f(num);
The accepted answer to the question I mentioned pretty much says what the second and third answer say, except, it also states that the behavior is console or browser dependent. So, how do different browsers treat this sort of thing nowadays (that question is from 6 years ago) ? Is console.log asynchronous for extremely large objects only ? And the original question, why does the num in my first example get printed out with a foo property ?
// the printed object has a "foo" property.
console.log(num, f(num));
This behavior is not specific to console.log. All of the arguments to a function are evaluated (in order) before the function starts running. Since f(num) is an argument to console.log, it is called before console.log gets a chance to look at num.
In essence, what's going on is:
var arg1 = num;
var arg2 = f(num); // modifies num in place, so arg1 changes as well
console.log(arg1, arg2);
Cannot reproduce MDN's example («Using an object in an array-like fashion»).
let obj = {
length: 0,
addEl: function (element) {
[].push.call(this, element);
};
};
// Node REPL still expect me to do something, so there's an error. Why?
Could you, guys, explain what's wrong here? Also it seems that I don't get the point with the mechanics here:
// from the example:
obj.addElem({});
obj.addElem({});
console.log(obj.length);
// → 2
What if we call the function with some different agrument, not {}, will it work? And if it won't, then why we should use {} exactly? What is the this context here: addEl method or the object itself? If the second, why not addEl function: it's not an array function, so it should have its own this (and, I guess, I'd use something like objThis = this; property).
One more related question is here.
The code in your post has some typos:
let obj = {
length: 0,
addEl: function (element) {
[].push.call(this, element);
};
^ syntax error
};
// Node REPL still expect me to do something, so there's an error. Why?
As you suspected in your comment in the code,
there is a syntax error, which I marked for you.
Remove that semicolon.
And then, when trying the example you wrote obj.addElem,
but in the above object literal you have addEl.
The example should work just fine, if you simply copy-paste it.
var obj = {
length: 0,
addElem: function addElem(elem) {
// obj.length is automatically incremented
// every time an element is added.
[].push.call(this, elem);
}
};
// Let's add some empty objects just to illustrate.
obj.addElem({});
obj.addElem({});
console.log(obj.length);
// → 2
What if we call the function with some different argument, not {}, will it work?
Sure it will. Why wouldn't it? An array in JavaScript can contain values of different types.
It doesn't need to be homogeneous,
so yes, you can insert other things than {}.
What is the this context here: addEl method or the object itself?
It's the object on which the method is called. So it's obj.
This is how method invocation works.
When you call obj.something(), the this inside something will be the obj.
If you still have some doubts about this example, feel free to drop a comment.
Since an object is not an array, but can behave like an array you need to borrow push from the Array object.
But in this case this refers to the array object created with the shorthand []. So we need to change this into the scope for obj using call.
Because there is a length property defined, push will update this value.
An empty object is passed as an element {}, but any other will do:
let obj = {
length: 0,
addEl: function(element) {
Array.prototype.push.call(this, element); //also borrowing push from the array.prototype prevents an extra array to be made in memory every time we call upon this function.
} //« fixed the typo here
};
obj.addEl({});
obj.addEl(1);
obj.addEl('a');
obj.addEl(true);
console.log(obj);
var array = {
length: 0,
push: function(obj){
this[this.length] = obj;
this.length++;
}
}
array.push(23);
You can try this, this solves your problrm i guess.
Recently I discovered that this syntax works in JavaScript (Chrome 53):
function foo([param1]) { // Function argument is declared as array and param1 is used as variable? What is the name of this syntax?
console.log(param1);
}
foo(['TestParameter1']); // Case 1 - works. Output: TestParameter1
foo('TestParameter1'); // Case 2 - works??? Why? Output: TestParameter1
foo(123); // Case 3 - does not work - VM860:1 Uncaught TypeError: undefined is not a function(…)
Result => TestParameter1 // this is the result
I see that param1 can be used as variable that references item with index 0 in the first argument (declared as array).
My questions are:
1) How is this syntax named (the [param1] part that lets you use param1 as variable)?
2) Why does "Case 2" work? Is there any automatic conversion?
As #Xufox pointed out, this works because of destructuring (array destructuring, to be more specific). Your second example works because a string is an array-like object, so you get T, which is param1[0]. Numbers are not arrays (or even array-like), so the the engine fails to destructure the argument.
If you coerce your number to a string it will work:
foo((123).toString());
This seems to be destructuring as #Xufox correctly pointed out.
Function parameters can in fact have destructuring:
go to https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
Search for this text: Pulling fields from objects passed as function parameter
Now the above shows an example of another kind of destructuring, example given below:
function userId({id}) {
return id;
}
var user = {
id: 42,
displayName: "jdoe"
};
console.log("userId: " + userId(user)); // "userId: 42"
however, I assume it applies to this as well:
function foo([param1]) {
console.log(param1);
}
Difference between integers and strings in this behaviour:
console.log('123'); //works, outputs 1, '123' = ['1', '2', '3'] of chars
console.log(['123']); //works, outputs 123
console.log([123]); //works, outputs 123
console.log(123); //error
In the above example, since strings are nothing but an array of chars, its perfectly fine that it works actually.
Exactly as said by those brilliant folks above. Here's how the computer reads it:
foo('testParamater1') = foo(['testParamater1']);
but...
foo(123) = foo([[1,2,3]);
Unfortunately for your specific use case, not the same. Sorry!
When I tried to run the following piece of code:
function flatten(a) {
return [].slice.call(arguments).reduce(function(acc, val) {
return acc.concat(Array.isArray(val) ? flatten.call(null, val) : val);
}, []);
}
I got the following error:
Uncaught RangeError: Maximum call stack size exceeded
But if I used flatten.apply instead of flatten.call, it works perfectly. (deep flattens the input array).
In addition to this link1 and link2, I read few other blogs and articles to understand why it behaves so. I either couldn't find the answer or I must have overlooked it. (pardon my soar eyes if in latter case)
Of course, the fundamental difference between these 2 is:
apply - requires the optional parameter be an array
call - requires the optional parameters be listed explicitly
Usually, a function may take any type of parameters - primitives and non-primitives. So, my questions are:
Could one of the optional parameters to the call method be of type Array or other non-primitive types?
Why does the above code exceeds call stack, when call is used?
Edit: Since there were 2 call methods used, my description was ambiguous. I made changes to clarify.
Your mistake is here:
[].slice.call(arguments).reduce
^^^^^^^^^
so when you pass [x], you're calling reduce on [[x]] and the first val becomes [x]. Then you call flatten once again with [x] and the story repeats itself.
On the other side, apply(..., val) will pass just x to flatten, thus reducing the nesting level by one.
If you're interested on how to use apply to flatten a deeply-nested array, this is possible without recursion:
while(ary.some(Array.isArray))
ary = [].concat.apply([], ary);
Here's a small illustration of call vs apply:
function fun() {
var len = arguments.length;
document.write("I've got "
+ len
+ " "
+ (len > 1 ? " arguments" : "argument")
+ ": "
+ JSON.stringify(arguments)
+ "<br>");
}
fun.call(null, 123);
// fun.apply(null, 123); <-- error, won't work
fun.call(null, [1,2,3]);
fun.apply(null, [1,2,3]);
When you call flatten.call(null, val), with val being an array, the flatten function receives as argument val what you passed as optional argument to call (the type isn't checked), so arguments is [val].
You see your val array is inside an array. When you call reduce, val, inside the callback, will still be an array, you flattened nothing, that's why it never stops.
When you call flatten.apply(null, val), with val being an array, the flatten function receives as arguments the elements of val, so arguments is identical to val (not [val]) : you've effectively unwrapped the array. That's why it works.