javascript基礎修煉(2)——What's this(上)

来源:https://www.cnblogs.com/dashnowords/archive/2018/08/02/9410495.html
-Advertisement-
Play Games

開發者的javascript造詣取決於對【動態】和【非同步】這兩個詞的理解水平。 [TOC] 一.this是什麼 是javascript關鍵字之一,是javascript能夠實現 面向對象編程 的核心概念。用得好能讓代碼優雅高端,風騷飄逸,用不好也絕對是坑人坑己利器。我們常常會在一些資料中看到對 的描 ...


目錄

開發者的javascript造詣取決於對【動態】和【非同步】這兩個詞的理解水平。

一.this是什麼

this是javascript關鍵字之一,是javascript能夠實現面向對象編程的核心概念。用得好能讓代碼優雅高端,風騷飄逸,用不好也絕對是坑人坑己利器。我們常常會在一些資料中看到對this的描述是:

this是一個特殊的與Execution Contexts相關的對象,用於指明當前代碼執行時的Execution Contextsthis在語句執行進入一個Execution Contexts時被賦值,且在代碼執行過程中不可再改變。
註:Execution Contexts也就是我們常聽到的"上下文""執行環境"

看不懂?看不懂就對了,我也看不懂。
對於this的指向,我們常會聽到這樣一個原則——this是一個指針,指向當前調用它的對象。但實際使用中,我們卻發現有時候很難知道當前調用它的是哪個對象,從而引發了一系列的誤用和奇怪現象。

今天,我們就換一種思路,試試如何從語言的角度一步一步地去理解this,你會發現:
只要你能聽懂中國話,就意味著你能理解this

二.近距離看this

2.1 this的語法意義

javascript是一門程式設計語言,也就是說,它是一種語言,是語言,就有語法特性。如果拋開this的原理和編程中的用法,僅從語文的層面去理解,它的本質就是代詞。什麼是代詞?漢語中的,,,你們,我們,他們這一類的詞語就是代詞。代詞並不具體指某一個具體的事物,但結合上下文,就可以知道這類詞語代替的是誰。
比如下麵這幾句描述的語境:

  • 大爺是趙本山
    • 請問:誰大爺是趙本山?
    • 沒法回答,因為沒有上下文約束,此處的可能指任何人。
  • 李雷來頭可不小,大爺是趙本山
    • 請問:誰大爺是趙本山?
    • 很容易回答,因為前一句話使得我們能夠得知當前上下文中,"他"指的就是"李雷"
  • ___來頭可不小,大爺是趙本山
    • 請問:誰大爺是趙本山?
    • 此處空格填誰,誰大爺就是趙本山。

小結一下:

代詞,用於指代某個具體事物,當結合上下文時,就可以知道其具體的指向。換句話說,有了上下文時,代詞就有了具體的意義。this在javascript語言中的意義,就如同代詞在漢語中的意義是一樣的。

2.2 不同作用域中的this

在ES6出現前,javascript中的作用域只分為全局作用域和函數作用域兩種。(以下部分暫不討論嚴格模式)。

  • 全局作用域中使用this

全局作用域中的this是指向window對象的,但window對象上卻並沒有this這個屬性:

  • 函數作用域使用this

函數作用域中的this也是有指向的(本例中指向window對象),我們知道函數的原型鏈是會指向Object的,所以函數本身可以被當做一個對象來看待,但遺憾的是函數的原型鏈上也沒有this這個屬性:

綜上所述,this可以直觀地理解為:

this與函數相關,是函數在運行時解釋器自動為其賦值的一個局部常量。

2.3 javascript代碼編寫方式

a.不使用this

這是有可能發生的。很多初學者會發現,自己在編寫javascript代碼時並沒有用到this,但是也並不影響自己編寫代碼。前面提到過上下文信息的意義在於讓代詞明確其指向,那麼如果一段話的上下文中並沒有使用代詞,在語文中我們就不需要聯繫上下文就能理解這段話;同理,如果函數的函數體中並沒有使用this關鍵字來指代任何對象,或者不需要關註其調用對象,那實際上就算不確定this的指向,函數的執行過程也不會有歧義。

