发布于 2026-01-06 8 阅读
0

JavaScript 中的方法链是什么?它是如何工作的?以及如何使用它?

JavaScript 中的方法链是什么?它是如何工作的?以及如何使用它?

方法链是一种流行的编程技巧,可以帮助你编写更简洁、更易读的代码。在本教程中,你将学习 JavaScript 中的方法链是什么以及它的工作原理。你还将学习如何使用方法链来提高代码的质量和可读性。

JavaScript 方法链简介

你用过像 jQuery 这样的库吗?如果是,那你可能见过类似的情况。这里指的是在同一行中,两个或多个方法以层叠的方式依次调用。如今,这种做法在纯 JavaScript 中也很常见。你可以在数组、字符串和 Promise 中看到它。

在所有这些情况下,流程都相同。首先,引用你要操作的对象。其次,使用所需的方法。但是,不是单独使用这些方法,而是按顺序使用。你实际上是将它们串联起来。让我们来看一些例子来说明这一点。

方法链示例

假设你想操作一个字符串。有两种方法可以实现。第一种方法不使用方法链。这需要你分别对字符串调用每个方法,并且每次都需要引用该字符串。第二种方法是使用方法链。

在这种情况下,你需要依次使用所有需要的字符串方法。你可以用一行代码完成,也可以用多行代码,这取决于你的需求。此外,你只需要在代码开头引用一次字符串。结果相同,但代码量却截然不同。

// Method chaining with string.
let myStr = ' - Hello-world. '

// Without method chaining:
myStr = myStr.toLowerCase()
myStr = myStr.replace(/-/g, ' ')
myStr = myStr.trim()

// With method chaining:
myStr = myStr.toLowerCase().replace(/-/g, ' ').trim()

// Alternative with method chaining and multiple lines:
myStr = myStr
  .toLowerCase()
  .replace(/-/g, ' ')
  .trim()

// Log the value of "myStr" variable.
console.log(myStr)
// Output:
// 'hello world.'
Enter fullscreen mode Exit fullscreen mode

如果你有一个数组,并且想用几个数组方法来操作它,情况也是如此。你也可以选择这两种方法:一种是不使用方法链,另一种是使用方法链,代码更简洁。就像处理字符串一样,结果是一样的,只是代码量不同。

// Method chaining with array.
let myArray = [1, 7, 3, null, 8, null, 0, null, '20', 15]

// Without method chaining:
myArray = myArray.filter(el => typeof el === 'number' && isFinite(el))
myArray = myArray.sort((x, y) => x - y)

// With method chaining:
myArray = myArray.filter(el => typeof el === 'number' && isFinite(el)).sort((x, y) => x - y)

// Alternative with method chaining and multiple lines:
myArray = myArray
  .filter(el => typeof el === 'number' && isFinite(el))
  .sort((x, y) => x - y)

// Log the value of "myArray" variable.
console.log(myArray)
// Output:
// [ 0, 1, 3, 7, 8 ]
Enter fullscreen mode Exit fullscreen mode

Promise 是一个很好的例子,因为它们几乎必须通过方法链才能正常工作。首先,你需要创建一个 Promise。然后,你需要添加相应的处理函数。这些处理函数对于处理 Promise 解析后获取的值至关重要。当然,除非你使用async 函数await关键字。

// Create a Promise
const myPromise = new Promise((resolve, reject) => {
  // Create a fake delay
  setTimeout(function() {
    // Resolve the promise with a simple message
    resolve('Sorry, no data.')
  }, 1000)
})

// With method chaining:
myPromise.then((data) => console.log(data)).catch(err => console.log(error))

// Alternative with method chaining and multiple lines:
myPromise
  .then((data) => console.log(data))
  .catch(err => console.log(error))
// Output:
// 'Sorry, no data.'
Enter fullscreen mode Exit fullscreen mode

JavaScript 中的方法链是如何运作的

你已经了解方法链的运作方式。更重要的问题是,它是如何工作的?答案很简单。它之所以有效,是因为 `this`关键字this。没错,我们说的就是那个臭名昭著的`this`关键字。关于 ` this`,this很多东西值得学习。为了保持本教程的简洁,我们先不深入探讨,而是用最简单的方式来讲解。

假设你有一个对象。如果你this在该对象内部使用 `get()` 方法,它指向的仍然是该对象本身。如果你随后创建该对象的一个​​实例(或副本),`get()` 方法this指向的仍然是该实例(或副本)。当你使用字符串或数组方法时,你实际上是在操作一个对象。

const myObj = {
  name: 'Stuart',
  age: 65,
  sayHi() {
    // This here refers to myObj
    return `Hi my name is ${this.name}.`
  },
  logMe() {
    console.log(this)
  }
}

myObj.sayHi()
// Output:
// 'Hi my name is Stuart.'

