【问题标题】:On Execution context of anonymous functions and call/apply/bind关于匿名函数的执行上下文和调用/应用/绑定
【发布时间】:2014-01-14 21:11:49
【问题描述】:

我正在尝试深入了解并理解而不是重复代码。我知道bind, call and apply 通过修改this 指向的内容来更改执行上下文。我不明白的是,这些方法是必不可少的和/或导致代码更短的领域。考虑以下几点:

var person = {
            firstName: 'john',
            lastName: 'doe',
            fullName: function () {
                console.log(this.firstName + " " + this.lastName);
            }
        }
//#1, binder is an html button
$('#binder').click(person.fullName.bind(person)); //john doe

//#2, anonymous is an html button
$('#anonymous').click(function () {
    person.fullName(); //john doe
});

//seems strange I see this application a lot 'borrowing' a method
//why?
var o = { firstName: 'obj', lastName: 'ect' };
person.fullName.call(o); //obj ect to console

我想知道一些关于何时使用 call 和 apply 是一种好的做法和/或节省大量时间的一般范例(并在不使用匿名函数 a la 函数 #1 之外进行绑定)

【问题讨论】:

  • apply 的另一个有用的方面是它允许您传递参数数组。在函数接受可变数量参数的情况下特别有用,例如jquery's when
  • 这对代码的复用性来说基本是好的。

标签: javascript


【解决方案1】:

要专注于这些功能中的每一个必不可少的地方,我想说

  • apply 在处理可变参数函数时最有用。它允许您将值数组转换为参数列表。

    function logwrapper(f){
      return function(){
        console.log("called");
        return f.apply(this, arguments);
      }
    }
    
    var bar = function(){ ... }
    var foo = logwrapper(bar);
    
  • bind 在您想将方法传递给一些只需要一个函数的代码时最有用。一个常见的例子是 settimeout 和其他需要回调的函数:

    setTimeout(function(){ obj.meth(); }, 100); //handwritten callback
    
    setTimeout(obj.meth.bind(obj), 100);        //Function.prototype.bind
    
    setTimeout(bind(obj, "meth"), 100);         //Some JS libraries have functions that
                                                //let you write the object only once.
    

    请记住,IE

  • call 主要用于借用方法,正如您已经注意到的。这是否有用将很大程度上取决于您的特定用例,但一个非常常见的重要用途是在arguments 上使用数组方法。由于历史原因,arguments 没有任何常用的数组方法(切片、映射等),因此您需要借用它们:

    function myvariadic(x){
       var rest = [].slice.call(x, 1);
    }
    

    您可能会看到的另一个示例是 hasOwnProerty 方法:

    for(k in obj){
       if(Object.prototype.hasOwnProperty.call(obj, k)){
          ....
       }
    }
    

    这使您可以调用真正的 hasOwnProperty 方法,即使对象使用其自己的 hasOwnProperty 键对其进行隐藏。

【讨论】:

    【解决方案2】:

    坦率地说,我不会对好的做法或坏的做法一文不值。
    如果您的目标是完全了解任何事情,那么您最好忘记它们。

    话虽如此,要了解何时适合使用call/applybind,您必须了解闭包,以及this的具体关闭

    我在这里使用了一个广义的闭包定义。不是您通常会在 JavaScript 技术聊天中遇到的那种,我们通常会在其中讨论 lexical 闭包

    为了这篇小文章的目的,假设闭包可以为任何变量(包括this)提供值,这些变量位于您关注的函数的当前范围之外。

    bindcallapply 基本上是为this 提供值(以及一些其他参数作为选项)。

    它只对两件事有用:

    • 将给定类的函数或方法调用到不同类(或根本没有类)的对象。

    稍后会详细介绍

    • this 提供一个值,以防当前关闭不满足您的需求。

    例如,传递对方法的引用会丢失与底层对象实例的连接。或者使用类原型函数。或者在事件处理程序的上下文中调用,其中 JS 已将 this 设置为捕获事件的 DOM 元素。

    致电申请

    call 将简单地将 this 设置为它的第一个参数的值,以便执行该函数。

    由于在 JS 中创建对象非常简单,您可能需要执行以下操作:

    Worker = function ()
    {
        this.things_done = 0;
    }
    
    Worker.prototype = {
        do_something: function (count)
        {
            this.things_done += count;
        }
    }
    
    var worker= new Worker();                  // Worker object
    var wanabee_worker = { things_done: 100 }; // classless object
    

    等等!您刚刚创建了与 Worker 没有类关系但仍然可以使用 Worker 方法的东西,因为它定义了所有必需的属性。

    worker.do_something.call (wanabee_worker, 10);
    

    允许wanabee_worker借用不相关的对象Worker的方法。

    反之亦然:

    function reset_work_count ()
    {
        this.things_done = 0;
    }
    
    reset_work_count.call (worker);
    

    这里我们有一个普通函数,它与Worker 没有任何关系,除了它使用相同的属性。 call 允许将其应用于 Worker 对象。

    applythis 完全一样。唯一的区别是其他参数的传递方式。

    绑定

    bind 将创建一个内部闭包并返回一个新的包装函数,该函数将使用传递给bind 的参数作为this 的值。

    典型示例:将事件处理程序绑定到特定对象。

    $('#binder').click(person.fullName.bind(person));
    

    在 JQuery goo 之下,代码最终的作用是

    binder.addEventListener ('click', person.fullName.bind(person), false);
    

    如果处理程序被简单地定义为person.fullName,则调用它时会将this 设置为捕获事件的DOM 元素。

    在这种特殊情况下,JS 引擎提供的this 的闭包不适合我们的需求,因此我们提供了一个使用bind 的替代方案。

    您可以使用 person.fullName.bind(person),而不是:

    function() { person.FullName(); }
    

    除了

    • lambda 函数很麻烦,而且会混淆代码,
    • bind 是一个内部构造,可以更有效地执行闭包。

    您还可以想象用于处理事件的对象将由某些代理函数动态分配/计算。在这种情况下,使用bind 将毫无用处,因为我们想要的是访问允许我们将 lambda 对象用作工作类之一的方法。

    function event_handler (param)
    {
        var obj = compute_lambda_obj ();
        Worker.prototype.do_something.call (ojb, param);
    }
    

    【讨论】:

    • 挑一点,this 与闭包没有任何关系。闭包都是关于词法范围的变量,而this,完全动态范围是相反的......
    • 如果你将闭包定义为解析外部符号的机制,this 就是一个完美的例子,说明只能来自闭包的东西。无论是否动态,它总是在 JavaScript 函数的范围之外。如果您愿意,可以将其称为“当前值”,只要它能让人们意识到它没有什么神奇之处:)。
    • this 并不是真正在函数调用之间存储的“当前值”。与内部函数可能已关闭的外部作用域中的变量不同,this 的行为更像是每次调用函数时都需要传递的额外函数参数。
    • ... 或者您可以将其视为由 JS 引擎修改的全局变量,因此您最好在下一个事件处理程序破坏它之前获取一个副本。不要误会我的意思:我发现 JavaScript 是一门非常聪明的语言,而且大多数时候因为它的简单性和适应性而令人愉悦。我根本不喜欢与之相伴的所有宗教。说了这么多,我完全同意我使用了我自己的闭包定义,这可能会产生误导,我修改了我的帖子以警告普通读者。
    • @missingno:从语义的角度来看,您可以将其称为闭包——它是一个与执行代码时可用的实例特定数据捆绑在一起的函数。但是,您说得对,这不是 lexical 闭包,因为 this 值不是来自(外部)词法范围的变量。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-24
    • 2011-06-21
    • 1970-01-01
    • 1970-01-01
    • 2012-10-12
    相关资源
    最近更新 更多