一点javascript元编程&可连接的二传手

时间:2016-05-18 01:30:18

标签: javascript metaprogramming fluent

我需要可连接的' setters,允许你做以下事情:

cool_shoes = new Shoes().color('glitter').style('platform')
console.log(cool_shoes.color()) // => 'glitter'

但我已经厌倦了一遍又一遍地编写相同的getter和setter代码,以及:

function Shoes() { this._color = null; this._style = null; }
Shoes.prototype.constructor = Shoes;

Shoes.prototype.color = function(arg) {
    if (arguments.length === 0) {
        return this._color;  // _slot accessor
    } else {
        this._color = arg;   // _slot setter
        return this;
    };
};

Shoes.prototype.style = function(arg) {
    if (arguments.length === 0) {
        return this._style;  // _slot accessor
    } else {
        this._style = arg;   // _slot setter
        return this;
    };
};

我的意思是,这很有效,但是当你应该能够做类似的事情时,它会重复很多:

function createGetterSetter(getter, setter) {
    return function(arg) {
        if (arguments.length === 0) {
            return getter();
        } else {
            setter(arg);
            return this;
        };
    };
};

然后像这样使用它:

Shoes.prototype.color = createGetterSetter(
    function() { return this._color; },
    function(arg) { this._color = arg; });

Shoes.prototype.style = createGetterSetter(
    function() { return this._style; },
    function(arg) { this._style = arg; });

当然,正如任何勤奋的javascript向导所知道的那样,它不会起作用:thisgettersetter.bind(this)时不会被绑定到正确的值被称为。

尽管我尽最大努力将createGetterSetter洒在所有正确的地方,但我仍然无法将其付诸实践。这应该相对简单,但我缺少什么?

更新

这里有一些富有想象力和优雅的答案,由于以前的默认原因,我仍然会使用Bryan Chen's answer:我的setter和getter需要做的不仅仅是引用对象属性。例如,在我的实际应用程序中,我调用了Command.prototype.messageType = utils.createGetterSetter( function() { return messageType(this._payload).toString(); }, function(str) { messageType(this._payload).write(str); }); ,如下所示:

foreach

...所以在我的案例中,简单地获取或设置对象中的插槽的解决方案不会起作用。但是,谢谢大家的一些好答案!

4 个答案:

答案 0 :(得分:6)

如您所知,问题this未在gettersetter上设置,那么为什么不设置它?



function createGetterSetter(getter, setter) {
  return function(arg) {
    if (arguments.length === 0) {
      return getter.call(this); // pass this to getter
    } else {
      setter.call(this, arg); // pass this to setter
      return this;
    };
  };
};

function Shoes() { this._color = null; this._style = null; }
Shoes.prototype.constructor = Shoes;

Shoes.prototype.color = createGetterSetter(
    function() { return this._color; },
    function(arg) { this._color = arg; });

Shoes.prototype.style = createGetterSetter(
    function() { return this._style; },
    function(arg) { this._style = arg; });

var cool_shoes = new Shoes().color('glitter').style('platform')
document.write(cool_shoes.color()) 




答案 1 :(得分:1)

接受的答案已经很好,但我会尝试更进一步。有时getter和setter分别需要的不仅仅是0和1个参数,所以我把更全面的东西放在一起:



game.addMouseMotionListener(new MouseAdapter() {
    @Override
    public void mouseMoved(MouseEvent e) {
        statusbar.setText(String.format("Your mouse is at %d, %d", e.getX(), e.getY()));
    }

    public void mouseExited(MouseEvent e){
        ifPaused = true;
    }   

    public void mouseEntered(MouseEvent e){
        ifPaused = false;
    }
});




