【问题标题】:Javascript: Function ChainingJavascript:函数链
【发布时间】:2013-04-07 22:09:59
【问题描述】:

解决方案:不。你不能这样做。用 && 链接命令并用它来完成。

tl;dr:在random.function("blah").check() 中,如果function("blah") 返回false,我如何让它忽略check()?或者这甚至可能吗?

背景:

好的,所以我对代码感到恐惧。但我试图通过编写能让我的生活稍微轻松一些的东西来拖延。而我的边缘,我的意思是微观上。所以基本上,我的整个项目都是基于对象和插件的。 (插件是包含对象的附加 .js 文件。例如,afk.js 看起来像这样:

global.afk = function(){};
afk.check = function(){ ... };

等等等等。通过加载那个 js 文件,我可以访问 afk 对象和其中的所有函数。可能效率不高,但它可以工作,并且可以更容易地弄清楚哪些功能正在做什么。

回到拖延问题。因为不知道加载了哪些插件,所以在一些共享的核心 .js 文件中,有这些条件语句:if (plugin.has("afk")) { afk.check(); }; 然而,我无聊让plugin.has() 返回实际对象而不是'true',所以我可以这样做:

plugin.has("afk").check();

按预期工作。除非没有afk插件,否则会抛出“false has no method check”的明显错误。

就是这样。有任何想法吗?另外,如果您对任何事情有任何疑虑,请告诉我。我知道我做的很多事情都是错误的/效率低下的,当我弄清楚它们时,我很高兴能解决它们。

【问题讨论】:

  • 查看jQuery的源码... :)
  • @PraveenKumar 我去过,但我似乎无法找出他们使用哪个位。我有时会迷失在 jQuery 中哈哈。
  • 我对你的问题投了赞成票……我也有同样的问题,在同样的情况下,迷失在 jQuery 代码中……说真的,他们很摇滚! :P
  • 不可能。而不是返回false,您应该在内部跟踪状态并根据check 方法应该做什么来决定。
  • @FelixKling 好吧,这不仅仅是为了检查。这适用于做任何事情的任何数量的功能。我正在考虑将plugin.has() 切换为plugin.has("afk","check"); 或类似的东西,所以不是链接函数,它只是一个函数。但是将变量传递给检查函数会变得很棘手。

标签: javascript node.js function object


【解决方案1】:

返回的对象应该有那个函数,否则你会得到一个错误。您可以做的是创建一个始终返回的基本 Object 类型。比你返回它时,它有一个空函数,不会产生错误。

类似:

var baseObj = function(){
    this.afk = function(){
        return this;
    }
    this.check = function(){
        return false;
    }
};

var advObj = new baseObj();
advObj.afk = function(){
    // extended func
    return new baseObj();
}

var a = new advObj();
a.afk("test").check();

【讨论】:

  • 因此,对于我添加的每个函数,我都必须向空白的空对象添加一个类似的函数,然后返回该对象,这样行就不会中断?那个空白物体一定很大。我有很多插件。 :P
  • 当然,这对于真实类支持的每个函数都需要一个虚拟函数,这听起来有点不切实际。如果可能的话,某种覆盖 call() 和 apply() 以模拟出每个可能的函数名称的虚拟类将是理想的。
  • 您并不完全知道链式函数是什么。 jQuery 本身是一个带有自定义函数的扩展数组。它将使用所有 $.fn 函数进行扩展。因此,返回的对象始终是this,该对象包含项目中的所有插件。因此不会崩溃。
【解决方案2】:

所以 jQuery 做的有点像这样:

$(plugin) -> 返回一个类似对象的集合
.has("afk") -> 过滤内部集合,并返回集合
.check() -> 对这个集合执行的操作

那么让我们做一个小例子:

function Wrapper(ele) {
  // make our collection
  this.coll = [ ele ];

  // function to filter
  this.has = function(filter) {
    this.coll = this.coll.filter(function(e) {
      return e === filter;
    });

    // we return ourself for chaining
    return this;
  };

  // function to execute on ourself
  this.check = function() {
    // returns actual value
    return this.coll.length;
  };
}

var $ = function(ele) {
  // wrapper around the `new` syntax
  return new Wrapper(ele);
};

console.log($('jan').has('jan').check()); // gives 1
console.log($('pietje').has('jan').check()); // gives 0

很酷的是,您现在可以通过扩展原型从外部文件扩展您的 Wrapper 对象:

Wrapper.prototype.hasNot = function(filter) { 
    /* do magic */
    return this;
}

