We are looking for ways to tune JavaScript so that we can do some real functional programming. To do this, it is necessary to fully understand function calls and function prototypes. We are looking for ways to tune JavaScript so that we can do some real functional programming. To do this, it is necessary to fully understand function calls and function prototypes.
Function prototype
Now, whether you have read or ignored the article corresponding to the link that has been dropped, we are going to continue!
If we open our favorite browser + JavaScript console, let's take a look at the properties of the Function. prototype object:
; html-script: false ]Object.getOwnPropertyNames(Function.prototype)//=> ["length", "name", "arguments", "caller", // "constructor", "bind", "toString", "call", "apply"]
The output here depends on your browser and JavaScript version. (Chrome 33 is used)
We can see several attributes we are interested in. In view of the purpose of this article, I will discuss the following:
Function. prototype. length
Function. prototype. call
Function. prototype. apply
The first is an attribute, and the other two are methods. In addition to the three variables, I would also like to discuss the special variable arguments, which is slightly different from Function. prototype. arguments (discarded.
First, I will define a "tester" function to help us figure out what happened.
; html-script: false ]var tester = function (a, b, c){ console.log({ this: this, a: a, b: b, c: c });};
This function records the value of the input parameter and the value of "context variable", that is, the value of this.
Now, let's try something:
; html-script: false ]tester("a"); //=> {this: Window, a: "a", b: (undefined), c: (undefined)} tester("this", "is", "cool"); //=> {this: Window, a: "this", b: "is", c: "cool"}
We noticed that if we do not enter 2nd or 3 parameters, the program will display them as undefined (undefined ). In addition, we noticed that the default "context" of this function is the Global Object window.
Use Function. prototype. call
The. call method of a function calls this function in this way. It sets the context variable "this" as the value of the first input parameter, and then transmits one or more other parameters to the function.
Syntax:
; html-script: false ]fn.call(thisArg[, arg1[, arg2[, ...]]])
Therefore, the following two rows are equivalent:
; html-script: false ]tester("this", "is", "cool"); tester.call(window, "this", "is", "cool");
Of course, we can input any parameters as needed:
; html-script: false ]tester.call("this?", "is", "even", "cooler"); //=> {this: "this?", a: "is", b: "even", c: "cooler"}
The main function of this method is to set the value of this variable of the function you call.
Use Function. prototype. apply
The. apply method of the function is more practical than. call. Similar to. call, The. apply call method also sets the context variable "this" as the value of the first parameter in the input parameter sequence. The second parameter of the input parameter sequence is also the last parameter, which is passed in as an array (or an array object of classes.
Syntax:
; html-script: false ]fun.apply(thisArg, [argsArray])
Therefore, all three rows below are equivalent:
; html-script: false ]tester("this", "is", "cool"); tester.call(window, "this", "is", "cool"); tester.apply(window, ["this", "is", "cool"]);
The ability to specify a parameter list as an array is useful in most cases (we will find the benefits of doing so ).
For example, Math. max is a Variable Parameter Function (a function can accept any number of parameters ).
; html-script: false ]Math.max(1,3,2);//=> 3 Math.max(2,1);//=> 2
In this way, if I have a numeric array and I need to use the Math. max function to find the largest one, how can I use a line of code to do this?
; html-script: false ]var numbers = [3, 8, 7, 3, 1];Math.max.apply(null, numbers);//=> 8
The. apply method really starts to show it's importance when coupled with the special arguments variable: The arguments object
The. apply method really starts to show that it is important to include a special parameter: Arguments object.
Each function expression has a special, usable local variable in its scope: arguments. To study its attributes, let's create another tester function:
; html-script: false ]var tester = function(a, b, c) { console.log(Object.getOwnPropertyNames(arguments));};
Note: In this case, we must use Object as above. getOwnPropertyNames, because arguments has some attributes that are not marked as enumerable, if you only use the console. log (arguments.
Now we use the old method to test it by calling the tester function:
; html-script: false ]tester("a", "b", "c");//=> ["0", "1", "2", "length", "callee"] tester.apply(null, ["a"]);//=> ["0", "length", "callee"]
The attributes of the arguments variable include the attributes corresponding to each parameter of the input function, which are no different from the. length and. callee attributes.
The. callee attribute provides reference for the function that calls the current function, but this is not supported by all browsers. For the moment, we ignore this attribute.
Let's redefine our tester function to make it richer:
; html-script: false ]var tester = function() { console.log({ 'this': this, 'arguments': arguments, 'length': arguments.length });}; tester.apply(null, ["a", "b", "c"]);//=> { this: null, arguments: { 0: "a", 1: "b", 2: "c" }, length: 3 }
Arguments: Is it an object or an array?
We can see that arguments is not an array at all, although more or less like. In many cases, although not, we still want to treat it as an array. Convert arguments into an array, which has a very good small function:
; html-script: false ]function toArray(args) { return Array.prototype.slice.call(args);} var example = function(){ console.log(arguments); console.log(toArray(arguments));}; example("a", "b", "c");//=> { 0: "a", 1: "b", 2: "c" }//=> ["a", "b", "c"]
Here we use the Array. prototype. slice Method to convert the class Array object into an Array. Because of this, the arguments object will be extremely useful when used together with. apply.
Some useful examples
Log Wrapper)
The logWrapper function is built, but it only works correctly under the mona1 function.
; html-script: false ]// old versionvar logWrapper = function (f) { return function (a) { console.log('calling "' + f.name + '" with argument "' + a); return f(a); };};
Of course, our existing knowledge allows us to build a logWrapper function that can serve any function:
; html-script: false ]// new versionvar logWrapper = function (f) { return function () { console.log('calling "' + f.name + '"', arguments); return f.apply(this, arguments); };};
By calling
; html-script: false ]f.apply(this, arguments);
We are sure that this function f will be called in the context that is exactly the same as before. Therefore, if we are willing to replace those logging functions in our code with the new "wrapped" version, it is not abrupt. Place the native prototype Method in the public function library. The browser has a large number of extremely useful methods that we can "borrow" to our code. This variable is often treated as "data. In function programming, we don't have this variable, but we need to use the function anyway!
; html-script: false ]var demethodize = function(fn){ return function(){ var args = [].slice.call(arguments, 1); return fn.apply(arguments[0], args); };};
Some other examples:
; html-script: false ]// String.prototypevar split = demethodize(String.prototype.split);var slice = demethodize(String.prototype.slice);var indexOfStr = demethodize(String.prototype.indexOf);var toLowerCase = demethodize(String.prototype.toLowerCase); // Array.prototypevar join = demethodize(Array.prototype.join);var forEach = demethodize(Array.prototype.forEach);var map = demethodize(Array.prototype.map);
Of course, there are many. Let's see how these are executed:
; html-script: false ]("abc,def").split(",");//=> ["abc","def"] split("abc,def", ",");//=> ["abc","def"] ["a","b","c"].join(" ");//=> "a b c" join(["a","b","c"], " ");// => "a b c"
Digress:
We will demonstrate later that the demethodize function is actually better used by parameter flip.
In functional programming, you usually need to take the "data" or "input data" parameter as the rightmost parameter of the function. The method usually binds this variable to the "data" parameter. For example, the String. prototype method usually operates on the actual String (that is, "data "). The same is true for the Array method.
The reason may not be understood immediately, but this will happen when you use colized or composite functions to express richer logic. This is exactly what I mentioned in the Introduction section about UnderScore. js. I will introduce it in detail later. Almost every Underscore. js function has a "data" parameter and serves as the leftmost parameter. This eventually makes it very difficult to reuse, and the code is hard to read or analyze. :-(
Manage Parameter order
; html-script: false ]// shift the parameters of a function by onevar ignoreFirstArg = function (f) { return function(){ var args = [].slice.call(arguments,1); return f.apply(this, args); };}; // reverse the order that a function accepts argumentsvar reverseArgs = function (f) { return function(){ return f.apply(this, toArray(arguments).reverse()); };};
Composite Functions
In the functional programming world, it is extremely important to combine functions. The general idea is to create small, testable functions to express a "unit logic", which can be assembled into a larger "structure" that can do more complex work"
; html-script: false ]// compose(f1, f2, f3..., fn)(args) == f1(f2(f3(...(fn(args...)))))var compose = function (/* f1, f2, ..., fn */) { var fns = arguments, length = arguments.length; return function () { var i = length; // we need to go in reverse order while ( --i >= 0 ) { arguments = [fns[i].apply(this, arguments)]; } return arguments[0]; };}; // sequence(f1, f2, f3..., fn)(args...) == fn(...(f3(f2(f1(args...)))))var sequence = function (/* f1, f2, ..., fn */) { var fns = arguments, length = arguments.length; return function () { var i = 0; // we need to go in normal order here while ( i++ < length ) { arguments = [fns[i].apply(this, arguments)]; } return arguments[0]; };};
Example:
; html-script: false ]// abs(x) = Sqrt(x^2)var abs = compose(sqrt, square); abs(-2); // 2