Javascript:使用函数上下文与作为参数传递的好处是什么?

Javascript: What is the benefit of using function context vs passing as parameter

本文关键字:参数传递 是什么 函数 上下文 Javascript      更新时间:2023-09-26

除了欺骗已经实现this的现有函数之外,为什么你要写一个javascript函数,这样你就需要改变它的上下文(通过.call.apply),而不是显式地传递"context"作为另一个参数?是否有性能上的好处?

的例子:

function tryIncrement(inc, context) {
    context = context || this; // just so we can reuse same fn for the example
    if( typeof context.val!= typeof 1|| typeof inc != typeof 1 ) return false;
    context.val += inc;
    return true;
}
var a = {name: 'A', val: 5}, b = {name: 'B', val: 20};
// reassign internal context
for(var i = 0, n = [1,3,"not a num",5]; i < n.length; i++) {
    if( tryIncrement.call(a, n[i]) ) console.log('incremented', i, n[i], a);
    else console.log('failed to increment', i, n[i], a);
}
// provide explicit context;
// could just as easily declared function so context was first param
// so it looked the same as previous implementation
for(var i = 0, n = [1,3,"not a num",5]; i < n.length; i++) {
    if( tryIncrement(n[i], b) ) console.log('incremented', i, n[i], b);
    else console.log('failed to increment', i, n[i], b);
}

在许多情况下,您可能希望使用this而不是传递额外的参数。考虑下面的函数:

Function.prototype.async = function () {
    setTimeout.bind(null, this, 0).apply(null, arguments);
};

这个函数允许我们按照如下方式延迟函数调用:

alert.async("This will display later.");
alert("This will display first.");

您可以在这里看到演示:http://jsfiddle.net/AjwQu/

不将函数绑定到this,我们可以将其作为形参传递:

function async(funct) {
    setTimeout.bind(null, funct, 0).apply(null, [].slice.call(arguments, 1));
}

我们现在会这样使用它:

async(alert, "This will display later.");
alert("This will display first.");

结果相同:http://jsfiddle.net/63dBF/

然而,要获得参数,我们必须使用[].slice.call(arguments, 1)代替。在第一个例子中,我们可以简单地使用arguments,因为该函数不是实参列表的一部分。

任何事物都有它的优点和缺点。你只需要知道什么时候用什么。希望这对你有帮助。

附加:将使用this的函数转换为接受额外参数的函数非常容易,反之亦然。首先让我们定义几个实用函数:

var functProto = Function.prototype;
var bind = functProto.bind;
var bindable = bind.bind(bind);
var callable = bindable(functProto.call);
var appliable = bindable(functProto.apply);

bindable函数允许您创建现有函数的可绑定版本,当调用该函数时,该函数返回绑定到给定参数的新函数。

callable函数允许您创建现有函数的可调用版本,当调用该函数时,使用给定的参数和this指针调用现有函数。

appliable函数允许您创建现有函数的可应用版本,该版本在调用时将给定的参数和this指针应用于现有函数。

给出第一个例子中的函数,我们可以在第二个例子中创建函数,如下所示:

var async = callable(functProto.async);

请看这里的演示:http://jsfiddle.net/3dSBS/

同样,我们可以将第二个示例中的函数转换为第一个示例中的函数,如下所示:
Function.prototype.async = function () {
    return async.apply(null, [this].concat([].slice.call(arguments)));
};

请看这里的演示:http://jsfiddle.net/rJQyS/

可以看到,使用this编写函数,然后构造接受上下文作为参数的函数,要比使用其他方法容易得多。

据我所知,this的使用并没有什么不同另一个参数,它的存在方式更复杂修改。

我认为回答你的问题最简单的方法是想象一下,如果基础Javascript语言的创建者遵循了你的约定。

没有this的世界

没有this的世界是一个可怕的嘈杂的地方,有很多过度的重复:

var arr = [1,2,3,4];
arr.reverse(arr); //4321

有更多的机会产生误导或冗长的语法

var str = "stringtobesplit";
"abiglongstringnotbeingsplit".split(str,":");
String.prototype.split(str,":");

它并没有完全摆脱apply至少:

Math.max.apply(arr);  //didn't add the initial `this` since it doesn't exist

实际上,可以选择只创建全局函数,或者在原型或对象上创建函数,这些原型或对象对接收的参数类型进行了假设,但不强制执行这些假设。例如,想象一下我们幻想世界中的toString方法。

你可以创建一个全局toString方法,它可以接受所有类型的对象,并尝试使它们都工作,或者你可以在每个类型的原型上有一个函数,就像它目前工作一样,不强制在该类型上调用它。有人可以调用

Array.prototype.toString(str)

并且我们需要优雅地处理它(因为它的价值在于apply似乎恢复到Object.prototype.toString并返回[Object String])。因此,我们需要确定在这些情况下调用的正确原型方法,这意味着我的猜测是约定将调用

str.toString(str) 

之类的

那有什么意义呢?

this用于处理原型链上javascript方法的常见情况。它为我们提供了一种简写,允许对象对自身进行操作,而无需复制对它的调用,也不必确切知道它的原型是什么。如果没有它,我们要么没有对象上的函数,要么每次都必须显式地调用函数本身,从而引入额外的语法和潜在的错误。

callapply是例外情况,即使this消失,apply至少也会有用处。把api写在异常情况下从来都不是一个好主意。如果您正在创建面向对象的代码,您应该使用this作为一种简单的方法来引用作为调用上下文的对象。如果你写得很好,那么call和apply应该在特殊情况下很少使用。

TL;DR - this被设计为Javascript的一部分是有原因的,当你在对象上创建方法时使用它,以获得更清晰易懂的语法。

当你做面向对象编程时,你的函数将依赖于上下文,把它作为参数提供是没有意义的,因为这会破坏面向对象编程的目的。

为回调提供隐式上下文也是有意义的。如果只需要上下文,则不必记住参数的正确顺序。在这种情况下,您根本不需要使用参数。所以不用

function mayCallback(param1, param2, context)

你可以直接写

function myCallback()

,如果不需要param1和param2,则使用this

为了解决我的主要目的——使用this是否比函数参数有性能优势?,答案似乎是没有:

http://jsperf.com/function-context-vs-parameter

虽然在对象中使用参数值而不是实例(this)变量似乎有一点好处(但是可能不是很明显)。

(请自己测试,如果有不同请评论)

关于其他答案的目的:@Aadit指出了一些整洁的用例,可维护性是有争议的个人偏好,但就像@ben336所说的,如果你正在使用对象(因此是OOP),那么this可能更有用。

ECMAScript第5版的本机函数bind可能是两个世界之间的一个有趣的桥梁,或者至少是一个耗时的探索。

上面引用的实例与参数值测试也可能是我的观点的一个很好的例子——如果你正在构建一个静态功能库,你可以"劫持"obj.callback2通过作用域到不同的this,或者只是直接调用obj.callback在你的备用上下文中。

相关文章: