一些 javascript 元编程和可链接的设置器

a bit of javascript metaprogramming & chainable setters

我需要 'chainable' 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 向导都知道,它不会起作用:当 gettersetter 被调用。

尽管我尽了最大努力将 .bind(this) 洒在所有正确的地方,但我仍然没有让它发挥作用。这个应该比较简单,但是我错过了什么?

更新

这里有一些富有想象力和优雅的答案,我仍然使用 出于先前默认的原因:我的 setters 和 getters 需要做的不仅仅是简单地引用对象属性。例如,在我的实际应用程序中,我对 createGetterSetter 的调用如下所示:

Command.prototype.messageType = utils.createGetterSetter(
    function() { return messageType(this._payload).toString(); },
    function(str) { messageType(this._payload).write(str); });

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

如您所知,问题是 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()) 

这个怎么样?

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

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

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。如果指定函数中已经存在具有参数数量的签名,overload() 将抛出错误。如果调用带有未声明签名的函数,它也会抛出错误。

否则,setters 正确链接(假设他们没有 return 任何东西)并且 getters return 他们的值正确。

更新

根据要求,这是 Chain() 的一个版本,作为混合而不是超级 class:

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);
      }
    }
  };
}

用法:

Shoes.prototype.overload = chain();

我也迟到了,但我认为这是一个有趣的问题。这是一个为对象中找到的所有属性动态创建可链接 getters 和 setters 的解决方案。您可以轻松地将其限制为只为以 _ 开头的属性制作它们,但为了简单起见,这会完成所有这些。

您只需要:

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").[=20= 来检索]

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