myObj.logMe()
// Output:
// {
//   name: 'Stuart',
//   age: 65,
//   sayHi: ƒ,
//   logMe: ƒ
// }
Enter fullscreen mode Exit fullscreen mode

如果是字符串,你处理的是基本数据类型。然而,你使用的方法(例如 `get()`)toLowerCase()存在于对象的原型上String。仅仅在某个对象上添加一个新方法并不足以实现链式调用。还有一个关键要素,那就是对象本身this

要使链式调用生效,方法必须返回它所操作的对象。它必须返回一个对象this。可以把它想象成接力棒。场上有一些跑者,位置各不相同。但是,他们不能同时跑。一次只能有一个人跑。当前正在跑的跑者完成他的部分后,必须将接力棒传递给下一个跑者。

只有当这种情况发生时,也就是下一位跑者接到接力棒时,他才能跑自己的部分。在我们的例子中,每个方法都代表一位跑者。接力棒会被返回this,也就是该方法正在操作的对象。如果没有接力棒,就没有this返回值,下一位跑者就无法接力,链式调用也就无法工作。

如何在 JavaScript 中实现方法链

以上是理论部分。现在,让我们来实践一下。要实现链式调用,你需要三样东西。首先,你需要一个对象。其次,这个对象需要一些你可以稍后调用的方法。第三,这些方法必须返回对象本身。this如果你想让它们可以链式调用,它们就必须返回对象本身。

让我们创建一个简单的对象来比喻人。这个人将具有几个属性:状态nameage兴趣和state兴趣。状态state将指定这个人当前处于什么状态。要改变这个状态,将有几个方法:改变walk()切换、sleep()改变改变兴趣eat()drink()work()exercise()

由于我们希望所有这些方法都能链式调用,因此它们最终都必须返回结果this。此外,还有一个实用方法。该方法会将当前状态记录到控制台。当您使用某个方法更改角色状态时,它也会调用此方法,以便您可以在控制台中看到新的状态。

// Create person object.
const person = {
  name: 'Jack Doer',
  age: 41,
  state: null,
  logState() {
    console.log(this.state)
  },
  drink() {
    // Change person's state.
    this.state = 'Drinking.'

    // Log current person's state.
    this.logState()

    // Return this to make the method chainable.
    return this
  },
  eat() {
    // Change person's state.
    this.state = 'Eating.'

    // Log current person's state.
    this.logState()

    // Return this to make the method chainable.
    return this
  },
  exercise() {
    // Change person's state.
    this.state = 'Exercising.'

    // Log current person's state.
    this.logState()

    // Return this to make the method chainable.
    return this
  },
  sleep() {
    // Change person's state.
    this.state = 'Sleeping.'

    // Log current person's state.
    this.logState()

    // Return this to make the method chainable.
    return this
  },
  walk() {
    // Change person's state.
    this.state = 'Walking.'

    // Log current person's state.
    this.logState()

    // Return this to make the method chainable.
    return this
  },
  work() {
    // Change person's state.
    this.state = 'Working.'

    // Log current person's state.
    this.logState()

    // Return this to make the method chainable.
    return this
  }
}

// Let's have some fun.
person
  .drink() // Output: 'Drinking.'
  .exercise() // Output: 'Exercising.'
  .eat() // Output: 'Eating.'
  .work() // Output: 'Working.'
  .walk() // Output: 'Walking.'
  .sleep() // Output: 'Sleeping.'

// Alternative on a single line:
person.drink().exercise().eat().work().walk().sleep()
// Output:
// 'Drinking.'
// 'Exercising.'
// 'Eating.'
// 'Working.'
// 'Walking.'
// 'Sleeping.'
Enter fullscreen mode Exit fullscreen mode

方法、链式调用、this 函数和箭头函数

使用箭头函数this也意味着一件事:你不能用箭头函数创建链式方法。原因是,在箭头函数中,`this`this并不绑定到对象实例,而是this指向全局对象。window如果你尝试返回`this`,this它返回的是全局对象window,而不是对象本身。

另一个问题是如何在箭头函数内部访问和修改对象属性。由于 `wuck`this是全局对象,window你不能直接引用该对象及其属性。你实际上是在尝试引用 `wuck` 本身window及其属性。

如果你坚持使用箭头函数,有一种方法可以绕过这个问题。this你不能再使用 `{{` 来引用对象,而应该直接使用对象名来引用。你需要将this箭头函数内部所有出现的 `{{` 替换为对象名。

// Create person object.
const person = {
  name: 'Jack Doer',
  age: 41,
  state: null,
  logState() {
    console.log(this.state)
  },
  drink: () => {
    person.state = 'Drinking.'

    person.logState()

    return person
  },
  eat: () => {
    person.state = 'Eating.'

    person.logState()

    return person
  },
  exercise: () => {
    person.state = 'Exercising.'

    person.logState()

    return person
  },
  sleep: () => {
    person.state = 'Sleeping.'

    person.logState()

    return person
  },
  walk: () => {
    person.state = 'Walking.'

    person.logState()

    return person
  },
  work: () => {
    person.state = 'Working.'

    person.logState()

    return person
  }
}

