「前端面試題系列4」this的原理以及用法

圖片描述

這是前端面試題系列的第 4 篇,你可能錯過了前面的篇章,能夠在這裏找到:javascript

在前端的面試中,常常會問到有關 this 的指向問題。最近,朋友Z 向我求助說,他一看到 this 的題目就犯難,搞不清楚 this 究竟指向了誰。我爲他作了解答,並整理成了這篇文章,但願能幫到有須要的同窗。html

一道面試題

朋友Z 給我看了這樣一道題:前端

var length = 10;

function fn () {
    console.log(this.length);
}
 
var obj = {
    length: 5,
    method: function (fn) {
        fn();
        arguments[0]();
    }
};
 
obj.method(fn, 1);

問:瀏覽器的輸出結果是什麼?java

它的答案是:先輸出一個 10,而後輸出一個 2git

讓咱們來解析一下緣由:github

  • 在咱們這道題中,雖然 fn 做爲 method 的參數傳了進來,但它的調用者並不受影響,任然是 window,因此輸出了 10。
  • arguments[0]();這條語句並不常見,可能你們有疑惑的點在這裏。 其實,arguments 是一種特殊的對象。在函數中,咱們無需指出參數名,就能訪問。能夠認爲它是一種,隱式的傳參形式
  • 當執行 arguments[0](); 時,其實調用了 fn()。而這時,fn 函數中 this 就指向了 arguments,這個特殊的對象。obj.method 方法接收了 2 個參數,因此 arguments 的 length,很顯然就是 2 了。

改造一下

再來,很多同窗對 this 的指向感到疑惑,是由於 this 並無指向咱們預期的那個對象。面試

就像這道題,從語義上來看,咱們指望 fn() 輸出的是 obj 本身的 length,也就是 5,而不是 10。那麼若是要獲得 5 的結果,咱們該如何修改這段代碼呢?segmentfault

其實只要多作一步處理就好。就是讓 this 指向 obj 本身。這裏,咱們能夠用 call 來改變 this 的指向,像下面這樣:瀏覽器

var length = 10;

function fn () {
    console.log(this.length);
}

var obj = {
    length: 5,
    method: function (fn) {
        // 在這裏用call 將 this 指向 obj 本身
        fn.call(this);
    }
};
 
obj.method(fn);

輸出的結果就是 5 了,搞定。數據結構

看吧,this 也沒那麼複雜吧,咱們只須要一些簡單的操做,就能控制 this 的指向了。那麼,問題來了,爲何有時候 this 會失控呢?

其實,這與 this 機制背後的原理有關。不過別急,讓咱們從理解 this 的基本概念開始,先來看看 this 究竟是什麼?

this 是什麼?

this 是 JavaScript 中的一個關鍵字。它一般被運用於函數體內,依賴於函數調用的上下文條件,與函數被調用的方式有關。它指向誰,則徹底是由函數被調用的調用點來決定的。

因此,this,是在運行時綁定的,而與編寫時的綁定無關。隨着函數使用場合的不一樣,this 的值也會發生變化。可是有一個總的原則:那就是this 總會指向,調用函數的那個對象

爲何要用this?

從概念上理解起來,彷佛有點費勁。那咱們爲何還要使用 this 呢?用了 this 會帶來什麼好處?

讓咱們先看下面這個例子:

function identify() {
    return this.name.toUpperCase();
}

var me = {
    name: "Kyle"
};

var you = {
    name: "Reader"
};

identify.call( me ); // KYLE
identify.call( you ); // READER

一開始咱們可能太不明白爲什麼這樣輸出。那不如先換個思路,與使用 this 相反,咱們能夠明確地將環境對象,傳遞給 identify()。像這樣:

function identify(context) {
    return context.name.toUpperCase();
}
identify( you ); // READER

在這個簡單的例子中,結果是同樣的。咱們能夠把環境對象直接傳入函數,這樣看來比較直觀。可是,當模式愈加複雜時,將執行環境做爲一個明確的參數傳遞給函數,就會顯得很是混亂了

而 this 機制,能夠提供一種更優雅的方式,來隱含地「傳遞」一個對象的引用,這會使得 API 的設計更加地乾淨,複用也會變得容易。

this 的原理

明白了 this 的概念以後,不經讓我好奇,爲什麼 this 指向的就是函數運的執行環境呢?

以前,看到了 阮老師 的一篇文章,十分透徹地分析了 this 的原理。我根據本身的理解,整理以下。

