方法链接和/或值缓存

时间:2014-09-07 08:23:43

标签: javascript

假设我有一个构造函数和一些实例方法,比如

function MyClass(name) {
  this.name = name || '';
}

MyClass.prototype = {
  constructor: MyClass,
  isEmptyName: function() {
    return this.name === '';
  }
}

现在我可以写

var myClass = new MyClass('Ben');
myClass.isEmptyName();

会返回false。现在,如果我再做一个方法,那么还会返回一个布尔值

MyClass.prototype = {
  constructor: MyClass,
  isEmptyName: function() {
    return this.name === '';
  }
  longerThan: function(len) {
    return this.name.length > len;
  }
}

我想像这样链接这些方法(不知何故,这就是我的问题:))

myClass.isEmptyName().and.longerThan(2);

现在省略'.and。'部分。我希望上层语句最终返回一个值

false&&是的 - >假

或更现实的样本:

myClass.notEmptyName().and.longerThan(4);

总结我的问题,我会说,我希望我的方法返回一个布尔值,如果它们被称为'直接'myClass.notEmptyName()应该返回true,但工作就像我写在样本中,否则。

其他图书馆以某种方式做到这一点,但我无法猜测,npm的should是一个很好的例子:

user.should.have.property('pets').with.lengthOf(4);
user.pets.should.be.instanceof(Array).and.have.lengthOf(4);

由于

5 个答案:

答案 0 :(得分:2)

那是不可能的。一个方法不能返回布尔值或者是可链接的,这取决于它以后的使用方式,因为它不知道以后如何使用它。

您可以链接以不同方式验证对象的方法,但如果要在表达式中使用它,则需要通过读取属性或调用方法来获取结果:

function MyClass(name) {
    this.name = name;
    this.and = this;
}

MyClass.prototype = {
    value: true,
    isNotEmpty: function() {
        this.value = this.value && this.name.length > 0; return this;
    },
    isLongerThan: function(len) {
        this.value = this.value && this.name.length > len; return this;
    },
    evaluate: function() {
        return this.value;
    }
};

console.log(new MyClass('Adam').isLongerThan(2).evaluate());
console.log(new MyClass('Bob').isNotEmpty().and.isLongerThan(3).evaluate());

演示:http://jsfiddle.net/Guffa/62e8dLwL/

编辑:

要允许多次评估,您可以重置evaluate方法中的值:

    evaluate: function() {
        var v = this.value;
        this.value = true;
        return v;
    }

答案 1 :(得分:2)

当然,你可以做到。我们将定义一个名为ChainResult的新中间状态对象,它记住底层对象,当前值和挂起操作(用于组合下一个测试的函数)。我们给这个对象一个valueOf方法,这样当JS试图将它作为一个原语进行评估时,它看起来就像#34;喜欢它有价值。为了使这项工作,事实证明ChainResult实际上需要是一个函数,因此我们将必要的属性从函数中挂起。

function ChainResult(obj, val) {
    function x() { }
    x.obj = obj;
    x.val = val;
    x.op = null;

    // the `valueOf` function spits out the current value when the object is evaluated
    x.valueOf = function() { return this.val; };

    // the test functions combine the results with the current value
    // using the current operation as set by a preceding `and` or `or`
    x.isEmptyName = function() { 
        x.val = x.op(x.val, x.obj._isEmptyName()); 
        return this; 
    };
    x.isLongerThan = function(len) { 
        x.val = x.op(x.val, x.obj._isLongerThan(len)); 
        return this; 
    };

    // we implement `and` and `or` via getters which set the operation
    // on the ChainResult object, and return `this` so we can keep chaining
    Object.defineProperties(x, {
        and: {
            get: function() { x.op = function(a,b) { return a && b; }; return x; }
        },
        or: {
            get: function() { x.op = function(a,b) { return a || b; }; return x; }
        }
    });

    return x;
}

MyClass定义需要稍微调整一下:

function MyClass(name) {
    this.name = name || '';
}

MyClass.prototype = {
    constructor: MyClass,

    // we implement the testers as pseudo-private functions
    _isEmptyName: function() { return this.name === ''; },
    _isLongerThan: function(len) { return this.name.length > len; },

    // when the public tester functions are invoked directly on the object
    // (when they are the first link in the chain), we construct and return a 
    // ChainResult object with the initial value set correctly
    isEmptyName: function() { return ChainResult(this, this._isEmptyName()); },
    isLongerThan: function(len) { return ChainResult(this, this._isLongerThan(len)) }
};

流速:

new MyClass('Bob')                 // create MyClass object
    .isEmptyName()                 // create ChainResult object with value `false`
    .or                            // remember `or` operation in ChainResult object
    .isLongerThan(2)               // update value of ChainResult object
;                                  // JS tries to convert to scalar, calls valueOf
// true

这需要防弹和收紧,但你明白了。

答案 2 :(得分:1)

  

我希望我的方法返回一个布尔值,如果它们被称为“直接”myClass.notEmptyName()应该返回true

您的方法总是直接在实例上调用,并且总是需要返回一个原始的布尔值。这样,上下文(myClass)就会丢失,你不能在结果上使用and方法(或属性获取器)。

我建议你看一下函数式编程,部分应用程序和currying,这对于像这样的流畅接口有很大帮助。给定

function and(fn1, fn2) {
    return function(val) {
        return fn1(val) && fn2(val);
    };
}
function or(fn1, fn2) {
    return function(val) {
        return fn1(val) || fn2(val);
    };
}

function hasEmptyName: function(val) {
    return val.name === '';
}
function hasNameLongerThan: function(len) {
    return function(val) {
        return val.name.length > len;
    };
}
你可以写

and(hasEmptyName, hasNameLongerThan(2))(myClass);

然而,使这些函数方法变得复杂。也许是这样的:

function method(name) {
    var args = Array.prototype.slice.call(arguments, 1);
    return function(instance) {
        return instance[name].apply(instance, args);
    };
}
Function.prototype.and = function (fn2) {
    var fn1 = this;
    return function(val) {
        return fn1(val) && fn2(val);
    };
}
Function.prototype.or = function (fn2) {
    var fn1 = this;
    return function(val) {
        return fn1(val) || fn2(val);
    };
}
Object.defineProperty(Object.prototype, "test", function(pred) {
    return pred(this);
});

现在你可以写

myClass.test(method("notEmptyName").and(method("longerThan", 4)));

答案 3 :(得分:0)

对于我自己的问题,这是一个答案(更好地称之为结果):

最后我基于这个帖子中的响应推出了另一个解决方案(谢谢大家!),因为原始问题无法解决,因为javascript运行时无法找到返回值,或返回链接时本身(对象)。解释很乱,对不起:(

查看我的lib,我的方法如下:

check(value)。 isString()等..

最初我想将这些链接起来check(value).isString().and.not.empty(),但这样,无法完成。 (挑战我)

最后我创建了用于链接的标记,而不是

check(value).isString().and.not.empty()

我可以写

check(value).isstring.and.not.empty.test()

不好,但仍然是。

check.js

要查看,请访问github repo上的 checkjs 注意: README已过时。

答案 4 :(得分:-2)

如果使用promises,则可以编写返回值并可以链接的函数。请注意,这些都是异步的。

找到这个JS插件:Kris Owal's Q,或者如果你想使用JS库,它们通常包含延迟对象和承诺。