深刻理解JavaScript this

this是什麼?作什麼?指向是什麼?javascript

函數中this調用:this-----》windowcss

方法中this調用:this-----》當前對象(嗲用方法的對象)html

構造函數中this調用:this------》該構造函數的實例對象java

借用方法調用:改變this的指向node

分析this的問題,須要明白兩點:(只看函數是怎麼被調用的,無論函數是怎麼來的)css3

  1.分析this是屬於哪一個函數程序員

  2.分析該函數是以什麼模式被調用的es6

嚴格模式中,函數調用模式內部的this爲:undefined

 

 

要說 JavaScript 這門語言最容易讓人困惑的知識點,this 關鍵詞確定算一個。JavaScript 語言面世多年,一直在進化完善,如今在服務器上還能夠經過 node.js 來跑 JavaScript。顯然,這門語言還會活好久。web

因此說,我一直相信,若是你是一個 JavaScript 開發者或者說 web 開發者,學好 JavaScript 的運做原理以及語言特色確定對你之後大有好處。編程

開始以前

在開始正文以前,我強烈推薦你先掌握好下面的知識:

  • 變量做用域和做用域提高
  • JavaScript 的函數
  • 閉包

若是沒有對這些基礎知識掌握踏實,直接討論 JavaScript 的 this 關鍵詞只會讓你感到更加地困惑和挫敗。

我爲何要學 this

若是上面的簡單介紹沒有說服你來深刻探索 this 關鍵詞,那我用這節來說講爲何要學。

考慮這樣一個重要問題,假設開發者,好比 Douglas Crockford (譯者注:JavaScript 領域必知牛人),再也不使用 new 和 this,轉而使用完徹底全的函數式寫法來作代碼複用,會怎樣?

事實上,基於 JavaScript 內置的現成的原型繼承功能,咱們已經使用而且將繼續普遍使用 new 和 this 關鍵詞來實現代碼複用。

理由一,若是隻能使用本身寫過的代碼,你是無法工做的。現有的代碼以及你讀到這句話時別人正在寫的代碼都頗有可能包含 this 關鍵詞。那麼學習怎麼用好它是否是頗有用呢?

所以,即便你不打算在你的代碼庫中使用它,深刻掌握 this 的原理也能讓你在接手別人的代碼理解其邏輯時事半功倍。

理由二,拓展你的編碼視野和技能。使用不一樣的設計模式會加深你對代碼的理解,怎麼去看、怎麼去讀、怎麼去寫、怎麼去理解。咱們寫代碼不只是給機器去解析,仍是寫給咱們本身看的。這不只適用於 JavaScript,對其餘編程語言亦是如此。

隨着對編程理念的逐步深刻理解,它會逐漸塑造你的編碼風格,無論你用的是什麼語言什麼框架。

就像畢加索會爲了得到靈感而涉足那些他並非很贊同很感興趣的領域,學習 this 會拓展你的知識,加深對代碼的理解。

什麼是 this ?

JavaScript this 指向