【讨论】:

  • 但是,这看起来我必须编写一个全局对象,其中包含每个插件文件中每个函数的版本。我开始认为我想做的事情是不可能的。
  • 好吧,我仍然需要一个全局对象,我可以从外部文件中修改它,这只会让它稍微容易一些。但这仍然没有比 true/false 函数和执行 if (plugin.has("afk")) { afk.check(); }; 更有效
  • 嗯...我不确定,但你可能会用Function.prototype.constructor 做一些疯狂的事情,每次在任何地方定义一个函数时自动向你的全局对象添加一个新的虚拟函数.不过,必须注意无限循环。
【解决方案3】:

使用 && 运算符是一种简单而肮脏的方法。

var x = {};

x.afk && x.afk();

x.afk = function () { console.log("I exist!"); };

x.afk && x.afk();

虽然不是您的最佳宽范围解决方案。

【讨论】:

  • 我的代码基本上已经是这个了。我确实使用 && 和 ||广泛使用运算符,我只是将 if 语句用作更易于阅读的示例。在定义了 plugin.has 和 afk.check 之后,运行的代码是:plugin.has("afk") && afk.check(); 这真的很好很简单,我只是觉得我描述的方式会更好、更容易、更美观。跨度>
  • 其他人提到覆盖或扩展 apply() 和 call() 这可能是你最好的选择,或者实现你自己的。
【解决方案4】:

在当前的 ECMAScript 规范中,将 .check() 变成无操作(以及可能存在于不存在的插件上的任何其他方法)是不可能的,因为您无法以可移植的方式访问未定义对象的未定义属性。

提议的"Direct Proxies" ECMAScript API 似乎可以解决这个问题,但它还没有标准化。我还怀疑以这种方式代理您的代码效率不高,因为您(AIUI)必须让您的 .has() 函数返回一个捕获代理对象,然后必须为每个“错过”的函数调用做真正的工作。恕我直言,最好确保条件代码根本不运行。

所以,除了其他地方提出的&& 方法,我能提供的只有这个:

plugin.if("afk", function() {
    // put conditional code here
});

