JavaScript链式规则,返回特定值而不是[Object object] [x]

6

问题在标题中,但是请先看这段代码:

function number(a) {
    return {
        add: function(b) {
            result = a + b;
            return this;
        }, substract(b) {
            result = a - b;
            return this;
        }
}

以上代码是链式规则的简单示例。我返回一个对象,因此可以连续执行:

number(2).add(5).add(3 * 12).substract(Math.random());

我的问题是,我必须返回一个对象以使函数可链式使用。我想模仿链式法则,但会返回特定的值。例如,number(2).add(3)将返回5。
非常感谢任何建议。
提前感谢大家。 [x]
3个回答

6

让数字值像5这样的数值“可链接”的一种方法是在适当的原型对象上定义一个方法,例如Number.prototype。例如:

Number.prototype.add = function (n) {
   return this + n
}

(5).add(2) // 7
5.0.add(2) // 7
5..add(2)  // 7
((5).add(2) + 1).add(34) // okay! 42

以上语法很有趣,因为5.add(2)是无效的:JavaScript在5.后面期望一个数字(或“nothing”)。由于这是全局副作用(它将影响所有数字),应该注意避免意外的交互。
使“5”可链式化的唯一另一种方法是创建一个新的Number对象(5不是真正的Number实例,即使它使用了Number.prototype!)然后复制所需的方法。(我曾经认为这是唯一的另一种方法,但请参见KooiInc的答案-但我不确定从toString返回非字符串是否定义良好。)
function ops(a) {
  return {
    add: function(b) {
      var res = new Number(a + b) // important!
      var op = ops(res)
      res.add = op.add // copy over singletons
      return res
    }
  }
}
function number(a) {
  return ops(a)
}

number(5).add(2) + 1           // 8
(number(5).add(2) + 1).add(34) // error! add is not a function

然而,需要记住这会引入一些微妙的问题:

typeof 5                        // number
typeof new Number(5)            // object
5 instanceof Number             // false
new Number(5) instanceof Number // true

这就是为什么我们需要一个Number(在JavaScript中搜索SO的“primitives”):

x = 5
x.foo = "bar"
x.foo // undefined

此外,结合cwolves的回答,考虑以下内容:
function number (n) { 
  if (this === window) { // or perhaps !(this instanceof number)
    return new number(n)
  } else {
    this.value = n
  }
}

那么new number(2)number(2)都将被计算为一个新的数字对象。

number(2).value     // 2
new number(2).value // 2
number(2) instanceof number     // true
new number(2) instanceof number // true

愉快地编码。


+1. 这个代码可以正常工作,但有点复杂。我认为这是一种方便的方法,我从这个例子中学到了很多。我在这里有些困惑,res.add = op.add 这一行是不是把 ops(number) 中的 add 方法复制到了 res.add 中并返回它?如果是这样,那么如果你有多个方法需要链式调用怎么办?[x] - xx3004
1
@xx3004 许多框架都带有一个"extend"方法,可以一次性复制一堆属性。(因为手动复制res.add = op.add; res.subtract = op.subtract等很繁琐 :-) 然而,在该示例中,请注意每次调用op时,都会创建一组新的函数。虽然可能不是问题,但这会创建更多的垃圾和数据绑定在闭包中,而不是使用原型的方法。 - user166390

3

您有两个选择。您可以返回新的对象:

function number(a){
    return this instanceof number ? (this.value = a, this) : new number(a);
}

number.prototype = {
    valueOf : function(){
        return this.value;
    },
    add : function(b){
        return new number(this.val + b);
    },
    subtract : function(b){
        return new number(this.val - b);
    }
};

或者您可以修改现有的代码(大部分与上面的代码相同,这里有所区别):
add : function(b){
    this.value += b;
    return this;
},

它们之间的区别在于它们的行为方式:

var x = new number(5),
    y = x.add(10);

// with first example
// x == 5, y == 15


// with 2nd example
// x == 15, y == 15, x === y

嗨,cwolves。现在它可以工作了。但我想知道是否有其他方法来链接函数(就像jQuery一样)并返回特定的值而不创建新对象?我的意思是x = number(2).add(2).add(3)而不是x = new number(2); x.add(2).add(3)?[x] - xx3004
@xx3004 - 请将构造函数中的返回语句修改为:return this instanceof number ? this : new number(a);。修改后的示例代码如下。 - user578895
@cwolves 我会把检查移到函数顶部,以避免在“非新建”情况下重复工作(即使在这个例子中这么微不足道)。这还可以保持意图不会“隐藏”在单个尾随三元条件中 :) - user166390
@cWolves 啊,我一直在看评论,没有看更新的代码。我只是觉得更新后的代码“太聪明了” :) - user166390
@pst - 我以前也有人批评我使用逗号的方式 :) 无论如何,我接受了这个观点。如果构造函数实际上很复杂,将if(!this instanceof number){ return new number(a); }放在构造函数顶部会更有意义。 - user578895
奇怪,当我运行number(4).add(2).add(4)时,你的示例代码只返回一个[Object object]而不是特定的值。我仍在努力使其正常工作,因为它来自于我的原始链式模拟,相比其他方式,我可以更轻松地编辑我的代码。 [x] - xx3004

3
如果您将值定义为属性(this.a),并在返回的对象中使用toString,则可以链接方法:
function number(a) {
    return {
        a: Number(a) || 0, //if not a, or a===NaN, default = 0
        add: function(b) {
            this.a += b;
            return this;
        },
        subtract: function(b){
            this.a -= b;
            return this;
        },
        valueOf: function(){
          return Number(this.a);
        },
        toString: this.valueOf
    }
}

var n = number(5);
alert(number.add(5).add(2).subtract(2)); //=> 10
alert(number.add(0.5));                  //=> 10.5
alert(number(2).add(5).add(3 * 12).subtract(Math.random());
                                         //=> 42.36072297706966

+1 Clever(number(5) + 3的情况应该在上面显示,结果为8——至少在FireFox 4中是这样)。您知道从toString返回非字符串是否被定义良好吗? - user166390
应该使用valueOf而不是toString,根据我的示例 :) - user578895
@所有人:实际上只使用 valueOf 是不行的,但是你可以强制使用 toString 来使用 valueOf - 请看我的编辑。这是一篇关于对象转换为原始类型的好文章:http://www.adequatelygood.com/2010/3/Object-to-Primitive-Conversions-in-JavaScript - KooiInc
这个程序按照我的预期完美地运行了,但我仍然不明白为什么toString: this.valueOf这一行非常重要?我甚至没有看到你调用它,那它有什么作用呢?[x] - xx3004
@xx3004 非常好的链接文章。由于某种原因,我以为那些(转换函数)只是在内部定义...我真傻。 - user166390
每个对象(实际上据我所知,布尔值)都有toString函数,我想那行代码是用来覆盖它的?但我不确定,仍在寻找答案,那行代码 toString: this.valueOf 是做什么的?[x] - xx3004

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接