淺析 JavaScript 中的方法鏈

方法鏈是一種流行的編程方法,能夠幫助你寫出更簡潔易讀的代碼。在本文中咱們一塊兒學習 JavaScript 中的方法鏈是什麼,以及它是怎樣工做的。另外咱們還會探討如何使用方法連接來提升代碼的質量和可讀性。javascript

JavaScript 中方法鏈

你必定曾經用過 jQuery 之類的庫,可能看到過相似的東西。在進行級聯時主要有兩種方法:一種是一個接一個的執行方法,另外一種是在同一行上。在純 JavaScript 中這種作法也很廣泛。你能夠在數組、字符串和 promise 看到它。前端

在這些狀況下全部的過程都是相同的。首先引用要使用的對象。而後根據須要使用多種方法。但不是單獨使用這些方法,而要一個接一個地使用。基本上是把它們連接在一塊兒。先看一些例子。java

方法鏈的例子

在處理字符串時有兩種方法。第一個種不用方法鏈,這要求必須在字符串上分別使用每一個方法,這樣必須每次都引用這個字符串。程序員

第二種方式是用方法鏈。這時能夠用全部想要的字符串方法。寫出的代碼也能夠是單行或多行,這取決於你的習慣。並且只須要引用一次字符串。儘管結果相同,可是代碼量卻有很大的差別。面試

// 在字符串上使用方法鏈的例子
let myStr = ' - Hello-world. '

// 不用方法鏈:
myStr = myStr.toLowerCase()
myStr = myStr.replace(/-/g, ' ')
myStr = myStr.trim()

// 用方法鏈:
myStr = myStr.toLowerCase().replace(/-/g, ' ').trim()

// 多行方法鏈的寫法:
myStr = myStr
  .toLowerCase()
  .replace(/-/g, ' ')
  .trim()

// 查看 "myStr" 的值
console.log(myStr)
// Output:
// 'hello world.'

在數組上也能用方法鏈:編程

// 在數組上使用方法鏈的例子
let myArray = [1, 7, 3, null, 8, null, 0, null, '20', 15]

// 不用方法鏈:
myArray = myArray.filter(el => typeof el === 'number' && isFinite(el))
myArray = myArray.sort((x, y) => x - y)

// 使用方法鏈:
myArray = myArray.filter(el => typeof el === 'number' && isFinite(el)).sort((x, y) => x - y)

// 多行方法鏈的寫法:
myArray = myArray
  .filter(el => typeof el === 'number' && isFinite(el))
  .sort((x, y) => x - y)

// 查看 "myArray" 的值.
console.log(myArray)
// Output:
// [ 0, 1, 3, 7, 8 ]

Promise 是一個很好的例子,由於在使用時差很少全都是方法鏈。首先建立一個 promise,而後添加適當的處理函數。segmentfault

// 建立 Promise
const myPromise = new Promise((resolve, reject) => {
  // 建立一個假延遲
  setTimeout(function() {
    // 用一條簡單的消息解決諾言 promise
    resolve('Sorry, no data.')
  }, 1000)
})

// 使用方法鏈:
myPromise.then((data) => console.log(data)).catch(err => console.log(error))

// 多行方法鏈的寫法:
myPromise
  .then((data) => console.log(data))
  .catch(err => console.log(error))
// Output:
// 'Sorry, no data.'

方法鏈是怎樣工做的

接下來研究它是怎樣工做的。答案很簡單,是由於 this數組

假設有一個對象。若是在該對象內使用 this,它會引用這個對象。若是建立該對象的實例或副本,則 this 將會引用這個實例或副本。當你使用某些字符串或數組方法時,其實是在用一個對象。promise