不少教科書會告訴你,this 指的是函數運行時所在的環境。可是,爲何會這樣?也就是說,函數的運行環境究竟是怎麼決定的?

理解 this 的原理,有助於幫咱們更好地理解它的用法。JavaScript 語言之因此有 this 的設計,跟內存裏面的數據結構有關係

來看一個簡單的示例:

var obj = { foo: 5 };

上面的代碼將一個對象賦值給變量 obj。JavaScript 引擎會先在內存裏面,生成一個對象 { foo: 5 },而後把這個對象的內存地址賦值給變量 obj。

也就是說,變量 obj 其實只是一個地址。後面若是要讀取 obj.foo,引擎先從 obj 拿到內存地址,而後再從該地址讀出原始的對象,返回它的 foo 屬性。

這樣的結構很清晰,但若是屬性的值是一個函數,又會怎麼樣呢?好比這樣:

var obj = { foo: function () {} };

這時,JavaScript 引擎會將函數單獨保存在內存中,而後再將函數的地址賦值給 foo 屬性的 value 屬性。

能夠看到,函數是一個單獨的值(以地址形式賦值),因此才能夠在不一樣的環境中執行。

又由於,JavaScript 容許在函數體內部,引用當前環境的其餘變量。因此須要有一種機制,可以在函數體內部得到當前的運行環境(context)。因此,this就出現了,它的設計目的就是在函數體內部,指代函數當前的運行環境

this 的用法

在理解了 this 的原理以後,咱們用下面的 5 種狀況,來討論 this 的用法。

一、純粹的函數調用

這是函數的最一般用法,屬於全局性調用,所以 this 就表明全局對象 window。

function test(){
    this.x = 1;
    console.log(this.x);
}
test(); // 1

二、做爲對象方法的調用

函數做爲某個對象的方法調用,這時 this 就指這個上級對象。

function test(){
    console.log(this.x);
}
var o = {};
o.x = 1;
o.m = test;
o.m(); // 1

三、做爲構造函數調用

所謂構造函數,就是經過這個函數生成一個新對象(object)。這時,this 就指這個新對象。

function test(){
    this.x = 1;
}
var o = new test();
console.log(o.x); // 1

四、apply調用

apply() 是函數對象的一個方法,它的做用是改變函數的調用對象,它的第一個參數就表示改變後的調用這個函數的對象。所以,this 指的就是這第一個參數。

var x = 0;
function test() {
    console.log(this.x);
}
var o = {};
o.x = 1;
o.m = test;
o.m.apply(); //0

apply() 的參數爲空時,默認調用全局對象。所以,這時的運行結果爲0,證實this指的是全局對象。

它與上文中提到的 call 的做用是同樣的,只是寫法上略有區別。因爲篇幅緣由,我會另啓一篇,來詳述它們的用法。

五、箭頭函數

ES6 中的箭頭函數,在大部分狀況下,使得 this 的指向,變得符合咱們的預期。但有些時候,它也不是萬能的,一不當心的話,this 一樣會失控。

由於篇幅內容較多,我會另寫一篇文章來介紹。

另外一道面試題

最後,讓咱們來鞏固一下 this 的概念和用法。來看一道面試題:

window.val = 1;
 
var obj = {
    val: 2,
    dbl: function () {
        this.val *= 2; 
        val *= 2;       
        console.log('val:', val);
        console.log('this.val:', this.val);
    }
};

 // 說出下面的輸出結果
 obj.dbl();
 var func = obj.dbl;
 func();

答案是輸出:2 、 4 、 8 、 8

解析:

  • 執行 obj.dbl(); 時, this.val 的 this 指向 obj,而下一行的 val 指向 window。因此,由 window.val 輸出 2,obj.val 輸出 4 。
  • 最後一行 func(); 的調用者是 window。 因此,如今的 this.val 的 this 指向 window。
  • 別忘了剛纔的運算結果,window.val 已是 2 了,因此如今 this.val *= 2; 的執行結果就是 4。
  • val *= 2; 的執行結果,就是 8 了。 因此,最終的結果就是輸出 8 和 8 。

總結

this 指代了函數當前的運行環境,依賴於函數調用的上下文條件,在運行時纔會進行綁定。請牢記總原則:this 總會指向,調用函數的那個對象

參考文獻

PS:歡迎關注個人公衆號 「超哥前端小棧」,交流更多的想法與技術。

相關文章
相關標籤/搜索