關於JS當中的call,apply和bind,相信你們和我同樣,已經看過了無數篇相關的文章,都有本身的理解。因此這篇文章並不是什麼科普類的文章,僅僅是把我本身的理解記錄下來。javascript
個人學習習慣,是喜歡把各類看似孤立的知識點串聯起來,綜合理解並運用,經過最簡單最直觀的思路把它理解透。因此,這篇文章將經過一段很是簡潔的等式,把JS當中一個相對較難的知識點,call,apply和bind給串聯起來:java
cat.call(dog, a, b) = cat.apply(dog, [a, b]) = (cat.bind(dog, a, b))() = dog.cat(a, b)
要理解JS當中的這三個關鍵字,首先得弄清楚它們是用來幹嗎的。複雜些來講,能夠引用MDN文檔的原文:app
可讓call()中的對象調用當前對象所擁有的function。你可使用call()來實現繼承:寫一個方法,而後讓另一個新的對象來繼承它(而不是在新對象中再寫一次這個方法)。
簡單些來講,能夠引用你們都看過的一句話:函數
爲了動態改變某個函數運行時的上下文(context)。
又或者是學習
爲了改變函數體內部this的指向
上面這些解釋都很正確,說得一點問題都沒有,可是裏面卻又引入了繼承
,上下文
,this
這些額外的知識點。若是我只想用最直觀的辦法去理解這三個關鍵字的做用,也許能夠這麼去理解:this
定義一個貓對象:code
class Cat { constructor (name) { this.name = name } catchMouse(name1, name2) { console.log(`${this.name} caught 2 mouse! They call ${name1} and ${name2}.`) } }
這個貓對象擁有一個抓老鼠的技能catchMouse()
。對象
而後相似的,定義一個狗對象:繼承
class Dog { constructor (name) { this.name = name } biteCriminals(name1, name2) { console.log(`${this.name} bite 2 criminals! Their name is ${name1} and ${name2}.`) } }
這個狗對象可以咬壞人biteCriminal()
。ip
接下來,咱們實例化兩個對象,分別獲得一隻叫「Kitty」的貓和叫「Doggy」的狗:
const kitty = new Cat('Kitty') const doggy = new Dog('Doggy')
首先讓它們彼此發揮本身的技能:
kitty.catchMouse('Mickey', 'Minnie') // Kitty caught mouse! They call Mickey and Minnie. doggy.biteCriminal('Tom', 'Jerry') // Doggy bite a criminal! Their name is Tom and Jerry.
如今,咱們但願賦予Doggy抓老鼠的能力,若是不使用這三個關鍵字,應該怎麼作呢?
方案A:修改Dog對象,直接爲其定義一個和Cat相同的抓老鼠技能。
方案B:讓Doggy吃掉Kitty,直接消化吸取Kitty的全部能力。
其實方案A和方案B的解決辦法是相似的,也是須要修改Dog對象,不過方案B會更簡單粗暴一點:
class Dog { constructor (name, kitty) { this.name = name this.catchMouse = kitty.catchMouse } biteCriminals(name1, name2) { console.log(`${this.name} bite 2 criminals! Their name is ${name1} and ${name2}.`) } } const kitty = new Cat('Kitty') const doggy = new Dog('Doggy', kitty) doggy.catchMouse('Mickey', 'Minnie') // Doggy caught 2 mouse! They call Mickey and Minnie.
上面這種方法實在是太不優雅,每每不少時候在定義Dog對像的時候根本就沒有打算過要爲它添加抓老鼠的方法。那麼有沒有一種辦法可以在不修改Dog對象內容的前提下,讓Doggy實例也可以擁有抓老鼠的辦法呢?答案就是使用call,apply或者bind關鍵字:
kitty.catchMouse.call(doggy, 'Mickey', 'Minnie') kitty.catchMouse.apply(doggy, ['Mickey', 'Minnie']) const doggyCatchMouse = kitty.catchMouse.bind(doggy, 'Mickey', 'Minnie') doggyCatchMouse() // Doggy caught 2 mouse! They call Mickey and Minnie. // Doggy caught 2 mouse! They call Mickey and Minnie. // Doggy caught 2 mouse! They call Mickey and Minnie.
反過來,讓Kitty擁有咬壞人的能力,也能夠經過這種辦法實現,讀者能夠自行嘗試。
看到這裏,相信讀者已經可以明白call,apply和bind的區別及做用,反過來再查看各自的概念,應該也可以更容易理解。
回到文章開頭的等式:
cat.call(dog, a, b) = cat.apply(dog, [a, b]) = (cat.bind(dog, a, b))() = dog.cat(a, b)
這裏的「等號」其實並不嚴謹,由於三個關鍵字的區別及背後的原理確定不是區區一個等號就可以歸納的,可是對於概念的理解以及實際狀況下的運用來講,這條等式未必不是一個好的思路。