【问题标题】:How to detect the end of a method chain in JavaScript?如何检测 JavaScript 中方法链的结束?
【发布时间】:2013-01-02 14:11:20
【问题描述】:

首先也是最重要的是,我试图检测方法链的结束调用。我还想设计一种方法来检测我在方法链中的方法调用中有多少方法“进入”或“向下”对象链。

例如,在我正在编写的插件中:

var result = $("#someDiv").myPlugin.foo().bar()._foo()._bar();

假设该方法当前正在 .bar() 中执行,我想知道我是整个链中的 2 个方法。

我需要以某种方式抽象这些信息的原因是,当我到达链中的最后一个方法时,我可以返回结果而不是插件对象,从而在该点打破链,以便访问我们的“结果”变量中的数据。

【问题讨论】:

  • 如果我正确理解了这个问题,我宁愿创建一个 .result() 方法,它被称为最后一个并返回您想要的值。
  • 给你的最后一个函数一个参数,告诉它它是否是链中的最后一个。
  • Juhana - 我曾想过,但我想找到一种方法让用户将数据返回到变量,而无需进行额外的方法调用。
  • Ilia G - 无法确定调用方法的顺序,因此不幸的是链中没有最后一个方法。
  • Juhana - 考虑到你不知道插件是什么,这是一个崇高的声明。该插件允许您直接在 JavaScript 中处理 PHP 返回的数据,因此实际上这是您希望使用此特定插件的主要方式。

标签: javascript jquery jquery-plugins


【解决方案1】:

这是一个来自your project的示例:

var strLenA = parseInt( P.strlen('some string').data );
var strLenB = parseInt( P.strlen('another string').data );
var totalStrLen = strLenA + strLenB;
console.log(strLenA, strLenB, totalStrLen);

从这里我可以看出为什么我们的答案并不充分——以及为什么你想摆脱.data。令人高兴的是,无论如何,您的 .data 总是返回一个字符串。因此,您可以使用神秘的 .toString 覆盖让您的方法仍然返回父方法的副本 - 但也允许将它们视为字符串。

这是一个例子:[带有fiddle]

var stringMagic = function() {
    var chain = "",
        self = this;
    self.toString = function () { return chain; }; // Where the real magic happens.
    self.add = function(str) {
        chain += str + " ";
        return self;
    };
};


var magi = new stringMagic();
alert(magi.add("hello").add("world")); // Alerts, "hello world"
magi.add("and").add("thanks").add("for").add("all").add("the").add("fish");
alert(magi); // Alerts, "hello world and thanks for all the fish"

在您的情况下,您可能需要做的就是将P 中的.data 更改为.toString 并将其包装在一个函数中。

将来当您添加对其他数据类型(例如数字和布尔值)的支持时,您可以像使用 toString 一样使用 valueOf。事实上,当返回值不是字符串时,您还应该继续包含toString,因为当他们将该数字视为字符串时 - 就像在console.log$.fn.text 中一样。这是上面的示例,但带有数字:http://jsfiddle.net/emqVe/1/

【讨论】:

  • 非常感谢您的关注和努力。你给了我一些很好的思考。我继续并实现了数据类型解析。我还添加了使用填充 PHP“代码”的特殊 JSON 块的功能。这些块在服务器上一次性执行并返回最终结果。
  • 我的荣幸。这是一个有趣的挑战,所以我有点陷入其中。出于好奇,你为什么坚持使用.data.result()/.end() 而不是实现toString/valueOf 之类的东西?我正在考虑自己在一个项目中使用toString/valueOf,但如果你发现它有问题,我不想这样做。
  • 与之前提出的以 .result() 结束链的解决方案没有区别。此解决方案之所以有效,是因为字符串作为参数传递,并且 Alert 将 .toString() 应用于任何非字符串对象。在任何其他上下文中,使用 magic = magi.add("hello").add("world"),magic 是一个对象,必须调用 out.toString() 才能返回当前文本。
  • @widget。你在正确的轨道上,但有区别。不同之处在于.result() 的功能是在.toString() 中实现的。看看 OP 在他的实例中实际试图完成的工作(阅读我上面回答的第一部分),这与帮助解决他的实际问题有关。当对象被视为字符串时,JavaScript 会自动使用.ToString 方法。例如,magi.add("hello").add("world") + "s" === "hello worlds"。如果这对您的情况没有帮助,那么我建议您寻求不同的解决方案。