/**
 *數據加工轉換類的函數,對開發者來說更關註結果,而並不在乎是誰在調用。
*/
function addNumber(a,b) {
    return a + b;
}

無論是電腦對象調用addNumber方法,或是算盤對象調用addNumber方法,甚至是人類對象通過心算調用addNumber方法,都無所謂,因為我們關註的是結果,而不是它怎麼來的。

b.不使用函數自帶的this

有時候我們編寫的代碼是需要用到一些關於調用對象的信息的,但由於不熟悉this的用法,許多開發者使用了另一種變通的方式,也就是顯式傳參。比如我們在一個方法中,需要打出上下文對象的名字,下麵兩種編寫方式都是可以實現的。

//方式一.使用this
invoker.whoInvokeMe = function(){
    console.log(this.name);
}

//方式二.不使用this
function whoInvokeMe2(invoker){
    console.log(invoker.name);
}

方式二的方式並不是語法錯誤,可以讓開發者避開了因為對this關鍵字的誤用而引發的混亂,同樣也避開了this所帶來的對代碼的抽象能力和簡潔性,同時會造成一些性能上的損失,畢竟這樣做會使得每次調用函數時需要處理更多的參數,而這些參數本可以通過內置的this獲取到。

c.面向對象的編程

提到this,必然會提到另一個詞語——面向對象。"面向對象"是一種編程思想,請暫時拋開封裝,繼承,多態等高大上的修飾詞帶來的負擔,純粹地感受一下這種思想本身。有的人說"面向對象"賦予了編程一種哲學的意義,它是使用程式語言的方式對現實世界進行的一種簡化抽象,現實世界的一個用戶,一種策略,一個消息,某個演算法,在面向對象的世界里均將其視為一個對象,也就是哲學意義上的無分別,每一個對象都有其生命周期,它怎麼來,要做什麼,如何消亡,以及它與萬物之間的聯繫。

面向對象的思想,是用程式語言勾勒現實世界框架的方式之一,它的出現不是用來為難開發者的,而是為了讓開發者能以更貼近日常生活的認知方式來提升對程式語言的理解能力。

2.4 如果沒有this

我們來看一下如果javascript中不使用this關鍵字,對程式編寫會造成什麼影響呢?
我們先來編寫一段簡單的定義代碼:

    //假設我們定義一個人的類
    function Person(name){

    }
     
    // 方法-介紹你自己(使用this編寫)
    Person.prototype.introduceYourselfWithThis = function () {
        if (Object.hasOwnProperty.call(this, 'name')) {
           return `My name is ${this.name}`;
        } 
        return `I have no name`;
    }

    // 方法-介紹你自己(不使用this編寫)
    Person.prototype.introduceYourself = function (invoker) {
        if (Object.hasOwnProperty.call(invoker, 'name')) {
            return `My name is ${invoker.name}`;
        }
        return `I have no name`;
    }

    //生成兩個實例,併為各自的name屬性賦值
    var liLei = new Person();
    liLei.name = 'liLei';
    var hanMeiMei = new Person();
    hanMeiMei.name = 'hanMeiMei';

在上面的簡單示例中,我們定義了一個不包含任何實例屬性的類,並使用不同的方式為其定義介紹你自己這個方法,第一種定義使用常規的面向對象寫法,使用this獲取上下文對象,獲取實例的name屬性;第二種定義不使用this,而是將調用者名稱作為參數傳遞進方法。
我們在控制台進行一些簡單的使用:

