前端進階課程之this指向

沉默抒懷者發表於2018-11-12

目錄:

  1. javascript中為什麼會有this這樣的語法
  2. 怎樣判斷this指向?
  3. settimeout或事件回撥函式,箭頭函式等特殊情況下this指向

一: 曾經學習this指向的慘痛經歷

在學習本課程的this指向之前,想和大家分享一下學習this的悲慘經歷,可能大家也都曾經看過很多很多關於this指向的文件,各種個樣,琳琅滿目,大部分說的都是常見那幾種情況下,this是如何指向的,例如,new的情況下this指向例項,又或者直接呼叫指向window等等,當時看了好像有點豁然開朗了,但是做題的時候,又說有特殊情況,按之前理解的又走不通了,或者過段時間,又忘記啦。

其實說到底,還是沒有真正理解this到底是如何運作的?接下來的文章希望可以帶領大家去真正理解this到底怎麼用?

二: javascript中為什麼會用this這樣的語法糖。

this的設計,完全是由於javascript中的資料(或者準確的說是函式)在記憶體中的儲存方式所決定的。

具體來說:函式在記憶體中是佔據一個獨立的堆記憶體,那麼其他地方呼叫這個函式其實是有一個地址指向該堆記憶體,因為,函式可能會有很多中呼叫方式,可能是直接呼叫,也可能是通過某個物件呼叫,不同的呼叫方式,那麼在函式內部怎麼區分呢?

有的人可能會說,採用call和apply啊,這樣就可以手動的傳入一個context,這樣函式中就可以根據這個context去區分是哪種呼叫方式了,這位同學真聰明,js本身就是這樣去處理的,但是這樣有個什麼問題呢?每次都需要我們手動的繫結一個context傳入函式裡,可以省略這一步嗎?可以,怎麼省略呢?

this啊,啦啦啦,this登場了,有了this,我們就不需要顯示的繫結context,而是函式在呼叫的時候,會預設在函式內部生成一個this,它會隱式的幫我們指定一個物件。當然,這樣就帶來了一個問題,不同的呼叫情況,this指向的內容是不一樣的(這也就衍生除了大家最頭疼的問題,到底怎麼判斷this的指向?)

參考文件:www.ruanyifeng.com/blog/2018/0…

三:如何判斷this的指向?

不同的函式呼叫方式,this指向都會不同

  1. 那麼首先,我們來說一下函式的呼叫: 記住函式只有一種呼叫方式:fn.call() 或者fn.apply();(下文都以call為例解釋) 其他方式都是基於該方式的語法糖
function fn(){
    
}
var obj = {
    fn: fn
}
我們大家最常見的呼叫方式:
fn();
obj.fn();

其實 轉換一下:
fn() === fn.call() === fn.call(undefined) //此時傳入的是undeifined,為什麼說函式中this指向window呢?後面有解答
obj.fn() === fn.call(obj)

而call和apply的第一個引數不就是傳入的context.
注意:
obj1.obj2.fn() <===> fn.call(obj1.obj2);
此時fn函式裡this指向的是obj1.obj2,並不是obj1

複製程式碼

所以,以後怎麼判斷this指向呢?將所有函式呼叫的方式統一轉換成call方式去呼叫,看看第一個引數到底是什麼就可以啦

參考文件:www.zhihu.com/question/19…

四:常見細節說明:

  1. fn.call() 在非嚴格模式下,傳入unefined,或者null時,函式內部this指向window.但是在嚴格模式下,call方法傳什麼,this指向什麼
function fn () {
    console.log(this); // 此時this === window
}
function gn () {
    "use strict";
    console.log(this); //此時 this === undefined
}
fn() <===> fn.call();
gn() <===> gn.call();

複製程式碼
  1. 回撥函式中的this指向什麼? settimout和setinterval回撥函式中的this永遠指向window(前提是回撥函式沒有使用箭頭函式,如果使用了箭頭函式,則回撥函式中的this指向與非同步函式所在的詞法作用域中的this保持一致)
//回撥函式為普通函式
function fn(){
   setTimeout(function(){
       console.log(this.a);//此時this.a === window.a 為undefined
   },10)
}
var obj = {
   a: 3,
   f: fn
}
obj.f();

//回撥函式為箭頭函式
function fn(){
   setTimeout(() => {
       console.log(this.a); // 此時this.a === obj.a 為3
   },10)
}
var obj = {
   a: 3,
   f: fn
}
obj.f();
複製程式碼
  1. 箭頭函式中的this指向什麼? 箭頭函式比較特殊,它的this不是由函式的呼叫位置決定,而是由它的詞法作用域中的this所決定。也就是說它的this不是在函式呼叫時確定,而是在函式編寫時就已經決定,即它的詞法作用域
var obj = {
    fn: function () {
        console.log(this); // 此時this === obj    
    },
    gn: () => {
        console.log(this); // 此時this === window
    }
}
obj.fn();
obj.gn();
複製程式碼
  1. this什麼時候可以省略?

這個可能是我自己在學習過程中產生的一個問題,就是有時候看程式碼好像this可以省略寫,但是我們分析的時候,還是按有this的情況去分析,實際上是我可能混淆了,只要記住一句話:this什麼時候也不可以省略,如果沒寫,就是沒寫,並不存在什麼隱式的呼叫this的說法。

那麼有的同學可能問了,在window中就可以省略啊。

例如如下程式碼:
var a = 10;
console.log(a === this.a); //true
這個時候,是因為在全域性作用域下,window可以省略不寫,
所以正常思路是a === window.a, 而同時在全域性作用域下this === window, 所以 a === this.a;

那麼如果是在window下呼叫函式:
function fn () {
    console.log(this);
}
fn() === fn.call()//此時函式內this === window //因為此時是由於call如果傳入undefined,則函式內預設繫結window,
window.fn() === fn.call(window)//此時this === window,因為call方法顯式的傳入了window。

也就是說,在非嚴格模式下:fn() === fn.call() === window.fn() === fn.call(window);

但是在嚴格模式下
function gn() {
    'use strict';
    console.log(this);
}
gn() === gn.call()// 此時this指向undefined
window.gn() === gn.call(window); //此時this指向window 

也就是說,嚴格模式下,傳入undefined,瀏覽器沒有預設繫結window,但是不管是嚴格模式,還是非嚴格模式下,如果是call顯示的傳入一個物件,那麼函式中this就一定指向該物件。

複製程式碼

五:總結:

  1. 以後在遇到判斷this指向的時候,統一全部轉換成call方法去分析,同時結合上面說到的回撥函式,箭頭函式,去分析即可。

  2. this 任何時候都不可以省略,全域性作用域下只是因為this === window,所以看上去可以不寫this,

  3. 在全域性作用域下,直接呼叫函式fn和手動通過window.fn()呼叫函式,在非嚴格模式下,可以認為是等價的,但是在嚴格模式下,函式內的this指向就不同了。

參考文件: developer.mozilla.org/zh-CN/docs/…

相關文章