// Let's have some fun.
person
  .drink() // Output: 'Drinking.'
  .exercise() // Output: 'Exercising.'
  .eat() // Output: 'Eating.'
  .work() // Output: 'Working.'
  .walk() // Output: 'Walking.'
  .sleep() // Output: 'Sleeping.'
Enter fullscreen mode Exit fullscreen mode

这样做的一个潜在缺点是你会失去所有灵活性。如果你复制对象,所有箭头函数仍然会硬编码到原始对象上。如果你同时使用 ` Object.assign()``Object.create()`创建副本,就会发生这种情况。

// Create original person object.
const person = {
  name: 'Jack Doer',
  age: 41,
  state: null,
  logState() {
    // Log the whole object.
    console.log(this)
  },
  drink: () => {
    person.state = 'Drinking.'

    person.logState()

    return person
  },
  eat: () => {
    person.state = 'Eating.'

    person.logState()

    return person
  }
}

// Let person eat.
person.eat()
// Output:
// {
//   name: 'Jack Doer',
//   age: 41,
//   state: 'Eating.',
//   logState: ƒ,
//   drink: ƒ,
//   eat: ƒ
// }

// Create new object based on person object.
const newPerson = new Object(person)

// Change the "name" and "age" properties.
newPerson.name = 'Jackie Holmes'
newPerson.age = 33

// Let newPerson drink.
// This will print Jack Doer not Jackie Holmes.
newPerson.drink()
// Output:
// {
//   name: 'Jack Doer',
//   age: 41,
//   state: 'Drinking.',
//   logState: ƒ,
//   drink: ƒ,
//   eat: ƒ
// }
Enter fullscreen mode Exit fullscreen mode

但是,如果您使用Object()构造函数,就不会出现上述问题。如果您使用Object()带有new关键字的构造函数,您将创建一个独立的副本作为新对象。当您对该副本使用某些方法时,该方法只会影响该副本,而不会影响原始对象。

// Create original person object.
const person = {
  name: 'Jack Doer',
  age: 41,
  state: null,
  logState() {
    // Log the whole object.
    console.log(this)
  },
  drink: () => {
    person.state = 'Drinking.'

    person.logState()

    return person
  },
  eat: () => {
    person.state = 'Eating.'

    person.logState()

    return person
  }
}

// Let person eat.
person.eat()
// Output:
// {
//   name: 'Jack Doer',
//   age: 41,
//   state: 'Eating.',
//   logState: ƒ,
//   drink: ƒ,
//   eat: ƒ
// }

// Create new object based on person object.
const newPerson = new Object(person)

// Change the "name" and "age" properties.
newPerson.name = 'Jackie Holmes'
newPerson.age = 33

// Let newPerson drink.
newPerson.drink()
// Output:
// {
//   name: 'Jackie Holmes',
//   age: 33,
//   state: 'Drinking.',
//   logState: ƒ,
//   drink: ƒ,
//   eat: ƒ
// }
Enter fullscreen mode Exit fullscreen mode

所以,如果你坚持要使用箭头函数,并且想要复制对象?最好使用Object()构造函数和new关键字来创建这些副本。否则,省去这些麻烦,直接使用普通函数就好。

方法链和类

喜欢JavaScript 类吗?如果是,那我有个好消息要告诉你。即使你更喜欢使用类,也可以在 JavaScript 中使用方法链。过程与对象类似,只是语法略有不同。重要的是,每个可以链式调用的方法都必须返回一个值this

// Create Person class.
class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
    this.state = null
  }

  logState() {
    console.log(this.state)
  }

  drink() {
    this.state = 'Drinking.'

    this.logState()

    return this
  }

  eat() {
    this.state = 'Eating.'

    this.logState()

    return this
  }

  sleep() {
    this.state = 'Sleeping.'

    this.logState()

    return this
  }
}

// Create instance of Person class.
const joe = new Person('Joe', 55)

// Use method chaining.
joe
  .drink() // Output: 'Drinking.'
  .eat() // Output: 'Eating.'
  .sleep() // Output: 'Sleeping.'
Enter fullscreen mode Exit fullscreen mode

结论:JavaScript 中的方法链是什么,它是如何工作的,以及如何使用它

方法链是一种简单却非常实用的方法。它可以帮助你编写更简洁、更易读的代码。希望这篇教程能帮助你理解 JavaScript 中的方法链是什么以及它的工作原理。现在,就看你如何将所学的方法链知识运用到你的代码中了。

文章来源:https://dev.to/alexdevero/what-method-chaining-in-javascript-is-how-it-works-and-how-to-use-it-53dd