基本上我们将function Chain() { this._methods = {}; } Chain.prototype.overload = function(key, method) { if (typeof key !== 'string') { throw new TypeError('key must be a string.'); } if (typeof method !== 'function') { throw new TypeError('method must be a function.'); } let attr, len = method.length; if ((attr = ( this._methods.hasOwnProperty(key) && this._methods[key] ) || {} ) && attr[len]) { throw new RangeError(`method ${key} of length ${len} already exists.`); } attr[len] = method; if (!this._methods.hasOwnProperty(key)) { this._methods[key] = attr; this[key] = function (...args) { let method = this._methods[key][args.length]; if (typeof method !== 'function') { throw new ReferenceError(`method ${key} of length ${args.length} does not exist.`); } let value = method.apply(this, args); return (typeof value === 'undefined' ? this : value); } } }; function Shoes() { this._color = null; this._style = null;} Shoes.prototype = new Chain(); Shoes.prototype.overload('color', function() { return this._color; }); Shoes.prototype.overload('color', function(arg) { this._color = arg; }); Shoes.prototype.overload('style', function() { return this._style; }); Shoes.prototype.overload('style', function(arg) { this._style = arg; }); Shoes.prototype.overload('value', function() { return { color: this._color, style: this._style }; }); Shoes.prototype.overload('value', function(attr) { return this[attr](); }); Shoes.prototype.overload('value', function(attr, arg) { this[attr](arg); }); var cool_shoes = new Shoes().color('glitter').style('platform'); document.write(JSON.stringify(cool_shoes.value()));构造为Chain(),因此我们可以为Shoes.prototype的实例重载成员方法。

Shoes()函数采用overload()key,确定方法签名中的参数数量,并将其添加到方法名称的可用签名中由method给出。如果已存在具有指定函数中的参数数量的签名,则key将引发错误。如果调用具有未声明签名的函数,它也会抛出错误。

否则,setter链正确(假设他们没有返回任何东西)和getters正确返回它们的值。

更新

根据要求,这里有一个overload()版本作为mixin而不是超类提供:

Chain()

用法:

function chain() {
  let _methods = {};

  return function overload(key, method) {
    if (typeof key !== 'string') {
      throw new TypeError('key must be a string.');
    }
    if (typeof method !== 'function') {
      throw new TypeError('method must be a function.');
    }

    let attr, len = method.length;

    if ((attr = (
            _methods.hasOwnProperty(key) && _methods[key]
          ) || {}
        ) && attr[len]) {
      throw new RangeError(`method ${key} of length ${len} already exists.`);
    }

    attr[len] = method;

    if (!_methods.hasOwnProperty(key)) {
      _methods[key] = attr;

      this[key] = function (...args) {
        let method = _methods[key][args.length];

        if (typeof method !== 'function') {
          throw new ReferenceError(`method ${key} of length ${args.length} does not exist.`);
        }

        let value = method.apply(this, args);

        return (typeof value === 'undefined' ? this : value);
      }
    }
  };
}

答案 2 :(得分:0)

这个怎么样?

function ToyClass() { this._c = 0; }

ToyClass.prototype.constructor = ToyClass;

function addFn( CLASS, ident, uncurriedMutation ) {
    CLASS.prototype[ident] = function( ...args ) {
      uncurriedMutation( this ).apply(this, args )
      return this
    }
}

addFn(ToyClass, 'increase', function( instance ){
    return function(){
        (instance._c)++
    }
})

const testCase = new ToyClass().increase()

console.log(testCase._c) // will be '1'

Link to repl

编辑:Simplified version.谢谢@Patrick Roberts

答案 3 :(得分:0)

我也很晚才参加聚会,但我认为这是一个有趣的问题。这是一个为对象中的所有属性动态创建可链接的getter和setter的解决方案。您可以轻松地将其限制为仅为以_开头的属性设置它们,但为了简单起见,这将完成所有这些操作。

您只需要:

Object.keys(new Shoes()).forEach(field => {
    let name = field.slice(1); // Remove underscore

    Shoes.prototype[name] = function(val) {
        if (val) {
            this[field] = val;
            return this;
        }

        return this[field];
    };
});

Full example.

它在Shoes原型中创建了一个新函数,用作getter和setter。使用cool_shoes.color("red")会将_color属性设置为red,只需调用不带参数的函数(cool_shoes.color() === "red")即可检索。

我正在考虑创建此功能而不使用新的Proxy对象显式创建所有getter和setter,但无法弄清楚。也许你会有更多的运气!