搜索
首页web前端js教程函数式JavaScript:.apply()、.call() 和arguments对象

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

函数原型

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

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

这里的输出依赖于你使用的浏览器和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
    });
};

这个函数简单记录了输入参数的值,和“上下文变量”,即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"}

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

使用Function.prototype.call

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

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

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

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

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

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

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

使用Function.prototype.apply

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

语法:

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

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

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

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

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

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

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

; 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

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

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

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

注:在这种情况下我们必须像上面这样使用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"]

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 }

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"]

这里我们利用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);
    };
};

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

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

通过调用

; 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);
    };
};

一些别的例子:

; 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);

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

; 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"

题外话:

后面我们会演示,实际上更好的使用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());
    };
};

组合函数

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

; 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


声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
JavaScript在行动中:现实世界中的示例和项目JavaScript在行动中:现实世界中的示例和项目Apr 19, 2025 am 12:13 AM

JavaScript在现实世界中的应用包括前端和后端开发。1)通过构建TODO列表应用展示前端应用,涉及DOM操作和事件处理。2)通过Node.js和Express构建RESTfulAPI展示后端应用。

JavaScript和Web:核心功能和用例JavaScript和Web:核心功能和用例Apr 18, 2025 am 12:19 AM

JavaScript在Web开发中的主要用途包括客户端交互、表单验证和异步通信。1)通过DOM操作实现动态内容更新和用户交互;2)在用户提交数据前进行客户端验证,提高用户体验;3)通过AJAX技术实现与服务器的无刷新通信。

了解JavaScript引擎:实施详细信息了解JavaScript引擎:实施详细信息Apr 17, 2025 am 12:05 AM

理解JavaScript引擎内部工作原理对开发者重要,因为它能帮助编写更高效的代码并理解性能瓶颈和优化策略。1)引擎的工作流程包括解析、编译和执行三个阶段;2)执行过程中,引擎会进行动态优化,如内联缓存和隐藏类;3)最佳实践包括避免全局变量、优化循环、使用const和let,以及避免过度使用闭包。

Python vs. JavaScript:学习曲线和易用性Python vs. JavaScript:学习曲线和易用性Apr 16, 2025 am 12:12 AM

Python更适合初学者,学习曲线平缓,语法简洁;JavaScript适合前端开发,学习曲线较陡,语法灵活。1.Python语法直观,适用于数据科学和后端开发。2.JavaScript灵活,广泛用于前端和服务器端编程。

Python vs. JavaScript:社区,图书馆和资源Python vs. JavaScript:社区,图书馆和资源Apr 15, 2025 am 12:16 AM

Python和JavaScript在社区、库和资源方面的对比各有优劣。1)Python社区友好,适合初学者,但前端开发资源不如JavaScript丰富。2)Python在数据科学和机器学习库方面强大,JavaScript则在前端开发库和框架上更胜一筹。3)两者的学习资源都丰富,但Python适合从官方文档开始,JavaScript则以MDNWebDocs为佳。选择应基于项目需求和个人兴趣。

从C/C到JavaScript:所有工作方式从C/C到JavaScript:所有工作方式Apr 14, 2025 am 12:05 AM

从C/C 转向JavaScript需要适应动态类型、垃圾回收和异步编程等特点。1)C/C 是静态类型语言,需手动管理内存,而JavaScript是动态类型,垃圾回收自动处理。2)C/C 需编译成机器码,JavaScript则为解释型语言。3)JavaScript引入闭包、原型链和Promise等概念,增强了灵活性和异步编程能力。

JavaScript引擎:比较实施JavaScript引擎:比较实施Apr 13, 2025 am 12:05 AM

不同JavaScript引擎在解析和执行JavaScript代码时,效果会有所不同,因为每个引擎的实现原理和优化策略各有差异。1.词法分析:将源码转换为词法单元。2.语法分析:生成抽象语法树。3.优化和编译:通过JIT编译器生成机器码。4.执行:运行机器码。V8引擎通过即时编译和隐藏类优化,SpiderMonkey使用类型推断系统,导致在相同代码上的性能表现不同。

超越浏览器:现实世界中的JavaScript超越浏览器:现实世界中的JavaScriptApr 12, 2025 am 12:06 AM

JavaScript在现实世界中的应用包括服务器端编程、移动应用开发和物联网控制:1.通过Node.js实现服务器端编程,适用于高并发请求处理。2.通过ReactNative进行移动应用开发,支持跨平台部署。3.通过Johnny-Five库用于物联网设备控制,适用于硬件交互。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SecLists

SecLists

SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )专业的PHP集成开发工具

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

功能强大的PHP集成开发环境