const myObj = {
  name: 'Stuart',
  age: 65,
  sayHi() {
    // 這裏的 this 是 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: ƒ
// }

若是是字符串,則使用的是原始數據類型。可是你所使用的方法例如 toLowerCase(),存在於 String 對象的原型中。在對象上使用方法鏈還有一個關鍵要素: this服務器

爲了使鏈起做用,方法必須返回與其一塊兒使用的對象,也就是必須返回 this。就像接力賽跑時的接力棒同樣。

在 JavaScript 中實現方法鏈

爲了使方法鏈有效,必須知足三個條件:首先,須要一些對象。其次,該對象須要一些之後能夠調用的方法。第三,這些方法必須返回對象自己,它們必須返回 this 才能使用方法鏈。

讓咱們建立一個簡單的對象 personperson name, agestate 屬性。state 用來表示當前處於什麼狀態。要想改變這個狀態,須要用到幾個方法:walk(), sleep(), eat(), drink(), work()exercise()

因爲咱們但願全部這些方法都是可鏈的,因此它們都必須返回 this。另外代碼中還有一個用來把當前狀態記錄到控制檯的工具方法。

// 建立 person 對象
const person = {
  name: 'Jack Doer',
  age: 41,
  state: null,
  logState() {
    console.log(this.state)
  },
  drink() {
    // 修改 person 的 state.
    this.state = 'Drinking.'

    // 把狀態輸出到控制檯
    this.logState()

    // 返回 this 值。
    return this
  },
  eat() {
    this.state = 'Eating.'
    this.logState()
    return this
  },
  exercise() {
    this.state = 'Exercising.'
    this.logState()
    return this
  },
  sleep() {
    this.state = 'Sleeping.'
    this.logState()
    return this
  },
  walk() {
    this.state = 'Walking.'
    this.logState()
    return this
  },
  work() {
    this.state = 'Working.'
    this.logState()
    return this
  }
}

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

// 寫在一行上
person.drink().exercise().eat().work().walk().sleep()
// Output:
// 'Drinking.'
// 'Exercising.'
// 'Eating.'
// 'Working.'
// 'Walking.'
// 'Sleeping.'

方法、鏈、this 和箭頭函數

必須使用 this 也意味着沒法使用箭頭函數建立方法鏈。由於在箭頭函數中,this 沒有綁定到對象的實例,而是全局對象 window 的引用。若是返回 this,那麼返回的不是對象自己而是 window

另外一個問題是從箭頭函數內部訪問和修改對象屬性。因爲 this 是全局對象 window,因此不能用它來引用對象及其屬性。

若是你必定要使用箭頭函數,必須想辦法繞過這種方法。不用 this 來引用該對象,必須直接經過其名稱引用該對象,也就是用對象名替換全部出如今箭頭功能內的 this

// 建立 person 對象
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
  }
}

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

這樣作的缺點是靈活性很差。若是若是用Object.assign()Object.create()複製對象,全部箭頭函數仍然會硬鏈接到原始對象。

// 建立原始 person 對象
const person = {
  name: 'Jack Doer',
  age: 41,
  state: null,
  logState() {
    // 打印整個對象
    console.log(this)
  },
  drink: () => {
    person.state = 'Drinking.'

    person.logState()

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

    person.logState()

    return person
  }
}

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

// 基於person對象建立新對象。
const newPerson = new Object(person)

// 修改 "name" 和 "age" 屬性
newPerson.name = 'Jackie Holmes'
newPerson.age = 33

// 讓 newPerson drink。
// 這會打印 Jack Doer 而不是 Jackie Holmes。
newPerson.drink()
// Output:
// {
//   name: 'Jack Doer',
//   age: 41,
//   state: 'Drinking.',
//   logState: ƒ,
//   drink: ƒ,
//   eat: ƒ
// }

可是,若是用 Object() 構造函數,就不會發生上述問題。若是用 new 關鍵字的和 Object() 構造造函數,將會建立獨立的新對象。當你對這個新對象使用某個方法時,它將僅對這個新對象有效,而對原始對象無效。

// 建立原始 person 對象
const person = {
  name: 'Jack Doer',
  age: 41,
  state: null,
  logState() {
    // 打印整個對象
    console.log(this)
  },
  drink: () => {
    person.state = 'Drinking.'

    person.logState()

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

    person.logState()

    return person
  }
}

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

// 基於 person 對象建立新對象 
const newPerson = new Object(person)

// 修改 "name" 和 "age" 屬性
newPerson.name = 'Jackie Holmes'
newPerson.age = 33

// 讓 newPerson drink.
newPerson.drink()
// Output:
// {
//   name: 'Jackie Holmes',
//   age: 33,
//   state: 'Drinking.',
//   logState: ƒ,
//   drink: ƒ,
//   eat: ƒ
// }

若是你必定要用箭頭功能,並想要複製對象的話,最好用 Object() 構造函數和 new 關鍵字建立這些副本。不然只須要用常規函數就夠了。

方法鏈和類

若是你喜歡使用 JavaScript 類,也能夠在JavaScript中使用方法連接。除了語法略又不一樣外,整個過程和對象是同樣的。可是要注意全部可鏈的方法都必須返回 this

// 建立 Person 類
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
  }
}

// 建立 Person 類的實例
const joe = new Person('Joe', 55)

// 使用方法鏈
joe
  .drink() // Output: 'Drinking.'
  .eat() // Output: 'Eating.'
  .sleep() // Output: 'Sleeping.'

總結

方法鏈是很是有用的,它能夠幫你編寫更短、更易讀的代碼。

173382ede7319973.gif


本文首發微信公衆號:前端先鋒

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章


歡迎繼續閱讀本專欄其它高贊文章:


相關文章
相關標籤/搜索