一点javascript元编程&可链接的设置器

a bit of javascript metaprogramming & chainable setters

本文关键字:链接 设置 amp javascript 编程 一点      更新时间:2023-09-26

我需要"可链接"的setter,允许您执行以下操作:

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时,this不会绑定到正确的值。

尽管我尽了最大的努力把.bind(this)洒在所有合适的地方,但我仍然没有让它发挥作用。这应该相对简单,但我缺少什么?

更新

这里有一些富有想象力和优雅的答案,我仍然使用Bryan Chen的答案,因为之前有一个默契的原因:我的setter和getter需要做的不仅仅是引用对象属性。例如,在我的实际应用程序中,我对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()) 

接受的答案已经很好了,但我将尝试更进一步。有时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()函数采用keymethod,确定方法签名中的参数数量,并将其添加到key给出的方法名称的可用签名中。如果在指定的函数中已经有一个包含大量参数的签名,overload()将抛出一个错误。如果调用了具有未声明签名的函数,它也会引发错误。

否则,setter将正确地链式(假设它们不返回任何内容),getter将正确返回它们的值。

更新

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

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

这个怎么样?

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'

链接到repl

edit:简化版。感谢@Patrick Roberts

我参加聚会也迟到了,但我觉得这是个有趣的问题。这里有一个解决方案,可以为对象中的所有属性动态创建可链接的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];
    };
});

完整的例子。

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

我曾考虑在不使用新的Proxy对象显式创建所有getter和setter的情况下创建此功能,但无法实现。也许你会有更多的运气!