那麼這兩種不同的寫法區別到底是什麼呢?

  • 函數實際功能的變化
    從上面的示例中不難看出,當開發中不使用this時,需要開發者自行傳入上下文對象,並將其以參數的形式在函數執行時傳入,如果傳入的invoker 對象和 this的指向一致,那麼結果就一致,如果不一致,則會造成混亂。
    • 從編碼角度來看
      introduceYourselfWithThis()方法只是introduceYourself(invoker)方法的特例(當this === invoker時)。
    • 從方法的含義來看
      定義者希望實現自我介紹功能而編寫了introduceYourself()方法,可是使用者在閱讀到introduceYourself()的源碼時看到的代碼表達的意義是:**我告訴你一個名字,你把它填在'My name is __'這句話中再返回給我。而不是一個與調用對象有著緊密聯繫的自我介紹**動作。
  • 畫蛇添足的參數傳遞
    在正確的使用過程中,thisinvoker 的指向是一致的,形參invoker的定義不僅增加了函數使用的複雜度,也增加了函數運行的負擔,卻沒有為函數的執行帶來任何新的附加信息。

  • 重覆的雷同代碼
    如果編碼中不使用this,也就相當於漢語中不使用代詞,那麼我們就需要在每一個獨立的句子中使用完整的信息。為了使introduceYourself()方法能夠正確的執行,我們需要在每一個實例生成後,為其綁定確切的實例方法,即:

    var liLei = new Person();
    liLei.name = 'liLei';
    //定義實例方法
    liLei.introduceYourself = function (){
        return `My name is liLei`;
    };
   
    var hanMeiMei = new Person();
    hanMeiMei.name = 'hanMeiMei';
    //定義實例方法
    hanMeiMei.introduceYourself = function (){
        return `My name is hanMeiMei`;
    }

即時不使用this,你也不會直接陷入無法編寫javascript代碼的境地,只是需要將所有的定義和使用場景全部具體化, 需要手動對所有的具體功能編寫具體實現,也就是"面向過程"的編程。

================================我是華麗的分割線======================================

【輕鬆一刻】

話說赤壁之戰後,一日閑來無事,孔明與劉關張三兄弟一起喝酒。孔明說,我出三道題考考各位學識修養,如何啊?三兄弟舉手贊同。
孔明:第一題,主公,赤壁之戰發生在哪裡?
劉備:赤壁啊
孔明:答對了,主公果然厲害。第二題,關將軍,雙方有多少人參戰?
關羽:聯軍5萬,曹軍20餘萬。
孔明:答對了,關將軍也是智勇雙全啊。最後一題,他們分別是誰?
張飛:我......我靠

願你能夠掌握this,不要在自己的代碼里搞出他們分別是誰的尷尬,小心被隊友活埋。

================================我是華麗的分割線======================================

三. this的一般指向規則

javascript中有四條關於this指向的基本規則。今天,我們將一起通過【碼農視角】【語文老師視角】來分別解讀這些規則,你會發現他們理解起來其實很自然。

規則1——作為函數調用時,this指向全局對象

瀏覽器中的全局對象,指的是window對象。這一規則指的就是我們在全局作用域或者函數作用域中使用function關鍵字直接聲明或使用函數表達式賦值給標識符的方式創建的函數。為了在調用時在記憶體中找到所聲明的方法,我們需要一個標識符來指向它的位置,具名函數可以通過它的名字找到,匿名函數則需要通過標識符來找到。作為函數調用的實質,就是通過方法名直或標識符找到函數並執行它。

一般什麼樣的函數我們會這樣定義呢?
就是那些不關註調用者的函數,比如上面舉例的addNumber()方法,這類函數往往是將一步或幾步業務邏輯組合在一起,起一個新的名字便於管理和重用,而並不關註使用者到底是誰。

語文老師解讀版
很好理解,當你想描述一個動作卻不知道或者不關註具體是誰做的,代詞就指向有的人
比如臧克家同學在作文里寫的這樣:
有的人活著,但是他已經死了;
有的人死了,但是他還活著;
上文中的指誰?指有的人;那有的人是誰?隨便,愛誰誰。

