淺析 JavaScript 中的方法鏈
方法鏈?zhǔn)且环N流行的編程方法,可以幫助你寫出更簡(jiǎn)潔易讀的代碼。在本文中我們一起學(xué)習(xí) JavaScript 中的方法鏈?zhǔn)鞘裁矗约八窃鯓庸ぷ鞯?。另外我們還會(huì)探討如何使用方法鏈接來提高代碼的質(zhì)量和可讀性。
JavaScript 中方法鏈
你一定曾經(jīng)用過 jQuery 之類的庫(kù),可能看到過類似的東西。在進(jìn)行級(jí)聯(lián)時(shí)主要有兩種方法:一種是一個(gè)接一個(gè)的執(zhí)行方法,另一種是在同一行上。在純 JavaScript 中這種做法也很普遍。你可以在數(shù)組、字符串和 promise 看到它。
在這些情況下所有的過程都是相同的。首先引用要使用的對(duì)象。然后根據(jù)需要使用多種方法。但不是單獨(dú)使用這些方法,而要一個(gè)接一個(gè)地使用?;旧鲜前阉鼈冩溄釉谝黄稹O瓤匆恍├?。
方法鏈的例子
在處理字符串時(shí)有兩種方法。第一個(gè)種不用方法鏈,這要求必須在字符串上分別使用每個(gè)方法,這樣必須每次都引用這個(gè)字符串。
第二種方式是用方法鏈。這時(shí)可以用所有想要的字符串方法。寫出的代碼也可以是單行或多行,這取決于你的習(xí)慣。而且只需要引用一次字符串。盡管結(jié)果相同,但是代碼量卻有很大的差異。
- // 在字符串上使用方法鏈的例子
- 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.'
在數(shù)組上也能用方法鏈:
- // 在數(shù)組上使用方法鏈的例子
- 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 是一個(gè)很好的例子,因?yàn)樵谑褂脮r(shí)差不多全都是方法鏈。首先創(chuàng)建一個(gè) promise,然后添加適當(dāng)?shù)奶幚砗瘮?shù)。
- // 創(chuàng)建 Promise
- const myPromise = new Promise((resolve, reject) => {
- // 創(chuàng)建一個(gè)假延遲
- setTimeout(function() {
- // 用一條簡(jiǎn)單的消息解決諾言 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.'
方法鏈?zhǔn)窃鯓庸ぷ鞯?/h3>
接下來研究它是怎樣工作的。答案很簡(jiǎn)單,是因?yàn)?this 。
假設(shè)有一個(gè)對(duì)象。如果在該對(duì)象內(nèi)使用 this,它會(huì)引用這個(gè)對(duì)象。如果創(chuàng)建該對(duì)象的實(shí)例或副本,則 this 將會(huì)引用這個(gè)實(shí)例或副本。當(dāng)你使用某些字符串或數(shù)組方法時(shí),實(shí)際上是在用一個(gè)對(duì)象。
- 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: ƒ
- // }
如果是字符串,則使用的是原始數(shù)據(jù)類型。但是你所使用的方法例如 toLowerCase(),存在于 String 對(duì)象的原型中。在對(duì)象上使用方法鏈還有一個(gè)關(guān)鍵要素:this。
為了使鏈起作用,方法必須返回與其一起使用的對(duì)象,也就是必須返回 this。就像接力賽跑時(shí)的接力棒一樣。
在 JavaScript 中實(shí)現(xiàn)方法鏈
為了使方法鏈有效,必須滿足三個(gè)條件:首先,需要一些對(duì)象。其次,該對(duì)象需要一些以后可以調(diào)用的方法。第三,這些方法必須返回對(duì)象本身,它們必須返回 this 才能使用方法鏈。
讓我們創(chuàng)建一個(gè)簡(jiǎn)單的對(duì)象 person。person 有 name, age 和 state 屬性。state 用來表示當(dāng)前處于什么狀態(tài)。要想改變這個(gè)狀態(tài),需要用到幾個(gè)方法:walk(), sleep(), eat(), drink(), work() 和 exercise()。
由于我們希望所有這些方法都是可鏈的,所以它們都必須返回 this。另外代碼中還有一個(gè)用來把當(dāng)前狀態(tài)記錄到控制臺(tái)的工具方法。
- // 創(chuàng)建 person 對(duì)象
- const person = {
- name: 'Jack Doer',
- age: 41,
- state: null,
- logState() {
- console.log(this.state)
- },
- drink() {
- // 修改 person 的 state.
- this.state = 'Drinking.'
- // 把狀態(tài)輸出到控制臺(tái)
- 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 和箭頭函數(shù)必須使用
也意味著無(wú)法使用箭頭函數(shù)創(chuàng)建方法鏈。因?yàn)樵诩^函數(shù)中,this 沒有綁定到對(duì)象的實(shí)例,而是全局對(duì)象 window 的引用。如果返回 this,那么返回的不是對(duì)象本身而是 window。
另一個(gè)問題是從箭頭函數(shù)內(nèi)部訪問和修改對(duì)象屬性。由于 this 是全局對(duì)象 window,所以不能用它來引用對(duì)象及其屬性。
如果你一定要使用箭頭函數(shù),必須想辦法繞過這種方法。不用 this 來引用該對(duì)象,必須直接通過其名稱引用該對(duì)象,也就是用對(duì)象名替換所有出現(xiàn)在箭頭功能內(nèi)的 this。
- // 創(chuàng)建 person 對(duì)象
- 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.'
這樣做的缺點(diǎn)是靈活性不好。如果如果用Object.assign() 和 Object.create()復(fù)制對(duì)象,所有箭頭函數(shù)仍然會(huì)硬連接到原始對(duì)象。
- // 創(chuàng)建原始 person 對(duì)象
- const person = {
- name: 'Jack Doer',
- age: 41,
- state: null,
- logState() {
- // 打印整個(gè)對(duì)象
- 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對(duì)象創(chuàng)建新對(duì)象。
- const newPerson = new Object(person)
- // 修改 "name" 和 "age" 屬性
- newPerson.name = 'Jackie Holmes'
- newPerson.age = 33
- // 讓 newPerson drink。
- // 這會(huì)打印 Jack Doer 而不是 Jackie Holmes。
- newPerson.drink()
- // Output:
- // {
- // name: 'Jack Doer',
- // age: 41,
- // state: 'Drinking.',
- // logState: ƒ,
- // drink: ƒ,
- // eat: ƒ
- // }
但是,如果用 Object() 構(gòu)造函數(shù),就不會(huì)發(fā)生上述問題。如果用 new 關(guān)鍵字的和 Object() 構(gòu)造造函數(shù),將會(huì)創(chuàng)建獨(dú)立的新對(duì)象。當(dāng)你對(duì)這個(gè)新對(duì)象使用某個(gè)方法時(shí),它將僅對(duì)這個(gè)新對(duì)象有效,而對(duì)原始對(duì)象無(wú)效。
- // 創(chuàng)建原始 person 對(duì)象
- const person = {
- name: 'Jack Doer',
- age: 41,
- state: null,
- logState() {
- // 打印整個(gè)對(duì)象
- 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 對(duì)象創(chuàng)建新對(duì)象
- 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: ƒ
- // }
如果你一定要用箭頭功能,并想要復(fù)制對(duì)象的話,最好用 Object() 構(gòu)造函數(shù)和 new 關(guān)鍵字創(chuàng)建這些副本。否則只需要用常規(guī)函數(shù)就夠了。
方法鏈和類
如果你喜歡使用 JavaScript 類,也可以在JavaScript中使用方法鏈接。除了語(yǔ)法略又不同外,整個(gè)過程和對(duì)象是一樣的。但是要注意所有可鏈的方法都必須返回 this。
- // 創(chuàng)建 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
- }
- }
- // 創(chuàng)建 Person 類的實(shí)例
- const joe = new Person('Joe', 55)
- // 使用方法鏈
- joe
- .drink() // Output: 'Drinking.'
- .eat() // Output: 'Eating.'
- .sleep() // Output: 'Sleeping.'
總結(jié)
方法鏈?zhǔn)欠浅S杏玫模梢詭湍憔帉懜?、更易讀的代碼。