【解决方案2】:

为了完整起见。另一种选择是传递一个对象,该对象将随着链的进展而更新。这将让您在适合您的时候访问结果值(而不必在链的末尾添加它)。

而不是这样的语法:

var result = chainableFunction.doThis().doThat().result;

你会得到:

chainableFunction.update(variableToUpdate).doThis().doThat();
var result = variableToUpdate.result;

逻辑与其他人提出的解决方案非常相似。使用哪一个可能取决于您的用例。必须用 .result 结束链的一个可能问题是没有什么可以阻止您以这种方式使用它:

var chainedUp = chainableFunction.doThis().doThat();
doSomething(chainedUp.result);
... 
chainedUp.doOneMoreThing()
... 
doSomething(chainedUp.result);  // oups, .result changed!

使用 variableToUpdate 选项,结果值不受未来函数调用的影响。同样,这在某些情况下可能是可取的,而不是在其他情况下。

下面的完整示例

#!/usr/bin/env node

var ChainUp = {};
(function(Class) {

  // Pure functions have no access to state and no side effects
  var Pure = {};
  Pure.isFunction = function(fn) {
     return fn && {}.toString.call(fn) === '[object Function]';
  };

  Class.instance = function() {
    var instance = {};
    var result;
    var updateRef;

    function callBack(fn) {
      // returning a clone, not a reference.
      if(updateRef) { updateRef.r = (result || []).slice(0); } 
      if(Pure.isFunction(fn)) { fn(result); }
    }

    instance.update = function(obj) {
      updateRef = obj;
      return instance;
    };

    instance.one = function(cb) {
        result = (result || []); result.push("one");
        callBack(cb);
        return instance;
      };
      instance.two = function(cb) {
        result = (result || []); result.push("two");
        callBack(cb);
        return instance;
      };
      instance.three = function(cb) {
        result = (result || []); result.push("three");
        callBack(cb);
        return instance;
      };
      instance.result = function() {
        return result;
      };
    return instance;
  };

}(ChainUp));


var result1 = {};
var chain = ChainUp.instance().update(result1);
var one = chain.one(console.log); // [ 'one' ]
console.log(one.result());        // [ 'one' ]
console.log(result1.r);           // [ 'one' ]

var oneTwo = chain.two(); 
console.log(oneTwo.result());  // [ 'one', 'two' ]
console.log(result1.r);        // [ 'one', 'two' ]

var result2 = {};
var oneTwoThree = chain.update(result2).three();
console.log(oneTwoThree.result()); // [ 'one', 'two', 'three' ]
console.log(result2.r);            // [ 'one', 'two', 'three' ]

console.log(result1.r);             // [ 'one', 'two' ]

注意。类和实例关键字可能不熟悉。这是我在使用闭包而不是原型继承从原型构造实例时使用的约定。您可以用 self 替换实例(并且 self = this 而不是 instance = {})..

