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

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

Dec 14, 2016 am 09:21 AM

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


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

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

What should I do if I encounter garbled code printing for front-end thermal paper receipts? What should I do if I encounter garbled code printing for front-end thermal paper receipts? Apr 04, 2025 pm 02:42 PM

Frequently Asked Questions and Solutions for Front-end Thermal Paper Ticket Printing In Front-end Development, Ticket Printing is a common requirement. However, many developers are implementing...

Demystifying JavaScript: What It Does and Why It Matters Demystifying JavaScript: What It Does and Why It Matters Apr 09, 2025 am 12:07 AM

JavaScript is the cornerstone of modern web development, and its main functions include event-driven programming, dynamic content generation and asynchronous programming. 1) Event-driven programming allows web pages to change dynamically according to user operations. 2) Dynamic content generation allows page content to be adjusted according to conditions. 3) Asynchronous programming ensures that the user interface is not blocked. JavaScript is widely used in web interaction, single-page application and server-side development, greatly improving the flexibility of user experience and cross-platform development.

Who gets paid more Python or JavaScript? Who gets paid more Python or JavaScript? Apr 04, 2025 am 12:09 AM

There is no absolute salary for Python and JavaScript developers, depending on skills and industry needs. 1. Python may be paid more in data science and machine learning. 2. JavaScript has great demand in front-end and full-stack development, and its salary is also considerable. 3. Influencing factors include experience, geographical location, company size and specific skills.

Is JavaScript hard to learn? Is JavaScript hard to learn? Apr 03, 2025 am 12:20 AM

Learning JavaScript is not difficult, but it is challenging. 1) Understand basic concepts such as variables, data types, functions, etc. 2) Master asynchronous programming and implement it through event loops. 3) Use DOM operations and Promise to handle asynchronous requests. 4) Avoid common mistakes and use debugging techniques. 5) Optimize performance and follow best practices.

The Evolution of JavaScript: Current Trends and Future Prospects The Evolution of JavaScript: Current Trends and Future Prospects Apr 10, 2025 am 09:33 AM

The latest trends in JavaScript include the rise of TypeScript, the popularity of modern frameworks and libraries, and the application of WebAssembly. Future prospects cover more powerful type systems, the development of server-side JavaScript, the expansion of artificial intelligence and machine learning, and the potential of IoT and edge computing.

How to merge array elements with the same ID into one object using JavaScript? How to merge array elements with the same ID into one object using JavaScript? Apr 04, 2025 pm 05:09 PM

How to merge array elements with the same ID into one object in JavaScript? When processing data, we often encounter the need to have the same ID...

How to achieve parallax scrolling and element animation effects, like Shiseido's official website?
or:
How can we achieve the animation effect accompanied by page scrolling like Shiseido's official website? How to achieve parallax scrolling and element animation effects, like Shiseido's official website? or: How can we achieve the animation effect accompanied by page scrolling like Shiseido's official website? Apr 04, 2025 pm 05:36 PM

Discussion on the realization of parallax scrolling and element animation effects in this article will explore how to achieve similar to Shiseido official website (https://www.shiseido.co.jp/sb/wonderland/)...

The difference in console.log output result: Why are the two calls different? The difference in console.log output result: Why are the two calls different? Apr 04, 2025 pm 05:12 PM

In-depth discussion of the root causes of the difference in console.log output. This article will analyze the differences in the output results of console.log function in a piece of code and explain the reasons behind it. �...

See all articles