Maison interface Web js tutoriel 函数式JavaScript:.apply()、.call() 和arguments对象

函数式JavaScript:.apply()、.call() 和arguments对象

Dec 14, 2016 am 09:21 AM

我们正在寻求调校JavaScript的方式,使得我们可以做些真正的函数式编程。为了做到这一点,详细理解函数调用和函数原型是非常有必要的。

函数原型

现在,不管你是已经读了还是忽略掉上面的链接所对应的文章,我们准备继续前进!
如果我们点开了我们喜欢的浏览器+JavaScript控制台,让我们看一下Function.prototype对象的属性:

; html-script: false ]
Object.getOwnPropertyNames(Function.prototype)
//=> ["length", "name", "arguments", "caller", 
//    "constructor", "bind", "toString", "call", "apply"]
Copier après la connexion

这里的输出依赖于你使用的浏览器和JavaScript版本。(我用的是Chrome 33)
我们看到一些我们感兴趣的几个属性。鉴于这篇文章的目的,我会讨论下这几个:

Function.prototype.length

Function.prototype.call

Function.prototype.apply

第一个是个属性,另外两个是方法。除了这三个,我还会愿意讨论下这个特殊的变量arguments,它和Function.prototype.arguments(已被弃用)稍有不同。

首先,我将定义一个“tester”函数来帮助我们弄清楚发生了什么。

; html-script: false ]
var tester = function (a, b, c){
    console.log({
        this: this,
        a: a,
        b: b,
        c: c
    });
};
Copier après la connexion

这个函数简单记录了输入参数的值,和“上下文变量”,即this的值。
现在,让我们尝试一些事情:

; 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"}
Copier après la connexion

我们注意到如果我们不输入第2、3个参数,程序将会显示它们为undefined(未定义)。此外,我们注意到这个函数默认的“上下文”是全局对象window。

使用Function.prototype.call

一个函数的 .call 方法以这样的方式调用这个函数,它把上下文变量this设置为第一个输入参数的值,然后其他的的参数一个跟一个的也传进函数。
语法:

; html-script: false ]
fn.call(thisArg[, arg1[, arg2[, ...]]])
Copier après la connexion

因此,下面这两行是等效的:

; html-script: false ]
tester("this", "is", "cool"); 
tester.call(window, "this", "is", "cool");
Copier après la connexion

当然,我们能够随需传入任何参数:

; html-script: false ]
tester.call("this?", "is", "even", "cooler"); 
//=> {this: "this?", a: "is", b: "even", c: "cooler"}
Copier après la connexion

这个方法主要的功能是设置你所调用函数的this变量的值。

使用Function.prototype.apply

函数的.apply方法比.call更实用一些。和.call类似,.apply的调用方式也是把上下文变量this设置为输入参数序列中的第一个参数的值。输入参数序列的第二个参数也是最后一个,以数组(或者类数组对象)的方式传入。

语法:

; html-script: false ]
fun.apply(thisArg, [argsArray])
Copier après la connexion

因此,下面三行全部等效:

; html-script: false ]
tester("this", "is", "cool"); 
tester.call(window, "this", "is", "cool"); 
tester.apply(window, ["this", "is", "cool"]);
Copier après la connexion

能够以数组的方式指定一个参数列表在多数时候非常有用(我们会发现这样做的好处的)。

例如,Math.max是一个可变参数函数(一个函数可以接受任意数目的参数)。

; html-script: false ]
Math.max(1,3,2);
//=> 3
 
Math.max(2,1);
//=> 2
Copier après la connexion

这样,如果我有一个数值数组,并且我需要利用Math.max函数找出其中最大的那个,我怎么用一行代码来做这个事儿呢?

; html-script: false ]
var numbers = [3, 8, 7, 3, 1];
Math.max.apply(null, numbers);
//=> 8
Copier après la connexion

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

.apply方法真正开始显示出它的重要是当配上特殊参数:Arguments对象。

每个函数表达式在它的作用域中都有一个特殊的、可使用的局部变量:arguments。为了研究它的属性,让我们创建另一个tester函数:

