【问题标题】:Can't understand this Javascript function (function overloading)无法理解这个 Javascript 函数(函数重载)
【发布时间】:2015-06-22 20:52:04
【问题描述】:

我正在阅读 Javascript Ninja 的秘密,遇到了一个我无法完全理解的示例。之前这里的其他一些用户也引用了同样的例子,但他们的疑问与我的不同。示例如下:

function addMethod(object, name, fn) {

  var old = object[name];

  object[name] = function(){

       if (fn.length == arguments.length)

          return fn.apply(this, arguments)

       else if (typeof old == 'function')

          return old.apply(this, arguments);

    };
}

这是一个函数重载的例子,这样使用:

var ninja = {};
addMethod(ninja,'whatever',function(){ /* do something */ });
addMethod(ninja,'whatever',function(a){ /* do something else */ });
addMethod(ninja,'whatever',function(a,b){ /* yet something else */ });

我对范围、闭包和 apply() 的使用有深入的了解。我的疑问是:

  • fn.length 将返回 fn 中定义的参数数量。 arguments.length 会返回哪些参数的数量吗?已经存在的函数?
  • 但如果是这样,并且它们匹配,为什么它会应用 NEW 函数而不是现有函数?
  • 如果 arguments.length 返回给定函数的编号,那么它们何时会不同?
  • 我添加了10个方法,从无参数开始,每次增加数量,添加第10个方法后,我调用没有参数的方法,它在哪里“存储”了第一个函数?
  • 我不明白这里 old 的用法。

我可能没有一些可以回答所有问题的关键概念。请随意给我一个解释,而不是单独回答问题。

【问题讨论】:

  • 1. arguments.length 将是传递给调用它的最近的外部函数的参数 #。 2.它将应用与您添加的方法具有相同调用签名的一个,否则转到最后一个添加的方法。 3. arguments.length 返回传递的内容,而不是定义的内容。 4. as old,每个方法都使用闭包来查看。基本上,它会从 10 个参数开始,如果没有 10,则尝试 9,如果不是 9,则尝试 8,一直回到形式参数与参数匹配的那个。
  • 顺便说一句,这是设置可变参数方法的一种非常缓慢且需要大量内存的方法。如果可能的话,arity 分支都应该在一个函数中定义,使用类似switch(arguments.length) 的东西来处理路由。

标签: javascript function methods overloading


【解决方案1】:

fn.length 将返回 fn 中定义的参数数量。 arguments.length 会返回哪些参数的数量吗?已经存在的函数?

没有。 arguments 是一个类似数组的局部变量,可在函数内部使用。它包含传递给函数的参数数量。

您的其他问题可以通过解决这个问题来回答:

我添加了10个方法,从无参数开始,每次增加数量,添加第10个方法后,我调用没有参数的方法,它“存储”第一个函数在哪里?

一步一步查看addMethod 方法可能会有所帮助:

function addMethod(object, name, fn) {

  var old = object[name];
  // Get the old function corresponding to this name. Will be "undefined"
  // the first time "addMethod" is called.


  object[name] = function(){
  // Now, assign object[name] to a new function.
  // The critical part of this function is that "old" is captured inside of
  // this function and will be available any time the function is called.

       if (fn.length == arguments.length)
       // if the number of parameters belonging to the function we've added
       // matches what was passed in, call "fn"
          return fn.apply(this, arguments)

       else if (typeof old == 'function')
       // Otherwise if there's another function with this name
       // call it instead.
          return old.apply(this, arguments);

    };
}

因此,让我们在您的示例中每次调用addMethod 并检查fnold 的值。您可以将其组织为堆叠在一起的函数,利用old 的范围。

addMethod(ninja,'whatever',function(){ /* do something */ });
// old === undefined

addMethod(ninja,'whatever',function(a){ /* do something else */ });
// old === function #1

addMethod(ninja,'whatever',function(a,b){ /* yet something else */ });
// old === function #2

