-3

I have a code design question. Consider the following code:

thatObj.doThis().setThat().add().update();

To allow chaining, I'm often writing return this;, and sometimes, here or there I forget to do it, then I got an error.

In many cases, I'm not asking for a particular result (e.g. thatObj.getName() or thatObj.getChildren()), but instead wanting to do some updates or calling setters (e.g. thatObj.setName("foo") or thatObj.addChild(child) or thatObj.update()), I was wondering if it would be more convenient to return this; for any call of methods, I mean as a javascript default behaviour, and if not, what could be the reasons not to do so.

Joseph Merdrignac
  • 3,510
  • 2
  • 19
  • 16
  • 3
    A radical change to JavaScript behavior like that is unlikely to happen. – Pointy Oct 16 '17 at 13:08
  • 1
    Your best bet is to create a class that can be extended and provides the interface you desire – Fez Vrasta Oct 16 '17 at 13:09
  • a class ? but i'm talking about every methods on every objects, to avoid writing `return this`, could it be a good idea to say from now JS will always return `this` from any methods ? @Pointy, yes i think it too, but i was wandering what could be the drawback of such a design for JS. Btw for any object oriented programming languages. This is broad question, about performance, optimization etc. Maybe too broad to be answered in a few words. – Joseph Merdrignac Oct 16 '17 at 14:14
  • That would be based on the assumption that the default coding style for that general-purpose programming language would be a fluent (chained) style with a lot of side-effects. Some people choose to create chained interfaces. Not everyone does. Not all chained interfaces are based upon returning `this`, so even within the subset of people who wanted to create a chained interface, only a subset of them would benefit from this very idiosyncratic behavior. I almost never use `this` in JavaScript because of its weird behavior, so I would have absolutely no interest in such a default behavior. – JLRishe Oct 16 '17 at 15:12
  • Your question basically boils down to "would there be any downside to fundamentally changing a language to accommodate my personal coding style"? Yeah, you're not everyone. – JLRishe Oct 16 '17 at 15:14
  • @JLRishe You're right, i'm not everyone, and that's why i asked that question ! To see what others can think a such a shift in oo programming. It's not really a question of personal style. Chaining was massively popularized by jQuery, and i'm not the one that create jQuery! No sincerly, i'm thinking that it would be more logic / more object oriented to return a pointer to an instance for every methods of an instance. Why should we lose that pointer ? – Joseph Merdrignac Oct 16 '17 at 22:24
  • @JosephMerdrignac I think you'll find that an extremely small amount of the methods in the jQuery source return `this` so despite that being your example of a chained library, this change wouldn't really have helped in the creation of jQuery. Another point to make is that a `return this` default behavior would be all but useless (and counterproductive) in object-oriented languages that eschew side-effects (Scala, F#), or to people who aim to write side-effect free code in JavaScript. – JLRishe Oct 17 '17 at 04:12
  • @JLRishe You should be right for Scala, F#. Consider ThreeJS, in the source code we can find more than 2000 methods (including constructors) and 642 occurence of `return this`. More than 25%. Since JS does not allow operators overloading, the chaining pattern is very useful to make some arithmetic (e.g. : `let v = myCube.position.clone().add(u).normalize().transform(mat)`), it may explain that proportion. – Joseph Merdrignac Oct 17 '17 at 07:16

1 Answers1

1
  • JS returns undefined if you don't return something explicitely,
  • JS constructors return this unless your constructor returns an Object.
  • CoffeeScript returns the last expression by default,
  • You want this to be returned by default by all methods on an object

everybody has it's own opinion what's the "right" way to do it.

could it be a good idea to say from now JS will always return this from any methods ?

And from one moment to the other, at least 2/3 of the web will be broken. So, tell me, is it a good idea?

JS has set its rules a long time ago, and something that basic is not going to change (as Pointy already mentioned). So why don't you take care of that behaviour:

//extracted that from the function to avoid memory leakage
function wrapFunction(fn) {
  return function() {
    let result = fn.apply(this, arguments);
    return result === undefined ? this : result;
  }
}

//filter === false   => only own methods
//filter === true    => own methods and inherited methods
//filter is Array    => only the passed keys (if they are methods)
//filter is RegExp   => use the RegExp to filter the keys
//filter is function => regular filterFunction
function returnThisAsDefault(objectOrConstructor, filter = false) {
  if (objectOrConstructor !== Object(objectOrConstructor))
    throw new TypeError("Passed argument must be an object or a constructor. Got ", typeof objectOrConstructor);

  const validKey = key => typeof proto[key] === "function" && key !== "constructor" && key !== "prototype";

  let proto = typeof objectOrConstructor === "function" ?
    objectOrConstructor.prototype :
    objectOrConstructor;

  let filterFn = Array.isArray(filter) ? filter.includes.bind(filter) :
    filter === false || filter === true ? () => true :
    filter instanceof RegExp ? filter.test.bind(filter) :
    typeof filter === "function" ? filter :
    () => false;

  let wrapped = {};
  for (let p = proto, done = new Set(["constructor", "prototype"]); p != null && p !== Object.prototype;) {
    for (let key of Object.getOwnPropertyNames(p)) {
      if (typeof proto[key] !== "function" || done.has(key) || !filterFn.call(p, key)) continue;
      done.add(key);

      let d = Object.getOwnPropertyDescriptor(p, key);
      //typeof d.value !== "function" means that proto[key] contains a getter returning a function
      if (!d.writable && !d.configurable || typeof d.value !== "function") {
        console.log(`function ${JSON.stringify(key)} not fit to be wrapped`, d);
        continue;
      }

      d.value = wrapFunction(d.value);
      wrapped[key] = d;
    }

    if (filter === false) break;
    else p = Object.getPrototypeOf(p);
  }

  Object.defineProperties(proto, wrapped);
  return objectOrConstructor;
}

let thatObject = returnThisAsDefault({
  doThis() {
    console.log("doThis()");
  },

  setThat() {
    console.log("setThat()");
  },

  add() {
    console.log("add()");
  },

  update() {
    console.log("update()");
    return "success";
  },
});

let result = thatObject.doThis().setThat().add().update();

console.log("result: ", result);
.as-console-wrapper {
  top: 0;
  max-height: 100%!important
}
Thomas
  • 11,958
  • 1
  • 14
  • 23
  • I tried you code. Very nice, i never thought to wrap every methods after being declared. Thx – Joseph Merdrignac Oct 16 '17 at 21:54
  • Of course changing JS behavior right now wasn't a good idea. It was not my purpose to do this for real. I asked for what could be the disadvantages of such a paradigm. The benefits could be : Getting rid of var declaration / naming. Allow powerful inline scripts (in the manner of jQuery). Less redundant code... ... but may be less readable ? – Joseph Merdrignac Oct 16 '17 at 22:29