Functional JavaScript: .apply(), .call(), and arguments objects
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"]
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 }); };
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"}
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[, ...]]])
So, the following two lines are equivalent:
; html-script: false ] tester("this", "is", "cool"); tester.call(window, "this", "is", "cool");
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"}
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])
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"]);
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
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
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)); };
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"]
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 }
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"]
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); }; };
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); }; };
By calling
; html-script: false ] f.apply(this, arguments);
我们确定这个函数f会在和它之前完全相同的上下文中被调用。于是,如果我们愿意用新的”wrapped”版本替换掉我们的代码中的那些日志记录函数是完全理所当然没有唐突感的。 把原生的prototype方法放到公共函数库中 浏览器有大量超有用的方法我们可以“借用”到我们的代码里。方法常常把this变量作为“data”来处理。在函数式编程,我们没有this变量,但是我们无论如何要使用函数的!
; 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.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);
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"
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()); }; };
组合函数
在函数式编程世界里组合函数到一起是极其重要的。通常的想法是创建小的、可测试的函数来表现一个“单元逻辑”,这些可以组装到一个更大的可以做更复杂工作的“结构”
; 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]; }; };
例子:
; html-script: false ] // abs(x) = Sqrt(x^2) var abs = compose(sqrt, square); abs(-2); // 2

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

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

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

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...

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.

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.

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 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 in JavaScript? When processing data, we often encounter the need to have the same ID...

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/)...

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. �...
