Home > Web Front-end > JS Tutorial > Functional JavaScript: .apply(), .call(), and arguments objects

Functional JavaScript: .apply(), .call(), and arguments objects

高洛峰
Release: 2016-12-14 09:21:49
Original
951 people have browsed it

We are looking for ways to tune JavaScript so that we can do some truly functional programming. In order to do this, it is necessary to understand function calls and function prototypes in detail.

Function Prototype

Now, whether you have read or ignored the article linked to above, we are ready to move on!
If we click on 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"]
Copy after login

The output here depends on the browser and JavaScript version you are using. (I'm using Chrome 33)
We see a few properties that interest us. For the purpose of this article, I will discuss these:

Function.prototype.length

Function.prototype.call

Function.prototype.apply

The first one is a property, and the other two are methods. In addition to these three, I would also like to discuss this special variable arguments, which is slightly different from Function.prototype.arguments (which has been deprecated).

First, I'll define a "tester" function to help us figure out what's going on.

; html-script: false ]
var tester = function (a, b, c){
    console.log({
        this: this,
        a: a,
        b: b,
        c: c
    });
};
Copy after login

This function simply records the value of the input parameter and the "context variable", which 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"}
Copy after login

We noticed that if we don’t enter the 2nd and 3rd parameters, the program will show them as undefined. In addition, we note 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 such a way that it sets the context variable this to the value of the first input parameter, and then passes the other parameters one by one. into the function.
Syntax:

; html-script: false ]
fn.call(thisArg[, arg1[, arg2[, ...]]])
Copy after login

So, the following two lines are equivalent:

; html-script: false ]
tester("this", "is", "cool"); 
tester.call(window, "this", "is", "cool");
Copy after login

Of course, we can pass in any parameters as needed:

; html-script: false ]
tester.call("this?", "is", "even", "cooler"); 
//=> {this: "this?", a: "is", b: "even", c: "cooler"}
Copy after login

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, .apply is also called by setting the context variable this to the value of the first parameter in the input parameter sequence. The second and last parameter of the input parameter sequence is passed in as an array (or array-like object).

Syntax:

; html-script: false ]
fun.apply(thisArg, [argsArray])
Copy after login

Thus, the following three lines are all equivalent:

; html-script: false ]
tester("this", "is", "cool"); 
tester.call(window, "this", "is", "cool"); 
tester.apply(window, ["this", "is", "cool"]);
Copy after login

Being able to specify a parameter list as an array is very useful most of the time (we will find out the benefits of doing so).

For example, Math.max is a variadic function (a function can accept any number of parameters).

; html-script: false ]
Math.max(1,3,2);
//=> 3
 
Math.max(2,1);
//=> 2
Copy after login

So, if I have an array of numbers and I need to use the Math.max function to find the largest one, how can I do this with one line of code?

; html-script: false ]
var numbers = [3, 8, 7, 3, 1];
Math.max.apply(null, numbers);
//=> 8
Copy after login

The .apply method really starts to show it’s importance when coupled with the special arguments variable: The arguments object

.

Every function expression has a special local variable available in its scope: arguments. In order to study its properties, let us create another tester function:

; html-script: false ]
var tester = function(a, b, c) {
    console.log(Object.getOwnPropertyNames(arguments));
};
Copy after login

Note: In this case we have to use Object.getOwnPropertyNames like above, because arguments has some properties that are not marked as enumerable, so if Just use console.log(arguments) This way they will not be displayed.

Now we follow the old method and test by calling the tester function:

; html-script: false ]
tester("a", "b", "c");
//=> ["0", "1", "2", "length", "callee"]
 
tester.apply(null, ["a"]);
//=> ["0", "length", "callee"]
Copy after login

The attributes of the arguments variable include attributes corresponding to each parameter passed in to the function. These are no different from the .length attribute and .callee attribute. The

