不得不說,要搞清楚this是需要一個前提的。
你首先得知道函數、對象、作用域等基本概念。
知道 call、apply、bind方法那再好不過了
當然學好語文是很重要的
需要知道第一人稱和第二人稱和第三人稱的區别
先來看一個新聞:
“我體内的惡魔已被鎖住了這麼多年,現在這種鎖鍊已經松了。”“我很害怕,很孤獨,很疑惑,我将要向導緻我的痛苦的根源——社會作出反擊,我想盡我所能地去傷害這個社會,然後死去。”
這段令人不寒而栗的文字,是美國北達科他州系列殺人案嫌犯約瑟夫·鄧肯于2005年5月11日寫下的。當人們在近兩個月後看到這段文字時,他已經将一個 5口之家的3人殘忍地殺害,并綁架了另外2名分别為8歲和9歲的孩子。
假設,你是一個警察,抓到了一個嫌疑犯然後你在他的家裡搜出這本日記,此時你作何感想?
你肯定會想,如果這是嫌疑人自己寫的,這不就等于認罪了嘛?
當然,犯罪嫌疑人也可以狡辯說,這日記根本不是我寫的,隻是我從網上摘抄的段子
我們試着把日記稍作改動,看看會有什麼效果變化。
他體内的惡魔已被鎖住了這麼多年,現在這種鎖鍊已經松了。他很害怕,很孤獨,很疑惑,他将要向導緻他的痛苦的根源——社會作出反擊,他想盡他所能地去傷害這個社會,然後死去。
注意到了嗎?
把第一人稱改成了第三人稱
這看上去根本不像日記,更像是一個小說故事。
為什麼一個字的改動會有這麼大差别呢?
帶着對這個問題的思考, 我們來開始今天this指向的學習。
this的英文含義
先看英文解釋 this: 這樣、這個
接下來看一段代碼
var obj = { show : function(){ console.log(this); } } obj.show();
結果很容易預測,打印obj對象本身
在JS中,this屬于一個關鍵字,也就是可以理解為,它是一個系統自帶命令
通常,我們把它的含義解釋為:當前對象
那麼問題來了: 當前對象到底是指誰呢?
在上面的代碼案例中,this代表的就是obj這個對象
接下來我們再看一段代碼
function show(){ console.log(this); } show();
結果打印window對象
如果你對這個打印結果感到奇怪,那麼可能你忽略了一個常識問題
window對象是可以省略不寫的!
所以,上面的代碼,實際上等價于:
function show(){ console.log(this); } window.show();
我再舉一個常見的例子,關于事件綁定
btn.onclick = function(){ console.log(this); } btn.onclick(); //手動調用函數 //除手動調用外,鼠标單擊按鈕也可以觸發函數執行
最終,無論是手動調用,還是單擊按鈕調用
打印結果都是btn對象
我們似乎總結出了一個this指向的規律this總是指向,調用該函數的對象
如果你的代碼結構是這樣的
對象.函數(); 那麼,函數裡的this,必然指向這個對象本身!
假設這個結論是成立的, 我們不妨來驗證一下我們的猜想!!
function show(){ console.log(this); } window.show(); //打印window對象 var obj1 = {}; obj1.show1 = show; obj1.show1(); //打印obj1對象 var obj2 = {}; obj2.show2 = obj1.show1; obj2.show2(); //打印obj2對象
從上面代碼的例子中,可以看出來
window對象和obj1對象和obj2對象,共享了一個函數 show
window.show == obj1.show1; //true window.show == obj2.show2; //true
三個對象,用了同一個函數
但打印出的this是各不相同的
window.show(); 打印出window對象
obj1.show1(); 打印出obj1對象
obj2.show2(); 打印出obj2對象
這似乎再一次印證了,我們剛才的猜想:
函數由哪個對象調用,this就指向哪個對象
科學是嚴謹的,得出結論之前,我們還是要反複驗證
再看一個例子:
btn.onclick = function(){ setTimeout(function(){ console.log(this); },0) } btn.onclick();
實際上,我隻是在原來代碼的基礎上,增加了一個延遲器,并且時間設為0
那麼打印出的this會不會有變化呢??
你可以先思考一番
通常按照直覺,我們會認為,延遲器隻是延緩了執行時間,打印結果依然還是btn對象,沒有變化
但經過測試發現,實際的打印結果,是 window對象
是我們剛才的猜想錯了嗎?
要解釋這個現象,我們得重新來觀察這段代碼
btn.onclick = function(){ //<----這個函數,用A來表示 setTimeout(function(){ //<----這個函數,用B來表示 console.log(this); },0) } btn.onclick();
注意代碼當中出現了兩個函數,我們分别起名字叫做函數A和函數B
按照我們剛才的猜想: 函數由哪個對象調用,this就指向哪個對象
所以,this指向會依賴它所在的函數
而這個函數,到底是 函數A還是函數B呢?
其實你不難從代碼中看的出來, this很明顯是在函數B中的
所以, 結果沒有打印出 btn, 現在我們也不感到奇怪了
因為, this已經不再函數 A的内部了,而是函數B的内部
你可能還要問,為什麼函數B裡的 this指向window呢?
這裡其實算是一個特例,傳入定時器的函數,由哪個對象調用,我們不得而知
這種情況,this就指向window
你暫時記住這個規律就好了,等你學完了作用域鍊,你就會明白其中的本質
回到我們開頭的新聞假設日記就是嫌疑人寫的。 但日記裡全是第三人稱。那麼 『 他 』到底是誰就很難說了
反過來如果日記裡用的都是第一人稱寫的。 那麼 『 我 』肯定指的是嫌疑人自己
JS函數當中的this關鍵字, 就相當于我們說話中的第一人稱代詞我
例如這樣一個例子:
A對B說:“我要殺了你!”
這裡的『我』指代A, 『你』指代B
B對A說:”我要弄死你!”
這裡的『我』指代B, 『你』指代A
所以你看,同樣的一個字,它可以指代任何人,關鍵看從誰的嘴裡說出來
function fn(){ //this, 就相當于中文裡的我 //不要上來就問this會指向誰 //我們必須搞清楚上下文環境,fn是誰調用的?(相當于這句話從誰的嘴裡說出來) //如果我們不能弄清楚這個問題,讨論this指向就沒有意義 console.log(this); }
到目前為止,我們差不多可以得出結論了下面用幾個練習最終驗證一下
var obj = { show: function(){ console.log(this); } }
上面的代碼,最終打印obj對象無論經過多少曲折,我們最終隻看一個結論,那就是:
this所在的函數,由哪個對象調用?
我把代碼進一步改造
function fn(){ console.log(this); } var obj = { show: fn } btn.onclick = function(){ window.setTimeout(function(){ obj.show(); }, 100); }
上面的代碼,最終打印還是obj對象
當然了,也總會有一些例外情況, 比如下面這個:
function m1(){ function m2(){ console.log(this); } m2(); } m1();
我們不禁要問,函數m2是由哪個對象調用的?
我們想盡了各種可能,最終發現都是錯的。
我們始終不知道這個m2由哪個對象調用,好像它就那樣執行了
而實際的打印結果呢?
不出意外,還是window對象
最後的結論1. 所有的this關鍵字,在函數運行時,才能确定它的指向2. this所在的函數由哪個對象調用,this就會指向誰3. 當函數執行時,沒有明确的調用對象時,則this指向window
由this衍生出的問題剛才遺留了一個問題沒有解決
btn.onclick = function(){ setTimeout(function(){ console.log(this); },0) } btn.onclick();
我們期待this指向btn,而this現在卻指向了window
這個問題該怎麼修複呢? 有很多辦法
如果你不知道call、apply、bind,那麼恐怕你隻能看得懂方法A
//方法A btn.onclick = function(){ var self = this; //使用變量保存this,self變量的值是不會随着環境改變的 setTimeout(function(){ console.log(self); },0) } btn.onclick();
//方法B btn.onclick = function(){ var self = this; //使用變量保存this function fn(){ //将代碼寫在一個函數fn中 console.log(this); } setTimeout(function(){ fn.call(self); //強行指定this為self對象 },0) } btn.onclick(); /* call方法的作用,是調用函數,同時指定this可以代表誰 例如 fn.call(obj) 意思就是 調用函數fn,并且this指向obj對象 */
//方法C btn.onclick = function(){ var self = this; //使用變量保存this function fn(){ //将代碼寫在一個函數fn中 console.log(this); } setTimeout(function(){ fn.apply(self); //使用apply方法調用函數,強行指定this為self對象 },0) } btn.onclick(); /* apply方法的作用,是調用函數,同時指定this可以代表誰 例如 fn.apply(obj) 意思就是 調用函數fn,并且this指向obj對象 */
//方法D btn.onclick = function(){ setTimeout(function(){ console.log(this); }.bind(this), 0 ) //使用bind方法,将定時器函數的this強行綁定為事件函數的this } btn.onclick(); /* bind方法的作用,是綁定函數的this,同時返回綁定後的新函數 例如 var fb = fn.bind(obj); window.fb(); 無論誰調用fb函數, 函數的this都會指向obj */
接下來的内容,請學完ES6的箭頭函數再來看吧1. 如何判斷箭頭函數的this?
因為箭頭函數不具備自己的this,所以非常簡單,假裝它不存在,就像這樣:
這下this的指向非常清晰了吧
2. 箭頭函數可以用call來改變this指向嗎?
不能!! 試圖改變箭頭函數的this是徒勞的。
最後一個特例:構造函數1. 什麼是構造函數?
假設有一個函數Fn, 我們有兩種方式來調用它
- 普通的調用 Fn()
- 配合new關鍵字來調用 new Fn()
第二種調用方式, 函數就變成了構造函數
---------------------------------------
注意,在構造函數中, 上面我們所講的結論,是不成立的!!
----------------------------------------
2. 那構造函數裡的this是誰呢?
請期待下一篇文章《構造函數與class》
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!