在我開始講解前,若是你學過一門基於類的面向對象編程語言(好比 C#,Java,C++),那請將你對 this 這個關鍵詞應該是作什麼用的先入爲主的概念扔到垃圾桶裏。JavaScript 的 this 關鍵詞是很不同,由於 JavaScript 原本就不是一門基於類的面向對象編程語言。

雖然說 ES6 裏面 JavaScript 提供了類這個特性給咱們用,但它只是一個語法糖,一個基於原型繼承的語法糖。

this 就是一個指針,指向咱們調用函數的對象。

我難以強調上一句話有多重要。請記住,在 Class 添加到 ES6 以前,JavaScript 中沒有 Class 這種東西。Class 只不過是一個將對象串在一塊兒表現得像類繼承同樣的語法糖,以一種咱們已經習慣的寫法。全部的魔法背後都是用原型鏈編織起來的。

若是上面的話很差理解,那你能夠這樣想,this 的上下文跟英語句子的表達很類似。好比下面的例子

Bob.callPerson(John);

就能夠用英語寫成 「Bob called a person named John」。因爲 callPerson() 是 Bob 發起的,那 this 就指向 Bob。咱們將在下面的章節深刻更多的細節。到了這篇文章結束時,你會對 this 關鍵詞有更好的理解(和信心)。

執行上下文

執行上下文 是語言規範中的一個概念,用通俗的話講,大體等同於函數的執行「環境」。具體的有:變量做用域(和 做用域鏈條,閉包裏面來自外部做用域的變量),函數參數,以及 this 對象的值。

引自: Stackoverflow.com

記住,如今起,咱們專一於查明 this 關鍵詞到底指向哪。所以,咱們如今要思考的就一個問題:

  • 是什麼調用函數?是哪一個對象調用了函數?

爲了理解這個關鍵概念,咱們來測一下下面的代碼。

var person = { name: "Jay", greet: function() { console.log("hello, " + this.name); } }; person.greet(); 

誰調用了 greet 函數?是 person 這個對象對吧?在 greet() 調用的左邊是一個 person 對象,那麼 this 關鍵詞就指向 personthis.name 就等於 "Jay"。如今,仍是用上面的例子,我加點料:

var greet = person.greet; // 將函數引用存起來; greet(); // 調用函數 

你以爲在這種狀況下控制檯會輸出什麼?「Jay」?undefined?仍是別的?

正確答案是 undefined。若是你對這個結果感到驚訝,沒必要慚愧。你即將學習的東西將幫助你在 JavaScript 旅程中打開關鍵的大門。

this 的值並非由函數定義放在哪一個對象裏面決定,而是函數執行時由誰來喚起決定。

對於這個意外的結果咱們暫且壓下,繼續看下去。(感受先後銜接得不夠流暢)

帶着這個困惑,咱們接着測試下 this 三種不一樣的定義方式。

找出 this 的指向

上一節咱們已經對 this 作了測試。可是這塊知識實在重要,咱們須要再好好琢磨一下。在此以前,我想用下面的代碼給你出個題:

var name = "Jay Global"; var person = { name: 'Jay Person', details: { name: 'Jay Details', print: function() { return this.name; } }, print: function() { return this.name; } }; console.log(person.details.print()); // ? console.log(person.print()); // ? var name1 = person.print; var name2 = person.details; console.log(name1()); // ? console.log(name2.print()) // ? 

console.log() 將會輸出什麼,把你的答案寫下來。若是你還想不清楚,複習下上一節。

準備好了嗎?放鬆心情,咱們來看下面的答案。

答案和解析

person.details.print()

首先,誰調用了 print 函數?在 JavaScript 中咱們都是從左讀到右。因而 this 指向 details 而不是 person。這是一個很重要的區別,若是你對這個感到陌生,那趕忙把它記下。

print 做爲 details 對象的一個 key,指向一個返回 this.name 的函數。既然咱們已經找出 this 指向 details ,那函數的輸出就應該是 'Jay Details'

person.print()

再來一次,找出 this 的指向。print() 是被 person 對象調用的,沒錯吧?

在這種狀況,person 裏的 print 函數返回 this.namethis 如今指向 person 了,那 'Jay Person'就是返回值。

console.log(name1)

這一題就有點狡猾了。在上一行有這樣一句代碼:

var name1 = person.print; 

若是你是經過這句來思考的,我不會怪你。很遺憾,這樣去想是錯的。要記住,this 關鍵詞是在函數調用時才作綁定的。name1() 前面是什麼?什麼都沒有。所以 this 關鍵詞就將指向全局的 window對象去。

所以,答案是 'Jay Global'

name2.print()

看一下 name2 指向哪一個對象,是 details 對象沒錯吧?

因此下面這句會打印出什麼呢?若是到目前爲止的全部小點你都理解了,那這裏稍微思考下你就天然有答案了。

console.log(name2.print()) // ?? 

答案是 'Jay Details',由於 print 是 name2 調起的,而 name2 指向 details

詞法做用域

你可能會問:「什麼是詞法做用域?」

逗我呢,咱們不是在探討 this 關鍵詞嗎,這個又是哪裏冒出來的?好吧,當咱們用起 ES6 的箭頭函數,這個就要考慮了。若是你已經寫了不止一年的 JavaScript,那你極可能已經碰到箭頭函數。隨着 ES6 逐漸成爲現實標準,箭頭函數也變得愈來愈經常使用。

JavaScript 的詞法做用域 並很差懂。若是你 理解閉包,那要理解這個概念就容易多了。來看下下面的小段代碼。

// outerFn 的詞法做用域 var outerFn = function() { var n = 5; console.log(innerItem); // innerFn 的詞法做用域 var innerFn = function() { var innerItem = "inner"; // 錯了。只能坐着電梯向上,不能向下。 console.log(n); }; return innerFn; }; outerFn()(); 

想象一下一棟樓裏面有一架只能向上走的詭異電梯。

JavaScript 的詞法做用域就像樓裏的一架只能向上走的詭異電梯

建築的頂層就是全局 windows 對象。若是你如今在一樓,你就能夠看到並訪問那些放在樓上的東西,好比放在二樓的 outerFn 和放在三樓的 window 對象。

這就是爲何咱們執行代碼 outerFn()(),它在控制檯打出了 5 而不是 undefined

然而,當咱們試着在 outerFn 詞法做用域下打出日誌 innerItem,咱們遇到了下面的報錯。請記住,JavaScript 的詞法做用域就好像建築裏面那個只能向上走的詭異電梯。因爲 outerFn 的詞法做用域在 innerFn 上面,因此它不能向下走到 innerFn 的詞法做用域裏面並拿到裏面的值。這就是觸發下面報錯的緣由:

test.html:304 Uncaught ReferenceError: innerItem is not defined
at outerFn (test.html:304)
at test.html:313

this 和箭頭函數

在 ES6 裏面,無論你喜歡與否,箭頭函數被引入了進來。對於那些還沒用慣箭頭函數或者新學 JavaScript 的人來講,當箭頭函數和 this 關鍵詞混合使用時會發生什麼,這個點可能會給你帶來小小的困惑和淡淡的憂傷。那這個小節就是爲大家準備的!

當涉及到 this 關鍵詞,箭頭函數 和 普通函數 主要的不一樣是什麼?

答案:

箭頭函數按詞法做用域來綁定它的上下文,因此 this 實際上會引用到原來的上下文。

引自:hackernoon.com

我實在無法給出比這個更好的總結。

箭頭函數保持它當前執行上下文的詞法做用域不變,而普通函數則不會。換句話說,箭頭函數從包含它的詞法做用域中繼承到了 this 的值。

咱們不妨來測試一些代碼片斷,確保你真的理解了。想清楚這塊知識點將來會讓你少點頭痛,由於你會發現 this 關鍵詞和箭頭函數太常常一塊兒用了。

示例

仔細閱讀下面的代碼片斷。

var object = { data: [1,2,3], dataDouble: [1,2,3], double: function() { console.log("this inside of outerFn double()"); console.log(this); return this.data.map(function(item) { console.log(this); // 這裏的 this 是什麼?? return item * 2; }); }, doubleArrow: function() { console.log("this inside of outerFn doubleArrow()"); console.log(this); return this.dataDouble.map(item => { console.log(this); // 這裏的 this 是什麼?? return item * 2; }); } }; object.double(); object.doubleArrow(); 

若是咱們看執行上下文,那這兩個函數都是被 object 調用的。因此,就此判定這兩個函數裏面的 this 都指向 object 不爲過吧?是的,但我建議你拷貝這段代碼而後本身測一下。

這裏有個大問題:

arrow() 和 doubleArrow() 裏面的 map 函數裏面的 this 又指向哪裏呢?

this 和箭頭函數

上一張圖已經給了一個大大的提示。若是你還不肯定,那請花5分鐘將咱們上一節討論的內容再好好想一想。而後,根據你的理解,在實際執行代碼前把你認爲的 this 應該指向哪裏寫下來。在下一節咱們將會回答這個問題。

回顧執行上下文

這個標題已經把答案泄露出來了。在你看不到的地方,map 函數對調用它的數組進行遍歷,將數組的每一項傳到回調函數裏面並把執行結果返回。若是你對 JavaScript 的 map 函數不太瞭解或有所好奇,能夠讀讀這個瞭解更多。

總之,因爲 map() 是被 this.data 調起的,因而 this 將指向那個存儲在 data 這個 key 裏面的數組,即 [1,2,3]。一樣的邏輯,this.dataDouble 應該指向另外一個數組,值爲 [1,2,3]

如今,若是函數是 object 調用的,咱們已經肯定 this 指向 object 對吧?好,那來看看下面的代碼片斷。

double: function() { return this.data.map(function(item) { console.log(this); // 這裏的 this 是什麼?? return item * 2; }); } 

這裏有個頗有迷惑性的問題:傳給 map() 的那個匿名函數是誰調用的?答案是:這裏沒有一個對象是。爲了看得更明白,這裏給出一個 map 函數的基本實現。

// Array.map polyfill if (Array.prototype.map === undefined) { Array.prototype.map = function(fn) { var rv = []; for(var i=0, l=this.length; i<l; i++) rv.push(fn(this[i])); return rv; }; } 

fn(this[i])); 前面有什麼對象嗎?沒。所以,this 關鍵詞指向全局的 windows 對象。那,爲何 this.dataDouble.map 使用了箭頭函數會使得 this 指向 object 呢?

我想再說一遍這句話,由於它實在很重要:

箭頭函數按詞法做用域將它的上下文綁定到 原來的上下文

如今,你可能會問:原來的上下文是什麼?問得好!

誰是 doubleArrow() 的初始調用者?就是 object 對吧?那它就是原來的上下文

this 和 use strict

爲了讓 JavaScript 更加健壯及儘可能減小人爲出錯,ES5 引進了嚴格模式。一個典型的例子就是 this 在嚴格模式下的表現。你若是想按照嚴格模式來寫代碼,你只須要在你正在寫的代碼的做用域最頂端加上這麼一行 "use strict;"

記住,傳統的 JavaScript 只有函數做用域,沒有塊做用域。舉個例子:

function strict() { // 函數級嚴格模式寫法  'use strict'; function nested() { return 'And so am I!'; } return "Hi! I'm a strict mode function! " + nested(); } function notStrict() { return "I'm not strict."; } 

代碼片斷來自 Mozilla Developer Network。

不過呢,ES6 裏面經過 let 關鍵詞提供了塊做用域的特性。

如今,來看一段簡單代碼,看下 this 在嚴格模式和非嚴格模式下會怎麼表現。在繼續以前,請將下面的代碼運行一下。

(function() {  "use strict"; console.log(this); })(); (function() { // 不使用嚴格模式 console.log(this); })(); 

正如你看到的,this 在嚴格模式下指向 undefined。相對的,非嚴格模式下 this 指向全局變量 window。大部分狀況下,開發者使用 this ,並不但願它指向全局 window 對象。嚴格模式幫咱們在使用 this 關鍵詞時,儘可能少作搬起石頭砸本身腳的蠢事。

舉個例子,若是全局的 window 對象恰好有一個 key 的名字和你但願訪問到的對象的 key 相同,會怎樣?上代碼吧:

(function() { // "use strict"; var item = { document: "My document", getDoc: function() { return this.document; } } var getDoc = item.getDoc; console.log(getDoc()); })(); 

這段代碼有兩個問題。

  1. this 將不會指向 item
  2. 若是程序在非嚴格模式下運行,將不會有錯誤拋出,由於全局的 window 對象也有一個名爲 document 的屬性。

在這個簡單示例中,由於代碼較短也就不會造成大問題。

若是你是在生產環境像上面那樣寫,當用到 getDoc 返回的數據時,你將收穫一堆難以定位的報錯。若是你代碼庫比較大,對象間互動比較多,那問題就更嚴重了。

值得慶幸的是,若是咱們是在嚴格模式下跑這段代碼,因爲 this 是 undefined,因而馬上就有一個報錯拋給咱們:

test.html:312 Uncaught TypeError: Cannot read property 'document' of undefined at getDoc (test.html:312) at test.html:316 at test.html:317

明確設置執行上下文

先前假定你們都對執行上下文不熟,因而咱們聊了不少關於執行上下文和 this 的知識。

讓人歡喜讓人憂的是,在 JavaScript 中經過使用內置的特性開發者就能夠直接操做執行上下文了。這些特性包括:

  • bind():不須要執行函數就能夠將 this 的值準確設置到你選擇的一個對象上。還能夠經過逗號隔開傳遞多個參數,如 func.bind(this, param1, param2, ...) 。
  • apply():將 this 的值準確設置到你選擇的一個對象上。第二個參數是一個數組,數組的每一項是你但願傳遞給函數的參數。最後,執行函數。
  • call():將 this 的值準確設置到你選擇的一個對象上,而後想 bind 同樣經過逗號分隔傳遞多個參數給函數。如:print.call(this, param1, param2, ...)。最後,執行函數。

上面提到的全部內置函數都有一個共同點,就是它們都是用來將 this 關鍵詞指向到其餘地方。這些特性可讓咱們玩一些騷操做。只是呢,這個話題太廣了都夠寫好幾篇文章了,因此簡潔起見,這篇文章我不打算展開它的實際應用。

重點:上面那三個函數,只有 bind() 在設置好 this 關鍵詞後不馬上執行函數。

何時用 bind、call 和 apply

你可能在想:如今已經很亂了,學習全部這些的目的是什麼?

首先,你會看到 bind、call 和 apply 這幾個函數處處都會用到,特別是在一些大型的庫和框架。若是你沒理解它作了些什麼,那可憐的你就只用上了 JavaScript 提供的強大能力的一小部分而已。

若是你不想了解一些可能的用法而想馬上讀下去,固然了,你能夠直接跳過這節,不要緊。

下面列出來的應用場景都是一些具備深度和廣度的話題(一篇文章基本上是講不完的),因此我放了一些連接供你深度閱讀用。將來我可能會在這篇終極指南里面繼續添加新的小節,這樣你們就能夠一次看過癮。

  1. 方法借用
  2. 柯里化
  3. 偏函數應用
  4. 依賴注入

若是我漏掉了其餘實踐案例,請留言告知。我會常常來優化這篇指南,這樣你做爲讀者就能夠讀到最豐富的內容。

閱讀高質量的開源代碼能夠升級你的知識和技能。

講真,你會在一些開源代碼上看到 this 關鍵詞、call、apply 和 bind 的實際應用。我會將這塊結合着其餘能幫你成爲更好的程序員的方法一塊兒講。

在我看來,開始閱讀最好的開源代碼是 underscore。它並不像其餘開源項目,如 d3,那樣鐵板一塊,而是內部代碼相互比較獨立,於是它是教學用的最佳選擇。另外,它代碼簡潔,文檔詳細,編碼風格也是至關容易學習。

JavaScript 的 this 和 bind

前面提到了,bind 容許你明確設定 this 的指向而不用實際去執行函數。這裏是一個簡單示例:

var bobObj = { name: "Bob" }; function print() { return this.name; } // 將 this 明確指向 "bobObj" var printNameBob = print.bind(bobObj); console.log(printNameBob()); // this 會指向 bob,因而輸出結果是 "Bob" 

在上面的示例中,若是你把 bind 那行去掉,那 this 將會指向全局 window 對象。

這好像很蠢,但在你想將 this 綁定到具體對象前你就必須用 bind 來綁定。在某些場景下,咱們可能想從另外一個對象中借用一些方法。舉個例子,

var obj1 = { data: [1,2,3], printFirstData: function() { if (this.data.length) return this.data[0]; } }; var obj2 = { data: [4,5,6], printSecondData: function() { if (this.data.length > 1) return this.data[1]; } }; // 在 obj1 中借用 obj2 的方法 var getSecondData = obj2.printSecondData.bind(obj1); console.log(getSecondData()); // 輸出 2 

在這個代碼片斷裏,obj2 有一個名爲 printSecondData 的方法,而咱們想將這個方法借給 obj1。在下一行

var getSecondData = obj2.printSecondData.bind(obj1); 

經過使用 bind ,咱們讓 obj1 能夠訪問 obj2 的 printSecondData 方法。

練習

在下面的代碼中

var object = { data: [1,2,3], double: function() { this.data.forEach(function() { // Get this to point to object. console.log(this); }); } }; object.double(); 

怎麼讓 this 關鍵詞指向 object。提示:你並不須要重寫 this.data.forEach

答案

在上一節中,咱們瞭解了執行上下文。若是你對匿名函數調用那部分看得夠細心,你就知道它並不會做爲某個對象的方法被調用。所以,this 關鍵詞指向了全局 window 對象。

因而咱們須要將 object 做爲上下文綁定到匿名函數上,使得裏面的 this 指向 object。如今,double 函數跑起來時,是 object 調用了它,那麼 double 裏面的 this 指向 object

var object = { data: [1,2,3], double: function() { return this.data.forEach(function() { // Get this to point to object. console.log(this); }.bind(this)); } }; object.double(); 

那,若是咱們像下面這樣作呢?

var double = object.double; double(); // ?? 

double() 的調用上下文是什麼?是全局上下文。因而,咱們就會看到下面的報錯。

Uncaught TypeError: Cannot read property 'forEach' of undefined at double (test.html:282) at test.html:289

因此,當咱們用到 this 關鍵詞時,就要當心在乎咱們調用函數的方式。咱們能夠在提供 API 給用戶時固定 this 關鍵詞,以此減小這種類型的錯誤。但請記住,這麼作的代價是犧牲了靈活性,因此作決定前要考慮清楚。

var double = object.double.bind(object); double(); // 再也不報錯 

JavaScript this 和 call

call 方法和 bind 很類似,但就如它名字所暗示的,call 會馬上呼起(執行)函數,這是兩個函數的最大區別。

var item = { name: "I am" }; function print() { return this.name; } // 馬上執行 var printNameBob = console.log(print.call(item)); 

callapplybind 大部分使用場景是重疊的。做爲一個程序員最重要的仍是先了解清楚這三個方法之間的差別,從而能根據它們的設計和目的的不一樣來選用。只要你瞭解清楚了,你就能夠用一種更有創意的方式來使用它們,寫出更獨到精彩的代碼。

在參數數量固定的場景,call 或 bind 是不錯的選擇。好比說,一個叫 doLogin 的函數常常是接受兩個參數:username 和 password。在這個場景下,若是你須要將 this 綁定到一個特定的對象上,call 或 bind 會挺好用的。

如何使用 call

之前一個最經常使用的場景是把一個類數組對象,好比 arguments 對象,轉化成數組。舉個例子:

function convertArgs() { var convertedArgs = Array.prototype.slice.call(arguments); console.log(arguments); console.log(Array.isArray(arguments)); // false console.log(convertedArgs); console.log(Array.isArray(convertedArgs)); // true } convertArgs(1,2,3,4); 

在上面的例子中,咱們使用 call 將 argument 對象轉化成一個數組。在下一個例子中,咱們將會調用一個 Array 對象的方法,並將 argument 對象設置爲方法的 this,以此來將傳進來參數加在一塊兒。

function add (a, b) { return a + b; } function sum() { return Array.prototype.reduce.call(arguments, add); } console.log(sum(1,2,3,4)); // 10 

咱們在一個類數組對象上調用了 reduce 函數。要知道 arguments 不是一個數組,但咱們給了它調用 reduce 方法的能力。若是你對 reduce 感興趣,能夠在這裏瞭解更多。

練習

如今是時候鞏固下你新學到的知識。

  1. document.querySelectorAll() 返回一個類數組對象 NodeList。請寫一個函數,它接收一個 CSS 選擇器,而後返回一個選擇到的 DOM 節點數組。
  2. 請寫一個函數,它接收一個由鍵值對組成的數組,而後將這些鍵值對設置到 this 關鍵詞指向的對象上,最後將該對象返回。若是 this 是 null 或 undefined,那就新建一個 object。示例:set.call( {name: "jay"}, {age: 10, email: '[[email protected]](/cdn-cgi/l/email-protection)'}); // return {name: "jay", age: 10, email: '[[email protected]](/cdn-cgi/l/email-protection)'}

JavaScript this 和 apply

apply 就是接受數組版本的 call。因而當使用 apply 時,多聯想下數組。

將一個方法應用(apply)到一個數組上。

我用這句話來記住它,並且還挺管用。apply 爲你的現有堆積的軍火庫又添加了同樣利器,增長了不少新的可能,你很快就能體會到這一點。

當你要處理參數數量動態變化的場景,用 apply 吧。將一系列數據轉化爲數組並用上 apply 能讓你寫出更好用和更具彈性的代碼,會讓你的工做更輕鬆。

如何使用 apply

Math.min 和 max 都是能夠接受多個參數並返回最小值和最大值的函數。除了直接傳 n 個參數,你也能夠將這 n 個參數放到一個數組裏而後藉助 apply 將它傳到 min 函數裏。

Math.min(1,2,3,4); // 返回 1 Math.min([1,2,3,4]); // 返回 NaN。只接受數字 Math.min.apply(null, [1,2,3,4]); // 返回 1 

看暈了嗎?若是真暈了,那我來解釋下。使用 apply 時咱們要傳一個數組由於它須要數組做爲第二個參數。而下面

Math.min.apply(null, [1,2,3,4]); // 返回 1 

作的事情基本等同於

Math.min(1,2,3,4); // 返回 1

這就是我想指出來的 apply 的神奇之處。它和 call 工做原理,不過咱們只要傳給它一個數組而不是 n 個參數。很好玩對吧?橋豆麻袋,這是否意味着 Math.min.call(null, 1,2,3,4); 執行起來和 Math.min.apply(null, [1,2,3,4]); 同樣?

啊,你說對了!看來你已經開始掌握它了

讓咱們來看下另外一種用法。

function logArgs() { console.log.apply(console, arguments); } logArgs(1,3,'I am a string', {name: "jay", age: "1337"}, [4,5,6,7]); 

沒錯,你甚至能夠傳一個類數組對象做爲 apply 的第二個參數。很酷對吧?

練習

  1. 寫一個函數,它接受一個由鍵值對組成的數組,而後將這些鍵值對設置到 this 關鍵詞指向的對象上,最後將該對象返回。若是 this 是 null 或 undefined,那就新建一個 object。示例:set.apply( {name: "jay"}, [{age: 10}]); // 返回 {name: "jay", age: 10}
  2. 寫一個相似 Math.max 和 min 的函數,不過接收的不是數字而是運算。前兩個參數必須是數字,然後面的參數你要將其轉化爲一個函數數組。下面提供一個方便你上手理解的示例:
function operate() { if (arguments.length < 3) { throw new Error("至少要三個參數"); } if (typeof arguments[0] !== 'number' || typeof arguments[1] !== 'number') { throw new Error("前兩個參數必須是數字"); } // 寫代碼 // 這是一個由函數組成的數組。你能夠用 call、apply 或者 bind。但不要直接遍歷參數而後直接塞到一個數組裏 var args; var result = 0; // 好了,開始吧,祝好運 } function sum(a, b) { return a + b; } function multiply(a,b) { return a * b; } console.log(operate(10, 2, sum, multiply)); // 必須返回 32 -> (10 + 2) + (10 * 2) = 32 

其餘文章和資料

假如我上面的解釋沒能讓你釋疑,那下面這些額外的資料能夠幫你更好地理解 bind 在 JavaScript 裏面是怎麼運做的。

  • 理解 JavaScript 函數 bind 的原型方法
  • Stackoverflow – 使用 JavaScript 的 bind 函數
  • JavaScript 中 call(), apply() 和 bind() 如何使用
  • 一看就懂 —— JavaScript 的 .call() .apply() 和 .bind()

我還強烈推薦你去學習 JavaScript 原型鏈,不單是由於裏面用到大量的 this 關鍵詞,並且它仍是 JavaScript 實現繼承的標準方式。

下面列出一些幫你瞭解 this 如何使用的書籍:

  • 編寫高質量 JavaScript代碼的68個有效方法:雖然是本古董,但此書確實寫得挺好並且還提供了簡單易懂的示例,教你怎麼用好 this、apply、call 和 bind 來寫出好代碼。書的做者是 TC39 的一個成員 Dave Hermann,因此你大可放心,他對 JavaScript 確定理解深入。
  • 你不知道的 JS —— this 和對象原型:Kyle Simpson 以一種清晰明瞭、對初學者很友好的方式,解釋了對象和原型是怎麼相互影響運做起來的,寫得很棒!

總結

考慮到 this 關鍵詞已經用到了難以計量的代碼中,它是 JavaScript 中咱們不得不聊的話題。

一個優秀的藝術家確定精於工具的使用。做爲一個 JavaScript 開發者,怎麼用好它的特性是最最重要的。

若是你想看到一些從特定角度對 this 關鍵詞深刻剖析的文章或者更多的代碼,請別忘了告訴我。這些可能的角度能夠是(但不限於)下面這些:

  • this 和 new 關鍵詞。
  • JavaScript 的原型鏈。
  • this 和 JavaScript 的類。

做者:老教授連接:https://juejin.im/post/5aefe76e6fb9a07abc29d4a1來源:掘金著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。

相關文章
相關標籤/搜索