【讨论】:

    【解决方案3】:

    在确定返回值时,无法确定调用是否是链中的最后一个实例,原因如下:

    var result = $("#someDiv").myPlugin.foo().bar()._foo()._bar();
    

    foo 返回myPlugin,在其上调用bar,在其上返回myPlugin,在其上调用_foo,在其上返回myPlugin,在其上调用_bar

    如此有效地,当_foo 返回其值(myPlugin)时,它是在该值被使用之前。除非_foo 具有通灵能力,否则它无法知道接下来会发生什么。

    正如您在 cmets 中指出的那样,最好的办法是使用一些“结束”方法,例如 results()

    另一个建议是将处理程序传递给myPlugin,该处理程序被调用以使用setTimeout(..., 0) 设置值。在 myPlugin 中有一个值,即 foo、bar、_foo 和 _bar 都已设置。我们称之为returnValue。修改 myPlugin 以接受一个方法作为它的唯一参数。让我们称之为handler。此方法的第一个参数将包含该值。在 myPlugin 内部,在您的 return 之前,执行以下操作:

    window.setTimeout(function () {
        handler(returnValue);
    }, 0);
    

    由于 setTimeout 的函数参数在执行完成之前不会被调用,因此它将包含为 returnValue 设置的最后一个值 - 实际上是链中最后一次调用设置的值。我认为这是最接近您想要实现的选择,因为开发人员不必担心最后调用他的哪个方法。

    【讨论】:

    • 是的,我完全清楚每个返回都是插件本身(从而使链接模式成为可能)。诀窍是检测是否是最后一个方法调用的条件,并在这种情况下返回结果数据与插件本身。感谢您的意见,但我拒绝相信目前不可能。
    • 我很欣赏你的热情!所以,你绝对需要把它放在return,对吧?如果没有,还有更多选择。
    • 喜欢,你愿意做var result = $("#someDiv").myPlugin.foo().bar()._foo()._bar()();$("#someDiv").myPlugin.foo().bar()._foo()._bar(result);$("#someDiv").myPlugin(result).foo().bar()._foo()._bar();吗?
    • 我目前正在做的只是:$("#someDiv").myPlugin.foo().bar()._foo().someMethod().data ...这只是设置一个属性来保存返回数据,它工作得很好。只是我真的不想这样做。
    • 嗯,我包括了如何做$("#someDiv").myPlugin(result).foo().bar()._foo()._bar(); 的解释,因为它最类似于您尝试尝试的内容。开发人员仍然不需要跟踪链中最后调用的方法,但我知道这不是您想要的。
    【解决方案4】:

    没有(合法的、简单的或好的)方法来找出 inside 方法的结果 outside 在返回后会发生什么。您应该使用“链结束标记”方法。

    再想一想,您是在寻找应用于对象的最后一个方法,还是想检测更明确的东西?也许您失去了将方法应用于决策的可能性(使用虚假的愚蠢方法名称):

    obj.turnLeft().pushUp().makeBig().makeSilent;
    if (colorSupport) {
      obj.paintRed();
    } else {
      obj.paintStripes();
    }
    obj.makeShine().lastMethodCallSoObjectIsNowInFinalState();
    

    【讨论】:

    • 我不活在“合法、轻松或美好”的世界里。 ;) 我不是在寻找应用于对象的最后一个方法,我只是想检测最后一个方法以返回计算结果。也许通过查看我的插件,您可能会更好地理解我的意图:github.com/Xaxis/jqueryphp
    • 哎呀,这是一个勇敢的想法!那么,如果我理解得很好,您的目标是在服务器端“保留”链式调用,而不是为每个调用执行单独的 HTTP 请求吗?
    • 如果是,您的 .doThis() .doThat() 方法只应将 command 元素(“this”和“that”)添加到对象的 command queue,最后的 lastMethod...() 应该是将队列发布到服务器端的那个,然后由服务器执行所有排队的方法并返回 -最后结果。 (如果我了解您的真正问题。)
    • 你不喜欢这样的语法:obj.phpcall("phpfunct",parm1,parm2).phpcall("anotherphpfunctwoparm").perform(); ?
    • 哇@Xaxis,确实很勇敢。我可以说它也很糟糕!所以我的理解是你已经需要一些回调函数——但它可能是事先定义的。因此,整个“在myPlugin 中保留一个被链中所有函数覆盖的变量”应该可以很好地工作(请参阅我更新的答案) - 无需使用 setTimeout,因为您将进行异步调用,无论如何。
    【解决方案5】:

    没有本地方法,但是,您可以根据需要将参数添加到要链接的方法中。并通过将其设置为true 来确定当前呼叫是否是最新的,如:

    var AlertText = {
      text: "",
      chainEntry: function(textToAdd, isTheLastOne) {
        this.text += textToAdd;
        // Perform only if this is told to be the last call in the chain:
        if (isTheLastOne) {
          alert(this.text);
          this.text = "";
        }
        //
        return this;
      }
    };
    
    AlertText
      .chainEntry("Hi, ")
      .chainEntry("how ")
      .chainEntry("are ")
      .chainEntry("you?", true); // alert("Hi, how are you?");
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-04-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-22
      相关资源
      最近更新 更多