規則2——作為方法調用時,this指向上下文對象

上文中我們看到函數的作用域鏈上是包含Object對象的,所以函數可以被當做對象來理解。當函數作為對象被賦值在另一個對象的屬性上時,這個對象的屬性值里會保存函數的地址,因為用函數作為賦值運算的右值時是一個引用類型賦值。如果這個函數正好又是一個匿名函數,那麼執行時只能通過對象屬性中記錄的地址信息來找到這個函數在記憶體中的位置,從而執行它。所以當函數作為方法調用時,this中包含的信息的本質是這個函數執行時是怎麼被找查找到的。答案就是:通過this所指向的這個對象的屬性找到的。

一般什麼樣的函數我們會這樣定義呢?
作為方法定義的函數,往往是另一個抽象合集的具體實現。比如前例的addNumber()這個方法,只是將兩個數字相加這樣一個抽象動作,至於是誰通過什麼方式來執行這個計算過程,無所謂,它可以概括所有對象將兩個數字相加並給出結果這一動作。可如果它作為一個對象方法來調用時,就有了更明確的現實指向意義:

  • Computer.addNumber()表達了電腦通過軟硬體聯合作用而給出結果的過程
  • Calculator.addNumber()表達了計算器通過簡易硬體計算給出結果的過程
  • Abacus.addNumber()表達了算盤通過加減珠子的方式給出結果的過程
  • ...

語文老師解讀版
當你想知道一個代詞具體指的是誰時,當然需要聯繫上下文語境進行理解。

規則3——作為構造函數使用時,this指向生成的實例

作為構造函數使用,就是new + 構造函數名的方式調用的情況。
js引擎在調用new操作符的邏輯可以用偽代碼表示為:

new Person('liLei') = {
    //生成一個新的空對象
    var obj = {}; 
    //空對象的原型鏈指向構造函數的原型對象
    obj.__proto__ = Person.prototype; 
    //使用call方法執行構造函數並顯式指定上下文對象為新生成的obj對象
    var result = Person.call(obj,"liLei"); 
    // 如果構造函數調用後返回一個對象,就return這個對象,否則return新生成的obj對象
    return typeof result === 'object'? result : obj;
}

暫不考慮構造函數有返回值的情況,那麼很容易就可以明白this為什麼指向實例了,因為類定義函數在執行的時候顯式地綁定了this為新生成的對象,也就是調用new操作符後得到的實例對象。

語文老師解讀版
有些同學喜歡抄襲,抄襲這個動作可以描述為:"把一份作業Copy一遍,在最後寫上自己的名字。"。如果李雷是喜歡抄襲的人之一,那麼他就掌握了"抄襲"這個方法,那你覺得他每次抄完作業後在署名的地方應該寫自己的名字"李雷"還是寫這一類人的總稱"喜歡抄襲的人"呢?
抬杠的那個同學,我記住你了!放學別走!

規則4——使用call/apply/bind方法顯式指定this

call/bind/apply這三個方法是javascript動態性的重要組成部分,後續的篇章會有詳細的講解。這裡只看一下API用法,瞭解一下其對於this指向的影響:

  • func.call(this, arg1, arg2...)
  • func.apply(this, [arg1, arg2...])
  • func.bind(this [, arg1[, arg2[, ...]]])

這個規則很好理解,就是說函數執行時遇到函數體里有this的語句都用顯式指定的對象來替換。

語文老師解讀版
就是直接告訴你下文中的代詞指什麼,比如:×××憲法(以下簡稱"本法"),那讀者當然就知道後面所說的"本法"指誰。

四. 基本規則示例

