3

我认为我在方法链接方面缺少一些东西。对我来说感觉不完整。

方法链接的工作原理是让每个方法返回this,以便可以调用该对象上的另一个方法。但是,返回值是this函数的结果而不是函数的结果这一事实对我来说似乎很不方便。

这是一个简单的例子。

const Obj = {
    result: 0,
    addNumber: function (a, b) {
        this.result = a + b;
        return this;
    },

    multiplyNumber: function (a) {
        this.result = this.result * a;
        return this;
    },
}

const operation = Obj.addNumber(10, 20).multiplyNumber(10).result
console.log(operation)

关键点:

  1. 链中的每个方法都Obj.addNumber(10, 20).multiplyNumber(10)返回this.
  2. 链的最后一部分.result是返回除 之外的值的部分this

这种方法的问题在于,它要求您附加一个属性/方法才能在末尾获得一个值,而不是this.

将此与 JavaScript 中的内置函数进行比较。

const str = "  SomE RandoM StRIng  "

console.log(str.toUpperCase()) // "  SOME RANDOM STRING  "
console.log(str.toUpperCase().trim()) // "SOME RANDOM STRING"
console.log(str.toUpperCase().trim().length) // 18

关键点:

  1. 链中的每个函数都不会返回函数的结果this(也许这是在幕后完成的)
  2. 在链的末尾不需要任何属性/方法来获得结果。

我们可以实现方法链来表现 Javascript 中内置函数的行为方式吗?

4

4 回答 4

2

首先,您的每个 console.log 都没有正确返回:

console.log(str.toUpperCase.trim) //undefined

它返回 undefined 因为 str.toUpperCase 返回函数对象并且不执行函数本身所以它不起作用
唯一正确的用法是

