【问题标题】:Overriding a JavaScript function while referencing the original在引用原始函数时覆盖 JavaScript 函数
【发布时间】:2010-09-22 17:43:30
【问题描述】:

我有一个函数a(),我想重写它,但还要根据上下文按顺序执行原始a()。例如,有时当我生成一个页面时,我想像这样覆盖:

function a() {
    new_code();
    original_a();
}

有时像这样:

function a() {
    original_a();
    other_new_code();
}

如何从覆盖的a() 中获取original_a()?有没有可能?

请不要以这种方式提出替代方案,我知道很多。我是专门问这种方式的。

【问题讨论】:

    标签: javascript overriding user-defined-functions


    【解决方案1】:

    你可以这样做:

    var a = (function() {
        var original_a = a;
    
        if (condition) {
            return function() {
                new_code();
                original_a();
            }
        } else {
            return function() {
                original_a();
                other_new_code();
            }
        }
    })();
    

    在匿名函数内声明 original_a 可以防止它弄乱全局命名空间,但它可以在内部函数中使用。

    就像 cmets 中提到的 Nerdmaster 一样,一定要在末尾包含 ()。您想调用外部函数并将 result(两个内部函数之一)存储在 a 中,而不是将外部函数本身存储在 a 中。

    【讨论】:

    • 对于任何像我这样的白痴 - 密切注意最后的“()” - 没有它,它返回外部函数,而不是内部函数:)
    • @Nerdmaster 感谢您指出这一点。我添加了一条注释,希望能帮助人们注意到。
    • 哇,我尝试在没有命名空间的情况下执行此操作,结果堆栈溢出,哈哈。
    • 我认为如果从函数中删除第一个和最后一个括号,结果将是相同的。喜欢从这里(functi..})。只是做}(); 会产生相同的结果,因为(function{})() 使用了第一组括号,因为你不能只声明匿名函数表达式,你必须分配它。但是通过 () 你没有定义任何东西,只是返回一个函数。
    • @MuhammadUmer 你是对的,函数表达式周围的括号不是必需的。我包括它们是因为没有它们,如果您只看前几行,它看起来(至少在我看来)就像您将外部函数直接分配给 a,而不是调用它并存储返回值。括号让我知道还有其他事情发生。
    【解决方案2】:

    Proxy pattern 可能会对您有所帮助:

    (function() {
        // log all calls to setArray
        var proxied = jQuery.fn.setArray;
        jQuery.fn.setArray = function() {
            console.log( this, arguments );
            return proxied.apply( this, arguments );
        };
    })();
    

    上面将其代码包装在一个函数中以隐藏“代理”变量。它将 jQuery 的 setArray 方法保存在一个闭包中并覆盖它。然后代理记录对该方法的所有调用并将调用委托给原始方法。使用 apply(this, arguments) 可以保证调用者不会注意到原始方法和代理方法之间的区别。

    【讨论】:

    • 确实如此。 'apply' 和 'arguments' 的使用使得这比其他答案更强大
    • 请注意,此模式不需要 jQuery——它们只是使用 jQuery 的一个函数作为示例。
    【解决方案3】:

    谢谢大家,代理模式真的很有帮助.....实际上我想调用一个全局函数 foo.. 在某些页面中,我需要做一些检查。所以我做了以下事情。

    //Saving the original func
    var org_foo = window.foo;
    
    //Assigning proxy fucnc
    window.foo = function(args){
        //Performing checks
        if(checkCondition(args)){
            //Calling original funcs
            org_foo(args);
        }
    };
    

    这真的帮了我很多忙

    【讨论】:

    • 当关注proxy pattern 时,在某些情况下实现此答案代码中不存在的细节很重要:而不是org_foo(args),调用org_foo.call(this, args)。这会维护this,就像 window.foo 正常调用(未代理)时一样。见this Answer
    【解决方案4】:

    您可以使用以下构造覆盖函数:

    function override(f, g) {
        return function() {
            return g(f);
        };
    }
    

    例如:

     a = override(a, function(original_a) {
          if (condition) { new_code(); original_a(); }
          else { original_a(); other_new_code(); }
     });
    

    编辑:修正了一个错字。

    【讨论】:

    • +1 这比其他代理模式示例更具可读性!
    • ...但如果我没记错的话,这仅限于零参数函数,或者至少是一些预定数量的参数。有什么方法可以将其推广到任意数量的参数而不会失去可读性?
    • @MuMind:是的,但是正确使用代理模式 - 请参阅 my answer
    【解决方案5】:

    传递任意参数:

    a = override(a, function(original_a) {
        if (condition) { new_code(); original_a.apply(this, arguments) ; }
        else { original_a.apply(this, arguments); other_new_code(); }
    });
    

    【讨论】:

    • 不会参数引用original_a 吗?
    【解决方案6】:

    @Matthew Crumley 提供的答案是利用立即调用的函数表达式,将旧的“a”函数关闭到返回函数的执行上下文中。我认为这是最好的答案,但就个人而言,我更愿意将函数“a”作为参数传递给 IIFE。我觉得这样比较好理解。

       var a = (function(original_a) {
            if (condition) {
                return function() {
                    new_code();
                    original_a();
                }
            } else {
                return function() {
                    original_a();
                    other_new_code();
                }
            }
        })(a);
    

    【讨论】:

      【解决方案7】:

      以上示例未正确应用 this 或将 arguments 正确传递给函数覆盖。下划线 _.wrap() 包装现有函数,应用 this 并正确传递 arguments。见:http://underscorejs.org/#wrap

      【讨论】:

        【解决方案8】:

        我有一些由其他人编写的代码,想在一个我在代码中找不到的函数中添加一行。因此,作为一种解决方法,我想覆盖它。

        但没有一个解决方案对我有用。

        以下是我的情况:

        if (typeof originalFunction === "undefined") {
            originalFunction = targetFunction;
            targetFunction = function(x, y) {
                //Your code
                originalFunction(a, b);
                //Your Code
            };  
        }
        

        【讨论】:

          【解决方案9】:

          我为类似的场景创建了一个小助手,因为我经常需要覆盖多个库中的函数。这个助手接受一个“命名空间”(函数容器)、函数名和覆盖函数。它将用新函数替换引用命名空间中的原始函数。

          新函数接受原始函数作为第一个参数,原始函数作为其余参数。它将每次都保留上下文。它也支持 void 和 non-void 函数。

          function overrideFunction(namespace, baseFuncName, func) {
              var originalFn = namespace[baseFuncName];
              namespace[baseFuncName] = function () {
                  return func.apply(this, [originalFn.bind(this)].concat(Array.prototype.slice.call(arguments, 0)));
              };
          }
          

          以 Bootstrap 为例:

          overrideFunction($.fn.popover.Constructor.prototype, 'leave', function(baseFn, obj) {
              // ... do stuff before base call
              baseFn(obj);
              // ... do stuff after base call
          });
          

          虽然我没有创建任何性能测试。它可能会增加一些不必要的开销,这可能是也可能不是什么大问题,具体取决于场景。

          【讨论】:

            【解决方案10】:

            在我看来,最重要的答案是不可读/不可维护的,其他答案没有正确绑定上下文。这是一个使用 ES6 语法来解决这两个问题的可读解决方案。

            const orginial = someObject.foo;
            someObject.foo = function() {
              if (condition) orginial.bind(this)(...arguments);
            };
            

            【讨论】:

            • 但是使用 ES6 语法使得它在使用上相当受限。你有适用于 ES5 的版本吗?
            【解决方案11】:

            所以我的答案最终成为了一个解决方案,它允许我使用指向原始对象的 _this 变量。 我创建了一个“Square”的新实例,但是我讨厌“Square”生成它的大小的方式。我认为它应该符合我的特定需求。然而,为了做到这一点,我需要广场有一个更新的“GetSize”函数,该函数的内部调用广场中已经存在的其他函数,例如this.height、this.GetVolume()。但为了做到这一点,我需要在没有任何疯狂黑客的情况下做到这一点。所以这是我的解决方案。

            其他一些对象初始化器或辅助函数。

            this.viewer = new Autodesk.Viewing.Private.GuiViewer3D(
              this.viewerContainer)
            var viewer = this.viewer;
            viewer.updateToolbarButtons =  this.updateToolbarButtons(viewer);
            

            另一个对象中的函数。

            updateToolbarButtons = function(viewer) {
              var _viewer = viewer;
              return function(width, height){ 
            blah blah black sheep I can refer to this.anything();
            }
            };
            

            【讨论】:

              【解决方案12】:

              不确定它是否适用于所有情况,但在我们的例子中,我们试图覆盖 Jest 中的 describe 函数,以便我们可以解析名称并在遇到某些情况时跳过整个 describe 块标准。

              这对我们有用:

              function describe( name, callback ) {
                if ( name.includes( "skip" ) )
                  return this.describe.skip( name, callback );
                else
                  return this.describe( name, callback );
              }
              

              这里有两点很关键:

              1. 我们不使用箭头函数() =>

                箭头函数将引用更改为this,我们需要将其作为文件的this

              2. 使用this.describethis.describe.skip 而不仅仅是describedescribe.skip

              同样,不确定它对任何人是否有价值,但我们最初试图摆脱 Matthew Crumley's excellent answer 但需要使我们的方法成为函数并接受参数以便在条件中解析它们。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2013-04-06
                • 1970-01-01
                • 2012-05-12
                • 2020-12-15
                • 1970-01-01
                • 2013-06-01
                • 2014-11-20
                相关资源
                最近更新 更多