在调用addMethod 3 次结束时,ninja.whatever 指的是一个函数,如果可能,调用函数#3,然后如果参数不匹配函数#,则调用old 函数(函数#2) 3的参数列表长度。

所以在视觉上,你可以这样想:

function #3 (a,b)
function #2 (a)
function #1 ()

每个函数的 old 引用指向它下面的函数。

现在让我们看看当您调用ninja.whatever() 时会发生什么。这个函数位于我们堆栈的“底部”。

  1. 当前与whatever 关联的函数是函数#3。调用函数 #3 时,fn.length != arguments.length 等会执行 old(函数 #2)。
  2. 所以现在我们正在执行函数 #2。同样,在调用函数 #2 时,fn.length != arguments.lengthold(函数 #1)会被执行。
  3. 最后,函数#1 被执行。这次是fn.length == arguments.length,所以调用了这个函数。

【讨论】:

  • 现在更清楚了。级联关闭方案是我缺少的关键概念。谢谢你的详细问题,这真的很有帮助。
  • @user1869935:很高兴为您提供帮助
【解决方案2】:

已添加评论:

//replace object[name] with a wrapper that either calls the passed-in 
//function (fn) or the old value of object[name]
function addMethod(object, name, fn) {

  //store object[name]    
  var old = object[name];



  object[name] = function(){

        //if the wrapper is called with as many arguments as is the arity of the passed in function (fn), call the passed in function (fn)
       if (fn.length == arguments.length)

          return fn.apply(this, arguments)

       //otherwise call the old value of object[name] but only if it is a function
       else if (typeof old == 'function')

          return old.apply(this, arguments);

    };
}

【讨论】:

    【解决方案3】:

    当一个函数被创建时,它保留对定义它的范围内的变量的访问。在这种情况下,我们创建的每个方法都可以访问包含变量 old、object、name 和 fn 的作用域。旧变量可以从我们添加的每个方法中访问,并且主对象对它的引用被覆盖,因此只能从该范围内的“旧”访问。您基本上有一堆“旧”变量通过它们级联以找到处理给定参数数量的变量。

    function addMethod(object, name, fn) {
    
      // Get a reference to the existing method name.
      var old = object[name];
    
      /*
        Write over the method name with a new method
        that checks for a specific argument length.
        If the method is called with a different
        argument length and "old" exists call the
        old method.
      */
    
      object[name] = function(){
    
           if (fn.length == arguments.length)
    
              return fn.apply(this, arguments)
    
           else if (typeof old == 'function')
    
              return old.apply(this, arguments);
    
        };
    }
    

    【讨论】:

      【解决方案4】:

      function.length 是函数期望的参数数量,即形式参数。 arguments.length 是传递给函数的实际参数数量,这意味着已经存在的函数,因为没有任何参数可以传递给新函数;它没有被调用,只是一个值。 arguments 对象是一个包含传递参数的类数组对象。 (知道这一点,length 属性可能更有意义)。给定参数的长度和形参的长度之间的区别在于,函数可以传递比规定参数更多或更少的参数。例如,console.log() 接受任意数量的参数。其中一些函数只是围绕参数对象包装一个循环,并对参数对象中的每个元素做一些事情。添加的每个方法都存储为作为addMethod 函数的第一个参数给出的对象的属性。变量old 只是一个变量,名称不是理解概念所必需的。也许作者通过old 暗示了对象及其属性的可变性(可变性是赋予属性新值/在声明后修改对象的能力)。

      【讨论】:

        【解决方案5】:

        dandavis 的 cmets 解决了您最初提出的大部分问题。 arguments.length 给出应用于该行中声明的函数的参数数量

        object[name] = function(){
        

        该方法“存储”在您在第一行代码中声明的ninja 对象中,并传递给后续调用。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-08-09
          • 1970-01-01
          • 2016-04-02
          • 1970-01-01
          • 2012-11-21
          • 1970-01-01
          • 2015-10-02
          • 1970-01-01
          相关资源
          最近更新 更多