【讨论】:

    【解决方案5】:

    它不适用于所有浏览器,但我认为您可以创建一个使用__noSuchMethod__ 的虚拟类来始终返回自身,而不是在调用不存在的方法时抛出错误。见this question

    我不知道这适用于哪些浏览器,我的总体印象是“不多”。

    【讨论】:

    • 我想我会试一试(我在 node.js 中将它作为一个程序运行,所以浏览器兼容性对我来说不是真正的问题:P)
    • 好的,所以plugin.has 在 true 时返回对象,在 false 时返回 plugin.blank 这是这样的:plugin.blank = { __noSuchMethod__ : function(a,b) { return; } }; 但是,我仍然得到“没有方法检查”:D跨度>
    • 那是因为 V8 不支持 __noSuchMethod__ - 这是非标准的。
    【解决方案6】:

    哦,你好。 Node-proxy 看起来很有希望。我实际上对此一无所知,但听起来它应该让你做这种事情。

    【讨论】:

      【解决方案7】:

      实际上你已经自己解决了问题,至少差不多:

      这适用于做任何事情的任意数量的函数。我正在考虑将plugin.has() 切换为plugin.has("afk","check"); 或类似的东西,所以不是链接函数,它只是一个函数。 但是将变量传递给检查函数会变得很棘手。

      嗯,基本上有两种方法:

      plugin_with_afk.has("afk","check").check(100); // "check" twice
      plugin_with_afk.has("afk","check")(100); // "check" not twice
      

      第一种方法其实很简单:如果插件有“afk”就返回this,否则返回一个有无操作方法的对象check

      Plugin.prototype.has = function(str, member){
          if(this.things.indexOf(str) > -1) {
              return this;
          } else {
              var tmp = {}
              tmp[member] = function(){}
              return tmp;
          }
      }
      
      plugin_with_afk.has("afk","check").check(1);     // has afk, check is there
      plugin_without_afk.has("afk","check").check(2);  // has no afk, check is no-op
      plugin_without_afk.has("nope","check").check(3); // has nope, check is there
      

      这还有一个很大的好处,就是你不需要使用任何函数包装器等等。但是,您必须指定两次使用的函数,如果您不小心使用了另一个名称,这将导致 ReferenceError

      plugin_with_afk.has("afk","check").test(1); // oh oh
      

      那么你能做些什么呢?创建一个简单的函数包装器:

      PluginWithFuncRet.prototype.has = function(str,member){
          var self = this;
          if(this.things.indexOf(str) > -1) {
              return function(){
                  return self[member].apply(self, arguments);
              }
          } else {
              return function(){};
          }
      }
      
      plugin_with_afk.has("afk","check")(4);   // calls check()
      plugin_without_afk.has("afk","check")(5);// no-op
      

      就是这样。请注意,您实际上应该将 has 重命名为 use_if 或类似名称,因为它现在所做的不仅仅是检查组件是否存在。

      完整代码(demo)

      var PluginBase = function(name, things){
          this.name = name;
          this.things = things;
      }
      
      /// First variant
      var Plugin = function() {
          this.constructor.apply(this, arguments);
      }
      Plugin.prototype.constructor = PluginBase;
      
      Plugin.prototype.has = function(str,member){
          if(this.things.indexOf(str) > -1) {
              return this;
          } else {
              var tmp = {}
              tmp[member] = function(){}
              return tmp;
          }
      }
      
      var plugin_with_afk = new Plugin("with afk", ["afk"]);
      plugin_with_afk.check = function(val){
          console.log("hi", val, this.name);
      };
      
      var plugin_without_afk = new Plugin("w/o afk", ["nope"]);
      plugin_without_afk.check = function(val){
          console.log("nope", val, this.name);
      }
      /// First variant demo
      
      plugin_with_afk.has("afk","check").check(1)
      plugin_without_afk.has("afk","check").check(2)
      plugin_without_afk.has("nope","check").check(3)
      
      /// Alternative
      var PluginWithFuncRet = function(){
          this.constructor.apply(this, arguments);
      }
      PluginWithFuncRet.prototype.constructor = PluginBase;
      
      PluginWithFuncRet.prototype.has = function(str,member){
          var self = this;
          if(this.things.indexOf(str) > -1) {
              return function(){
                  return self[member].apply(self,arguments);
              }
          } else {
              return function(){}
          }
      }
      
      plugin_with_afk    = new PluginWithFuncRet("with afk",["afk"]);
      plugin_with_afk.check = function(val){
          console.log("Hi",val,this.name);
      }
      plugin_without_afk    = new PluginWithFuncRet("w/o afk",["nope"]);
      plugin_without_afk.check = function(val){
          console.log("Nope",val,this.name);
      }
      
      /// Alternative demo
      plugin_with_afk.has("afk","check")(4);
      plugin_without_afk.has("afk","check")(5);
      plugin_without_afk.has("nope","check")(6);
      

      结果:

      喜 1 与 afk
      不 3 w/o afk
      嗨 4 与 afk
      不 6 w/o afk
      

      【讨论】:

        【解决方案8】:

        所以这就是我想出的,并最终最终使用。
        与其创建一个虚拟包装器,我决定加载所有的
        插件到主包装器中。所以现在,afk 是 plg.afk,就像
        所有其他插件。这让我可以编写这个包装器:

        global.& = function(a,b) {
          var x,y,z;if (!a) return false; //if nothing, return false.
          if (a.indexOf(".") < 0) { //if value has no "."
            if (plg["data"][a]) { x = ["data",a]; } //use data function if exists
            else { x = ["basic",a]; } //else assume it's a basic function
          } else { //or if it does contain "." (such as my example, afk.check)
            x = a.split(".") //define the plugin, and the function as an array.
          }
          y = x[0],z = x[1]; //define the plugin,function as variables.
          if (!plg[y]) { return false; } //return if plugin doesn't exist.
          if (!plg[y][z]) { return false; } //return if function doesn't exist.
          if (!b) { plg[y][z](); } //if no variable, call raw function
          return plg[y]z[](b); //return function, passing an optional variable.
        }
        

        所以这行得通!我可以打电话给 $("afk.check"); 它本质上是这样的:

        if (plugin.has("afk") && afk.hasOwnProperty("check")) { afk.check(); };
        

        :D

        我可以传递变量,并使用基本功能的快捷方式。

        例如,如果我调用$("boot");,它会检查引导函数的数据,如果找不到,则返回到 basic.boot();,并调用正确的函数。并且我可以调用数据函数并传递变量例如在保存数据的情况下,$("save",data);

        如果这是一个愚蠢的想法,请随时告诉我! 但到目前为止它对我有用

        【讨论】:

        • 这是一个愚蠢的想法,因为您最终会得到一堆无法维护的代码。你基本上是在重新实现eval()。我也看不出你怎么能在这里链接命令。
        • @JanJongboom 它不是关于链接命令,而是更多关于能够在调用之前检查函数是否存在。 :$
        • 在这种情况下,plugin.afk &amp;&amp; plugin.afk() 是一种更好的解决方案恕我直言,它不涉及使用魔术字符串调用包装函数,然后拆分字符串并调用魔术,或者任何闻起来像 eval 的东西 :-)
        • 这也是一个相当标准的模式。
        • 是的,我开了个玩笑,说这完全是因为我在拖延,而且开始看起来不像是个玩笑了。我一直在寻找一种更简单的方法,而我基本上已经有了最简单的方法。谢谢。我想知道我是否可以删除问题...
        猜你喜欢
        • 2015-02-21
        • 1970-01-01
        • 2015-08-17
        • 1970-01-01
        • 1970-01-01
        • 2023-03-07
        • 2016-02-26
        • 2015-05-10
        • 1970-01-01
        相关资源
        最近更新 更多