.callee attribute provides a reference to the function that called the current function, but this is not supported by all browsers. For now, we ignore this property.
Let’s redefine our tester function to make it a little 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 }
Copy after login

Arguments: Is it an object or an array?

We can see that arguments is not an array at all, although it is more or less similar. In many cases, we'll want to treat it as an array even though it's not. There is a very nice shortcut function to convert arguments into an array:

; 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"]
Copy after login

Here we use the Array.prototype.slice method to convert an array-like object into an array. Because of this, arguments objects end up being extremely useful when used in conjunction with .apply.

Some useful examples

Log Wrapper

builds the logWrapper function, but it only works correctly with unary functions.

; html-script: false ]
// old version
var logWrapper = function (f) {
    return function (a) {
        console.log('calling "' + f.name + '" with argument "' + a);
        return f(a);
    };
};
Copy after login

Of course, our existing knowledge allows us to build a logWrapper function that can serve any function:

; html-script: false ]
// new version
var logWrapper = function (f) {
    return function () {
        console.log('calling "' + f.name + '"', arguments);
        return f.apply(this, arguments);
    };
};
Copy after login

By calling

; html-script: false ]
f.apply(this, arguments);
Copy after login
我们确定这个函数f会在和它之前完全相同的上下文中被调用。于是,如果我们愿意用新的”wrapped”版本替换掉我们的代码中的那些日志记录函数是完全理所当然没有唐突感的。
把原生的prototype方法放到公共函数库中
浏览器有大量超有用的方法我们可以“借用”到我们的代码里。方法常常把this变量作为“data”来处理。在函数式编程,我们没有this变量,但是我们无论如何要使用函数的!
Copy after login
; html-script: false ]
var demethodize = function(fn){
    return function(){
        var args = [].slice.call(arguments, 1);
        return fn.apply(arguments[0], args);
    };
};
Copy after login

Some other examples:

; html-script: false ]
// String.prototype
var split = demethodize(String.prototype.split);
var slice = demethodize(String.prototype.slice);
var indexOfStr = demethodize(String.prototype.indexOf);
var toLowerCase = demethodize(String.prototype.toLowerCase);
 
// Array.prototype
var join = demethodize(Array.prototype.join);
var forEach = demethodize(Array.prototype.forEach);
var map = demethodize(Array.prototype.map);
Copy after login

Of course, many, many more. 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"
Copy after login

Digression:

We will demonstrate later that actually a better way to use the demethodize function is parameter flipping.

在函数式编程情况下,你通常需要把“data”或“input data”参数作为函数的最右边的参数。方法通常会把this变量绑定到“data”参数上。举个例子,String.prototype方法通常操作的是实际的字符串(即”data”)。Array方法也是这样。

为什么这样可能不会马上被理解,但是一旦你使用柯里化或是组合函数来表达更丰富的逻辑的时候情况会这样。这正是我在引言部分说到UnderScore.js所存在的问题,之后在以后的文章中还会详细介绍。几乎每个Underscore.js的函数都会有“data”参数,并且作为最左参数。这最终导致非常难重用,代码也很难阅读或者是分析。:-(

管理参数顺序

; html-script: false ]
// shift the parameters of a function by one
var ignoreFirstArg = function (f) {
    return function(){
        var args = [].slice.call(arguments,1);
        return f.apply(this, args);
    };
};
 
// reverse the order that a function accepts arguments
var reverseArgs = function (f) {
    return function(){
        return f.apply(this, toArray(arguments).reverse());
    };
};
Copy after login

组合函数

在函数式编程世界里组合函数到一起是极其重要的。通常的想法是创建小的、可测试的函数来表现一个“单元逻辑”,这些可以组装到一个更大的可以做更复杂工作的“结构”

; 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];
    };
};
Copy after login

例子:

; html-script: false ]
// abs(x) = Sqrt(x^2)
var abs = compose(sqrt, square);
 
abs(-2); // 2
Copy after login


Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template