為了更清晰地看到上面兩條原則的區別,我們來看一個示例:

        var heroIdentity = '[Function Version]Iron Man';
        
        function checkIdentity(){
            return this.heroIdentity;
        } 

        var obj = {
            name:'Tony Stark',
            heroIdentity:'[Method Version]Iron Man',
            checkIdentityFromObj:checkIdentity
        }

        function TheAvenger(name){
            this.heroIdentity = name;
            this.checkIdentityFromNew = checkIdentity;
        }

        var tony = new TheAvenger('[New Verison]Iron Man');

        
        console.log('1.直接調用方法時結果為:',checkIdentity());
        console.log('2.通過obj.checkIdentityFromObj調用同一個方法結果為:',obj.checkIdentityFromObj());
        console.log('3.new操作符生成的對象:',tony.checkIdentityFromNew());
        console.log('4.call方法顯示修改this指向:',checkIdentity.call({heroIdentity:'[Call Version]Iron Man'}));

控制台輸出的結果是這樣的:

同一個方法,同一個this,調用的方式不同,得到的結果也不同。

五. 後記

在基礎面前,一切技巧都是浮雲。

如果認為明白了this的基本規則就可以為所欲為,那你就真的too young too simple了。
瞭解了基本指向規則,只能讓你在開發中自己儘可能少挖坑或者不挖坑。但是想要填別人的坑或者讀懂大師級代碼中簡潔優雅的用法,還需要更多的修煉和反思。實際應用中許多複雜的使用場景是很難一下子搞明白this的指向以及為什麼要指定this的指向的。
筆者將在《javascript基礎修煉(3)——What's this(下)》中詳細講述開發中千奇百怪的this。欲知後事如何,先點個贊先吧!

參考文章:
[1].js中的new()到底做了什麼
[2].ECMA-262-3 in detail. Chapter 1. Execution Contexts


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 原文:http://blog.itpub.net/9240380/viewspace-666622/ SQL> declare 2 v_sid v$session.sid%type; --定義如下兩個type類型,用於接收cursorv_serial# v$session.serial#%type; ...
  • 大數據分析如今已不能再稱之為新技術。大多數移動應用程式開發人員已經明白,他們需要挖掘他們的數據來積極獲取日常的見解。許多大型應用程式開發企業已經意識到,要在市場上不斷地發展和更新,必須採用大數據技術。亞馬遜,微軟,甲骨文等大型跨國公司已經採用了大數據解決方案來拓展業務,希望為消費者提供最好的服務。科 ...
  • 本文導讀:刪除表中的數據的方法有delete,truncate, 其中TRUNCATE TABLE用於刪除表中的所有行,而不記錄單個行刪除操作。TRUNCATE TABLE 與沒有 WHERE 子句的 DELETE 語句類似;但是,TRUNCATE TABLE 速度更快,使用的系統資源和事務日誌資源 ...
  • Preface MasterHA is a tool which can be used in MySQL HA architecture.I'm gonna implement it and have some tests depend on it later. Framework Hostnam ...
  • 一,效果圖。 二,代碼。 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>html基礎</title> </head> <body> <!--HTML標題--> <h1>這是h1標題</h1> <h2>這是h2標題</h2> < ...
  • 目標 BOM編程 window和document對象 window對象的屬性和方法 document對象的屬性和方法 JavaScript中對象的分類 1. 瀏覽器對象:window對象 2. window對象,這個對象的屬性和方法通常被統稱為BOM(Browser Object Model,瀏覽器 ...
  • 來自:http://www.dabaoku.com/texiao/caidan/201007156435.shtml# 大寶庫 css已經改動,但是js似懂非懂,明白大概原理但是實際操作為啥這麼寫還要仔細研究,先拿過來留作自己日常參考(如果有侵權請告知刪除) ...
  • 一、問題 近日,寫了一個ASP.Net項目,但是bootstrap-table在別人的電腦上顯示不出來,在自己的電腦上能顯示,有些瀏覽器也是能顯示,但部分瀏覽器就是顯示不出來。找了很多原因,最後有個老師和我說是內核版本的問題。 核心:雙核瀏覽器雙核主要是谷歌和IE,但如果某些瀏覽器其中有個內核的版本 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...