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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...