console.log(str.toUpperCase().trim()

现在关于您的问题,在没有结果的情况下很容易做到,而且效率更高。
javascript 中的所有内容都有一个名为 valueOf() 的方法,这是我为数字调用类似的所有内容的示例,尽管我更喜欢只创建函数而不是对象。

const Obj = {
    addNumber: function (a = 0) {
        return a + this.valueOf();
    },

    multiplyNumber: function (a = 1) {
        return a*this.valueOf();
    },
}
const nr = 2;
Object.keys(Obj).forEach(method => {
    Number.prototype[method] = Obj[method];
})
console.log(Number.prototype); // will print out addNumber and multiplyNumber
// Now You can call it like this
console.log(nr.addNumber().multiplyNumber()); // Prints out 2 because it becomes (nr+0)*1
console.log(nr.addNumber(3).multiplyNumber(2)) // Prints out 10;

于 2021-10-16T13:53:30.687 回答
1

我认为您误解了方法链接实际上是什么。它只是调用多个方法而不将每个中间结果存储在变量中的简写。换句话说,这是一种表达方式:

const uppercase = " bob ".toUpperCase()
const trimmed = uppercase.trim()

像这样

const result = " bob ".toUpperCase().trim()

没有什么特别的事情发生。该trim方法只是在 的结果上调用" bob ".toUpperCase()。从根本上说,这归结为运算符优先级和操作顺序。运算符是一个访问器.,从左到右求值。这使得上面的表达式等同于这个(用于显示评估顺序的括号):

const result = (" bob ".toUpperCase()).trim()

无论每个单独的方法返回什么,都会发生这种情况。例如,我可以这样做:

const result = " bob ".trim().split().map((v,i) => i)

这相当于

const trimmed = " bob ".trim()
const array = trimmed.split() //Note that we now have an array
const indexes = array.map((v,i) => i) //and can call array methods

所以,回到你的例子。你有一个对象。该对象在内部封装了一个值,并向该对象添加了用于操作结果的方法。为了使这些方法有用,您需要不断返回具有这些方法可用的对象。最简单的机制是返回this。如果您实际上正在尝试使对象可变,这也可能是最合适的方法。但是,如果不变性是一个选项,则可以改为实例化要返回的新对象,每个对象都具有原型中所需的方法。一个例子是:

function MyType(n) {
  this.number = n
}
MyType.prototype.valueOf = function() {
  return this.number
}
MyType.prototype.add = function(a = 0) {
  return new MyType(a + this)
}
MyType.prototype.multiply = function(a = 1) {
  return new MyType(a * this)
}

const x = new MyType(1)
console.log(x.add(1))                 // { number: 2 }
console.log(x.multiply(2))            // { number: 2 }
console.log(x.add(1).multiply(2))     // { number: 4 }
console.log(x.add(1).multiply(2) + 3) // 7

关于这一点要注意的关键是您仍在使用您的对象,但是valueOf原型上的 允许您直接将number用作对象的值,同时仍然使方法可用。这在最后一个示例中显示,我们直接将 3 添加到它(不访问number)。this它通过直接添加到方法的数字参数在整个实现中得到利用。

于 2021-10-16T14:46:46.733 回答
0

方法链接是在同一对象的另一个方法上调用方法以获得更清晰和可读的代码的机制。

在 JavaScript 方法链接中,大多数使用this对象类中的关键字来访问其方法(因为this关键字指的是调用它的当前对象)

当某个方法返回 this 时,它只是返回返回它的对象的一个​​实例,所以换句话说,要将方法链接在一起,我们必须确保我们定义的每个方法都有一个返回值,以便我们可以调用另一个方法就可以了。

在上面的代码中,函数 addNumber 从函数调用返回当前执行的上下文。然后下一个函数在此上下文中执行(引用同一个对象),并调用与该对象关联的其他函数。这是这个链接工作的必要条件。函数链中的每个函数都返回当前的执行上下文。这些函数可以链接在一起,因为之前的执行返回的结果可以进一步处理。

这是 JavaScript 的魔力和独特性的一部分,如果您来自其他语言,如 Java 或 C#,它可能看起来很奇怪,但thisJavaScript 中的关键字的行为不同。

于 2021-10-16T14:00:33.930 回答
0

您可以避免使用带有-trap的Proxy 对象的必要性this 能够隐式返回值。get

在这里你可以找到一个更通用的工厂。

const log = Logger();

log(`<code>myNum(42)
  .add(3)
  .multiply(5)
  .divide(3)
  .roundUp()
  .multiply(7)
  .divide(12)
  .add(-1.75)</code> => ${
    myNum(42)
    .add(3)
    .multiply(5)
    .divide(3)
    .roundUp()
    .multiply(7)
    .divide(12)
    .add(-1.75)}`,
  );

log(`\n<code>myString(\`hello world\`)
  .upper()
  .trim()
  .insertAt(6, \`cruel coding \`)
  .upper()</code> => ${
    myString(`hello world`)
        .upper()
        .trim()
        .insertAt(6, `cruel coding `)
        .upper()
    }`);

log(`<br><code>myString(\`border-top-left-radius\`).toUndashed()</code> => ${
  myString(`border-top-left-radius`).toUndashed()}`);

// the proxy handling
function proxyHandlerFactory() {
  return {
    get: (target, prop) => {
      if (prop && target[prop]) {
        return target[prop];
      } 
      return target.valueOf;
    }
  };
}

// a wrapped string with chainable methods
function myString(str = ``) {
  const proxyHandler = proxyHandlerFactory();
  const obj2Proxy = {
    trim: () => nwProxy(str.trim()),
    upper: () => nwProxy(str.toUpperCase()),
    lower: () => nwProxy(str.toLowerCase()),
    insertAt: (at, insertStr) => 
      nwProxy(str.slice(0, at) + insertStr + str.slice(at)),
    toDashed: () => 
      nwProxy(str.replace(/[A-Z]/g, a => `-${a.toLowerCase()}`.toLowerCase())),
    toUndashed: () => nwProxy([...str.toLowerCase()]
      .reduce((acc, v) => {
        const isDash = v === `-`;
        acc = { ...acc,
          s: acc.s.concat(isDash ? `` : acc.nextUpcase ? v.toUpperCase() : v)
        };
        acc.nextUpcase = isDash;
        return acc;
      }, {
        s: '',
        nextUpcase: false
      }).s),
    valueOf: () => str,
  };

  function nwProxy(nwStr) {
    str = nwStr || str;
    return new Proxy(obj2Proxy, proxyHandler);
  }

  return nwProxy();
}

// a wrapped number with chainable methods
function myNum(n = 1) {
  const proxyHandler = proxyHandlerFactory();
  const obj2Proxy = {
    add: x => nwProxy(n + x),
    divide: x => nwProxy(n / x),
    multiply: x => nwProxy(n * x),
    roundDown: () => nwProxy(Math.floor(n)),
    roundUp: () => nwProxy(Math.ceil(n)),
    valueOf: () => n,
  };

  function nwProxy(nwN) {
    n = nwN || n;
    return new Proxy(obj2Proxy, proxyHandler);
  }

  return nwProxy();
}

// ---- for demo ---- //
function Logger() {
  const report =
    document.querySelector("#report") ||
    document.body.insertAdjacentElement(
      "beforeend",
      Object.assign(document.createElement("pre"), {
        id: "report"
      })
    );

  return (...args) => {
    if (!args.length) {
      return report.textContent = ``;
    }

    args.forEach(arg =>
      report.insertAdjacentHTML(`beforeEnd`,
        `<div>${arg.replace(/\n/g, `<br>`)}</div>`)
    );
  };
}
body {
  font: 12px/15px verdana, arial;
  margin: 0.6rem;
}

code {
  color: green;
}

于 2021-10-16T13:48:58.220 回答