【问题标题】:Javascript: What is the benefit of using function context vs passing as parameterJavascript:使用函数上下文与作为参数传递有什么好处
【发布时间】:2013-03-20 02:03:49
【问题描述】:

除了欺骗已经实现 this 的现有函数之外,您为什么要编写一个 javascript 函数以便您需要更改其上下文(通过 .call.apply)而不是显式传递“上下文”作为另一个参数?有性能优势吗?

例子:

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

【问题讨论】:

    标签: javascript function optimization functional-programming


    【解决方案1】:

    在很多情况下,您可能希望使用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 编写函数然后构造函数接受上下文作为参数比其他方式要容易得多。

    【讨论】:

    • 您展示的主要是“语法糖”,即以一种方式编写与另一种方式相比看起来更酷。参见jsfiddle.net/drzaus/63dBF/2——我可以以同样的方式使用asynca(fn, [args]),不需要对参数进行切片,而且它“同样有效”(实际上,更好的是:console.log)。我的观点更倾向于是否存在某种性能优势,因为显式与隐式上下文的可维护性是有争议的(考虑为 jQuery 事件生成回调函数并跟踪this)。
    • @drzaus - 哎呀,我知道你的代码是好的,但你的函数asynca 没有按预期工作。问题是您在执行setTimeout 之前调用了fn.apply(null, args)。常见的错误。它应该是setTimeout(function () { return fn.apply(null, args); }, 0)。请参阅your codemine。我看不出您的代码如何在console.log 上更好地工作。话虽如此,使用this 而不是额外的参数并没有性能优势。这只是一个偏好问题。只剩下4个字符了。 =-)
    • 好点,fixed。回复:console.log——它实际上对你或我的都不起作用,我猜console.log 只是一个奇怪的函数——它抛出“未捕获的类型错误:非法调用”。正如我们一直说的那样,也许这只是偏好。
    • 为有用的讨论点赞。
    • @drzaus - 您使用的是哪个浏览器?我在 Ubuntu 12.10 上使用 Opera 12.14 amd64,它非常适合我。我不知道为什么它不适合你,但这绝对不是因为我的async 功能。请改用console.log.bind(console).async("Something to print.")
    【解决方案2】:

    据我所知,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 的一部分是有原因的,当您在对象上创建方法时使用它以获得更清晰易懂的语法。

    【讨论】:

    • 我的意思是当我们编写一个函数时,我们为什么要编写它来依赖于固有上下文而不是显式上下文。据我所知,this 的使用与另一个参数并没有什么不同,只是有一种更复杂的修改方式。
    • 好吧对不起,我重写了一个更完整的解释。
    • 我明白你关于 OOP 的观点,但如果我写了 JS,我可以像使用其他语言一样轻松地使用 String.reverse(str)str.reverse() ——哎呀,你已经使用 Math.max(1,2,3) 代替的1.max(2,3)。也许这就是函数式编程和 OOP 的区别。
    • @drzaus - 没有冒犯,但 1.max(2, 3) 看起来很迟钝 - 更不用说它会抛出 SyntaxError。您需要改用(1).max(2, 3)1..max(2, 3)。然而,这都是一个偏好问题。如果你想要一个Number.prototype.max 方法,那么你可以自己实现它。我个人觉得max 属于Math 命名空间,而不是Number。需要注意的是,Math 是一个命名空间,而不是一个构造函数。因此,使用Math.max 非常有意义——您不想污染全局范围。 Math 命名空间直接取自 Java - java.lang.Math
    • @drzaus 你说得对,两个反向实现都是有效的。面向对象的版本更简洁,并且具有以下优点:如果您有几种不同类型的反向方法,您可以调用 obj.reverse 而不必知道您使用的是哪种类型。这对于反向没有多大意义,但它可以用于其他情况(toString 是一个很好的例子。)最后,您可以以您认为可读和可维护的任何样式编写代码。但这些模式在可读性和灵活性方面确实具有真正的优势
    【解决方案3】:

    当您进行面向对象编程时,您的函数将取决于上下文,将其作为参数提供是没有意义的,因为这会破坏面向对象编程的目的。

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

    function mayCallback(param1, param2, context)

    你可以写

    function myCallback()

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

    【讨论】:

      【解决方案4】:

      为了解决我的主要目的——使用this 对函数参数有性能优势吗?——答案似乎是

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

      尽管there seems to be a slight benefit(但可能并不重要)在对象中使用参数值而不是实例 (this) 变量。

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

      关于其他答案解决的目的:有一些neat use cases as pointed out by @Aadit,可维护性可能是个人偏好,但like @ben336 said 如果您使用的是对象(因此是 OOP),那么this 可以更多有用。

      ECMAScript 5th-edition native function bind 可能是两个世界之间有趣的桥梁,或者至少是一个需要探索的时间线。

      上面引用的instance vs parameter values test 也可能是我观点的一个很好的例子——如果你正在构建一个静态功能库,你可以通过将范围限定为不同的this 来“劫持”obj.callback2,或者只是直接在您的备用上下文中调用 obj.callback

      【讨论】:

      • 您问这个问题的主要目的是关于性能?这有点傻。您有任何理由相信这是实际代码中的瓶颈吗?如果不是,那么还有一百万件比这更重要的事情。
      • 告诉我我的问题很愚蠢有点愚蠢,尤其是因为至少有 2 个人似乎认为这是一个不错的问题,而您花时间阅读到底部只是为了发表评论。我问是因为我想确保我没有遗漏一些模糊(或明显)的东西,比如 “但是如果你在 情况下使用它,那么你的性能 x1000” .
      猜你喜欢
      • 2022-12-17
      • 2018-06-27
      • 2019-12-05
      • 1970-01-01
      • 2014-02-22
      • 1970-01-01
      • 2011-09-15
      • 1970-01-01
      • 2011-08-16
      相关资源
      最近更新 更多