; html-script: false ]
var tester = function(a, b, c) {
    console.log(Object.getOwnPropertyNames(arguments));
};
Copier après la connexion

注:在这种情况下我们必须像上面这样使用Object.getOwnPropertyNames,因为arguments有一些属性没有标记为可以被枚举的,于是如果仅仅使用console.log(arguments)这种方式它们将不会被显示出来。

现在我们按照老办法,通过调用tester函数来测试下:

; html-script: false ]
tester("a", "b", "c");
//=> ["0", "1", "2", "length", "callee"]
 
tester.apply(null, ["a"]);
//=> ["0", "length", "callee"]
Copier après la connexion

arguments变量的属性中包括了对应于传入函数的每个参数的属性,这些和.length属性、.callee属性没什么不同。

.callee属性提供了调用当前函数的函数的引用,但是这并不被所有的浏览器支持。就目前而言,我们忽略这个属性。
让我们重新定义一下我们的tester函数,让它丰富一点:

; 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 }
Copier après la connexion

Arguments:是对象还是数组?

我们看得出,arguments完全不是一个数组,虽然多多少少有点像。在很多情况下,尽管不是,我们还是希望把它当作数组来处理。把arguments转换成一个数组,这有个非常不错的快捷小函数:

; 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"]
Copier après la connexion

这里我们利用Array.prototype.slice方法把类数组对象转换成数组。因为这个,在与.apply同时使用的时候arguments对象最终会极其有用。

一些有用例子

Log Wrapper(日志包装器)

构建了logWrapper函数,但是它只是在一元函数下正确工作。

; html-script: false ]
// old version
var logWrapper = function (f) {
    return function (a) {
        console.log('calling "' + f.name + '" with argument "' + a);
        return f(a);
    };
};
Copier après la connexion

当然了,我们既有的知识让我们能够构建一个可以服务于任何函数的logWrapper函数:

; html-script: false ]
// new version
var logWrapper = function (f) {
    return function () {
        console.log('calling "' + f.name + '"', arguments);
        return f.apply(this, arguments);
    };
};
Copier après la connexion

通过调用

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

一些别的例子:

; 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);
Copier après la connexion

当然,许多许多。来看看这些是怎么执行的:

; 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"
Copier après la connexion

题外话:

后面我们会演示,实际上更好的使用demethodize函数的方式是参数翻转。

在函数式编程情况下,你通常需要把“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());
    };
};
Copier après la connexion

组合函数

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

; 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];
    };
};
Copier après la connexion

例子:

; html-script: false ]
// abs(x) = Sqrt(x^2)
var abs = compose(sqrt, square);
 
abs(-2); // 2
Copier après la connexion


Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn

Outils d'IA chauds

Undresser.AI Undress

Undresser.AI Undress

Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover

AI Clothes Remover

Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool

Undress AI Tool

Images de déshabillage gratuites

Clothoff.io

Clothoff.io

Dissolvant de vêtements AI

AI Hentai Generator

AI Hentai Generator

Générez AI Hentai gratuitement.

Article chaud

R.E.P.O. Crystals d'énergie expliqués et ce qu'ils font (cristal jaune)
3 Il y a quelques semaines By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Meilleurs paramètres graphiques
3 Il y a quelques semaines By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Comment réparer l'audio si vous n'entendez personne
3 Il y a quelques semaines By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25: Comment déverrouiller tout dans Myrise
4 Il y a quelques semaines By 尊渡假赌尊渡假赌尊渡假赌

Outils chauds

Bloc-notes++7.3.1

Bloc-notes++7.3.1

Éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise

SublimeText3 version chinoise

Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1

Envoyer Studio 13.0.1

Puissant environnement de développement intégré PHP

Dreamweaver CS6

Dreamweaver CS6

Outils de développement Web visuel

SublimeText3 version Mac

SublimeText3 version Mac

Logiciel d'édition de code au niveau de Dieu (SublimeText3)

Comment créer et publier mes propres bibliothèques JavaScript? Comment créer et publier mes propres bibliothèques JavaScript? Mar 18, 2025 pm 03:12 PM

L'article discute de la création, de la publication et du maintien des bibliothèques JavaScript, en se concentrant sur la planification, le développement, les tests, la documentation et les stratégies de promotion.

Comment optimiser le code JavaScript pour les performances dans le navigateur? Comment optimiser le code JavaScript pour les performances dans le navigateur? Mar 18, 2025 pm 03:14 PM

L'article traite des stratégies pour optimiser les performances JavaScript dans les navigateurs, en nous concentrant sur la réduction du temps d'exécution et la minimisation de l'impact sur la vitesse de chargement de la page.

Que dois-je faire si je rencontre l'impression de code brouillé pour les reçus en papier thermique frontal? Que dois-je faire si je rencontre l'impression de code brouillé pour les reçus en papier thermique frontal? Apr 04, 2025 pm 02:42 PM

Des questions et des solutions fréquemment posées pour l'impression de billets thermiques frontaux pour le développement frontal, l'impression de billets est une exigence commune. Cependant, de nombreux développeurs mettent en œuvre ...

Comment déboguer efficacement le code JavaScript à l'aide d'outils de développeur de navigateur? Comment déboguer efficacement le code JavaScript à l'aide d'outils de développeur de navigateur? Mar 18, 2025 pm 03:16 PM

L'article traite du débogage efficace de JavaScript à l'aide d'outils de développeur de navigateur, de se concentrer sur la définition des points d'arrêt, de l'utilisation de la console et d'analyser les performances.

Comment utiliser les cartes source pour déboguer le code JavaScript minifié? Comment utiliser les cartes source pour déboguer le code JavaScript minifié? Mar 18, 2025 pm 03:17 PM

L'article explique comment utiliser les cartes source pour déboguer JavaScript minifiée en le mappant au code d'origine. Il discute de l'activation des cartes source, de la définition de points d'arrêt et de l'utilisation d'outils comme Chrome Devtools et WebPack.

Comment utiliser efficacement le cadre de collections de Java? Comment utiliser efficacement le cadre de collections de Java? Mar 13, 2025 pm 12:28 PM

Cet article explore une utilisation efficace du cadre de collections de Java. Il met l'accent sur le choix des collections appropriées (liste, set, map, file d'attente) en fonction de la structure des données, des besoins en performances et de la sécurité des threads. Optimisation de l'utilisation de la collection grâce à

TypeScript pour les débutants, partie 2: Types de données de base TypeScript pour les débutants, partie 2: Types de données de base Mar 19, 2025 am 09:10 AM

Une fois que vous avez maîtrisé le didacticiel TypeScript de niveau d'entrée, vous devriez être en mesure d'écrire votre propre code dans un IDE qui prend en charge TypeScript et de le compiler en JavaScript. Ce tutoriel plongera dans divers types de données dans TypeScript. JavaScript a sept types de données: null, non défini, booléen, numéro, chaîne, symbole (introduit par ES6) et objet. TypeScript définit plus de types sur cette base, et ce tutoriel les couvrira tous en détail. Type de données nuls Comme javascript, null en typeScript

Début avec Chart.js: tarte, beignet et graphiques à bulles Début avec Chart.js: tarte, beignet et graphiques à bulles Mar 15, 2025 am 09:19 AM

Ce tutoriel expliquera comment créer des graphiques à tarte, anneaux et bulles à l'aide de chart.js. Auparavant, nous avons appris quatre types de graphiques de graphique. Créer des graphiques à tarte et à anneaux Les graphiques à tarte et les graphiques d'anneaux sont idéaux pour montrer les proportions d'un tout divisé en différentes parties. Par exemple, un graphique à secteurs peut être utilisé pour montrer le pourcentage de lions mâles, de lions féminins et de jeunes lions dans un safari, ou le pourcentage de votes que différents candidats reçoivent lors des élections. Les graphiques à tarte ne conviennent que pour comparer des paramètres ou des ensembles de données uniques. Il convient de noter que le graphique à tarte ne peut pas dessiner des entités avec une valeur nulle car l'angle du ventilateur dans le graphique à tarte dépend de la taille numérique du point de données. Cela signifie toute entité avec une proportion nulle

See all articles