主頁 > 企業開發 > this指向終極解決方案(附帶手寫系結函式)

this指向終極解決方案(附帶手寫系結函式)

2022-12-22 06:45:41 企業開發

目錄
  • this指向
    • this定義
    • this的兩種系結方式
      • 默認系結
      • 顯式系結
        • new系結(具有顯式系結效果)
        • 隱式系結(具有顯式系結效果)
    • this系結優先級
    • 函式的this指向
    • this系結丟失的情況
    • 手寫callapplybind

this指向


this定義

this用于指定對當前物件的參考,


this的兩種系結方式

為什么說是兩種?在《你不知道的JavaScript(上卷)》一書中共提到了四種系結方式,如下:

  1. 默認系結

  2. 隱式系結

  3. 顯式系結

  4. new系結

實際上這四種系結方式有兩種方式重復了(隱式系結和new系結),我們在學習程序中應帶有辯證思維去看待問題,基于獺子細致的分析與總結,實際上this的系結可以認為只存在兩種方式:默認系結顯式系結,分析如下:

默認系結

在嚴格模式下,全域作用域的this物件會變為undefined,在非嚴格模式的普通函式若不作為物件方法,它的this系結都會自動系結到window全域物件上,即默認系結,

顯式系結

函式可以使用call/apply/bind等方法系結this物件,這種方式屬于強制系結措施,this的指向是可預見的(即系結誰就指向誰),我們重點談談他們的用法,

基本格式:函式名稱.call(要系結的物件, 引數串列)

call:接受一個引數串列,會立即執行,

apply:接受陣列形式的引數,會立即執行,

bind:接受一個引數串列,回傳原函式拷貝,不會立即執行,

若需應用,一般可以這樣思考:我們想要函式的this值指向哪個物件?

let a = { name: '小紅' }
function getName() {
    console.log(this.name)
}
getName() // 這里默認系結全域物件
getName.call(a) // 顯式系結物件a

new系結(具有顯式系結效果)

我們來看看new關鍵字的執行程序:

  1. 創建一個新的空物件
  2. 將建構式的原型賦給新創建物件(實體)的隱式原型
  3. 利用顯式系結將建構式的this系結到新創建物件并為其添加屬性
  4. 回傳這個物件

很顯然,這里的this的指向同樣是可預見的,

基于上面的執行程序,我們可以手寫實作一下(面試題):

function myNew(fn, ...args) { // 建構式作為引數
    let obj = {}
    obj.__proto__ = fn.prototype
    fn.apply(obj, args)
    return obj
}

一步一行代碼,是不是很簡潔,

注意:我們可以理解為new的程序應用了顯式系結

隱式系結(具有顯式系結效果)

關于隱式系結,這里提一下書里被翻譯過的作者原話:

另一條需要考慮的規則是呼叫位置是否有背景關系物件

其實隱式系結也可以理解為它應用了顯式系結,比如我們在利用模板字面量創建物件的時候,普通函式作為物件方法擁有與顯式系結同樣的效果,可以理解為已經執行了顯式系結這一程序,即函式會系結對應的實體物件,如下:

let a = {
    x: 10,
    y: function () {    // 作為物件方法,存在顯示系結效果,
        console.log(this)
    }
}
a.y()
// 輸出結果:a { x: 10 y: f } 即函式內部的this指向被系結的實體物件

this系結優先級

順序:顯式系結 > 默認系結

注意:箭頭函式本身沒有this,不會應用以上規則


函式的this指向

關于this指向,我們會更多的關注函式內部的this指向,一般而言會考察以下兩種型別的題目:

  • 自定義物件內部函式的this指向
  • 全域物件下函式的this指向

可以利用以下準則去解決this指向問題,實際上我們只需處理這兩種函式:

  1. 對于非嚴格模式下的普通函式會有兩個情況:

    1.1 作為物件方法,this會系結物件(執行new系結程序),

    1.2 不作為物件方法,在非嚴格模式下this默認系結window

  2. 箭頭函式沒有this,它只會繼承最近一層普通函式全域作用域this

注意:call/apply/bind方法不能改變箭頭函式的this指向,因為箭頭函式本身沒有this

我們盡量一次性解決所有this指向問題,首先設計這樣兩個結構,如下:

// 普通函式結構
function a(){}          // 普通函式宣告
setTimeout(function(){})// 內置函式
(function(){})()        // 立即執行函式
return function(){}     // 匿名函式
// 箭頭函式結構
let a = ()=>{}          // 箭頭函式宣告
setTimeout(()=>{})      // 內置函式
(()=>{})()              // 立即執行函式
return ()=>{}           // 匿名函式

利用上面的結構,分析第一種情況,如下:

let obj = {
    fun: function () { // 這里是普通函式

        console.log(this) // 普通作為物件方法定義,內部this指向obj

        // 以下普通函式都不作為物件方法定義,this全部指向window
        function a() { console.log(this) }; a();      // 普通函式宣告執行
        setTimeout(function () { console.log(this) });// 內置函式
        (function () { console.log(this) })();        // 立即執行函式
        return function () { console.log(this) };     // 匿名函式  

    },
    arr: () => { // 這里是箭頭函式

        console.log(this) // 箭頭函式的this俺規則繼承全域作用域指向window

        // 以下普通函式都不作為物件方法定義,this全部指向window
        function a() { console.log(this) }; a();      // 普通函式宣告執行
        setTimeout(function () { console.log(this) });// 內置函式
        (function () { console.log(this) })();        // 立即執行函式
        return function () { console.log(this) };     // 匿名函式  

    }
}
obj.fun()() // 會執行普通函式內部的所有普通函式和回傳的匿名函式
obj.arr()() // 會執行箭頭函式內部的所有普通函式和回傳的匿名函式

輸出結果:第一個thisobj物件,后面九個this全是window物件

接下來分析第二種情況,如下:

let obj = {
    fun: function () { // 這里是普通函式

        console.log(this) // 普通作為物件方法定義,this指向obj

        // 以下是箭頭函式,它的this按規則繼承最近一次普通函式即全部指向obj
        let a = () => { console.log(this) }; a();// 箭頭函式宣告
        setTimeout(() => { console.log(this) }); // 內置函式
        (() => { console.log(this) })();         // 立即執行函式
        return () => { console.log(this) };      // 匿名函式

    },
    arr: () => { // 這里是箭頭函式

        console.log(this) // 箭頭函式的this俺規則繼承全域作用域指向window

        // 以下是都是箭頭函式,它的this按規則繼承全域作用域全部指向window
        let a = () => { console.log(this) }; a();// 箭頭函式宣告
        setTimeout(() => { console.log(this) }); // 內置函式
        (() => { console.log(this) })();         // 立即執行函式
        return () => { console.log(this) };      // 匿名函式
    }
}
obj.fun()() // 會執行普通函式內部的所有箭頭函式和回傳的匿名函式
obj.arr()() // 會執行箭頭函式內部的所有箭頭函式和回傳的匿名函式

輸出結果:前五個this都是obj物件,后面五個this都是window物件

分析第三種情況,如下:

// 箭頭函式和普通函式放在全域中宣告,全部指向window
function fun() { console.log(this) }; fun();  // 普通函式宣告執行
setTimeout(function () { console.log(this) });// 內置函式
(function () { console.log(this) })();        // 立即執行函式

let arr = () => { console.log(this) }; arr(); // 箭頭函式宣告
setTimeout(() => { console.log(this) });      // 內置函式
(() => { console.log(this) })();              // 立即執行函式

輸出結果:六個this全是window物件

總結解題的關鍵點:首先判斷是箭頭函式還是普通函式,箭頭函式只會按規則繼承this指向,普通函式則要分兩種情況:作為物件方法和不作為物件方法:作為物件方法this會系結到物件上,不作為物件方法this則系結到window(非嚴格模式),

補充說明:普通函式作為物件方法實際上已經執行了new系結(可以看上面的手寫new程序),因此普通函式作為物件方法,它的this會指向物件,


this系結丟失的情況

函式別名(作為引數被傳遞或呼叫):例如obj.foo或手寫Promise中的resolve方法,我們可以理解為他是一個已經定義好的函式,它的this指向具體要看他在哪里使用,而且要分清楚它是普通函式還是箭頭函式,

obj.foo // 是一個函式
// 等價于下面我們定義好的普通函式或箭頭,如下
let foo = function{}{ console.log(this) }
let foo = ()=>{ console.log(this) }

補充說明:實際上this丟失只有這一種情況


手寫callapplybind

前置知識:ES6 剩余引數、Function.prototype原型方法定義,在呼叫時每個function可通過隱式原型(原型鏈)找到此方法,

Function.prototype.myCall = function (obj, ...args) {
    obj = obj === null || obj === undefined ? window : obj;
    return (() => {
        obj.method = this; //作為臨時方法傳遞給物件
        obj.method(...args);
        delete obj.method;
    })();
}

callapply區別,apply的接受引數為陣列形式

Function.prototype.myApply = function (obj, ...args) {
    obj = obj === null || obj === undefined ? window : obj;
    return (() => {
        obj.method = this; //作為臨時方法傳遞給物件
        obj.method(...args[0]);
        delete obj.method;
    })();
}

普通版:bind方法是硬系結,回傳值為原函式的拷貝,其this值不可再修改,

Function.prototype.myBind = function (obj, ...args1) {
    obj = obj === null || obj === undefined ? window : obj;
    return (...args2) => {
        this.apply(obj, args1.concat(args2));
    };
}

進階版:bind方法可支持new關鍵字

Function.prototype.myNewBind = function (obj, ...args1) { // 函式 1
    obj = obj === null || obj === undefined ? window : obj;
    let self = this;
    let fn = function (...args2) { // 函式 2
        return self.apply(this instanceof fn ? this : obj, args1.concat(args2));
    };
    fn.prototype = Object.create(self.prototype); // 維持其原型
    fn.prototype.constructor = fn
    return fn;
}

程序決議:

  1. 為什么要維持原形?

原生函式中bind在執行new操作時會保留其所系結函式的原型,我們希望在執行new關鍵字后myNewBind函式也能擁有同樣的效果,若沒有進行維持原型這一步操作,我們的new操作效果其實是把回傳的函式 2 作為建構式操作去生成實體,會丟失之前所系結函式的原型,無法實作繼承,

  1. 為什么使用instanceof

判斷當前物件是否為回傳的建構式所生成的實體物件,若是則認為執行了new關鍵字操作,回傳的建構式內部需要this代表新的實體物件,而不是舊的obj物件,

  1. 為什么要使用Object.create()

我們希望回傳的函式也有自己的獨立原型,直接將一個建構式原型賦給另一個建構式原型會使兩個原型物件的資料捆綁(參考值特點)在一起,即需要保持原型物件資料的獨立性,

Object.create()的運行程序手寫如下:

function createObject(obj) { // 引數為原型物件
    let temp = function () { };
    temp.prototype = obj;
    return new temp();
}

參考

你不知道的JavaScript (上卷)

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/540480.html

標籤:其他

上一篇:小程式開發與web開發的區別及特殊功能實作

下一篇:認識一下 Mobx

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • IEEE1588PTP在數字化變電站時鐘同步方面的應用

    IEEE1588ptp在數字化變電站時鐘同步方面的應用 京準電子科技官微——ahjzsz 一、電力系統時間同步基本概況 隨著對IEC 61850標準研究的不斷深入,國內外學者提出基于IEC61850通信標準體系建設數字化變電站的發展思路。數字化變電站與常規變電站的顯著區別在于程序層傳統的電流/電壓互 ......

    uj5u.com 2020-09-10 03:51:52 more
  • HTTP request smuggling CL.TE

    CL.TE 簡介 前端通過Content-Length處理請求,通過反向代理或者負載均衡將請求轉發到后端,后端Transfer-Encoding優先級較高,以TE處理請求造成安全問題。 檢測 發送如下資料包 POST / HTTP/1.1 Host: ac391f7e1e9af821806e890 ......

    uj5u.com 2020-09-10 03:52:11 more
  • 網路滲透資料大全單——漏洞庫篇

    網路滲透資料大全單——漏洞庫篇漏洞庫 NVD ——美國國家漏洞庫 →http://nvd.nist.gov/。 CERT ——美國國家應急回應中心 →https://www.us-cert.gov/ OSVDB ——開源漏洞庫 →http://osvdb.org Bugtraq ——賽門鐵克 →ht ......

    uj5u.com 2020-09-10 03:52:15 more
  • 京準講述NTP時鐘服務器應用及原理

    京準講述NTP時鐘服務器應用及原理京準講述NTP時鐘服務器應用及原理 安徽京準電子科技官微——ahjzsz 北斗授時原理 授時是指接識訓通過某種方式獲得本地時間與北斗標準時間的鐘差,然后調整本地時鐘使時差控制在一定的精度范圍內。 衛星導航系統通常由三部分組成:導航授時衛星、地面檢測校正維護系統和用戶 ......

    uj5u.com 2020-09-10 03:52:25 more
  • 利用北斗衛星系統設計NTP網路時間服務器

    利用北斗衛星系統設計NTP網路時間服務器 利用北斗衛星系統設計NTP網路時間服務器 安徽京準電子科技官微——ahjzsz 概述 NTP網路時間服務器是一款支持NTP和SNTP網路時間同步協議,高精度、大容量、高品質的高科技時鐘產品。 NTP網路時間服務器設備采用冗余架構設計,高精度時鐘直接來源于北斗 ......

    uj5u.com 2020-09-10 03:52:35 more
  • 詳細解讀電力系統各種對時方式

    詳細解讀電力系統各種對時方式 詳細解讀電力系統各種對時方式 安徽京準電子科技官微——ahjzsz,更多資料請添加VX 衛星同步時鐘是我京準公司開發研制的應用衛星授時時技術的標準時間顯示和發送的裝置,該裝置以M國全球定位系統(GLOBAL POSITIONING SYSTEM,縮寫為GPS)或者我國北 ......

    uj5u.com 2020-09-10 03:52:45 more
  • 如何保證外包團隊接入企業內網安全

    不管企業規模的大小,只要企業想省錢,那么企業的某些服務就一定會采用外包的形式,然而看似美好又經濟的策略,其實也有不好的一面。下面我通過安全的角度來聊聊使用外包團的安全隱患問題。 先看看什么服務會使用外包的,最常見的就是話務/客服這種需要大量重復性、無技術性的服務,或者是一些銷售外包、特殊的職能外包等 ......

    uj5u.com 2020-09-10 03:52:57 more
  • PHP漏洞之【整型數字型SQL注入】

    0x01 什么是SQL注入 SQL是一種注入攻擊,通過前端帶入后端資料庫進行惡意的SQL陳述句查詢。 0x02 SQL整型注入原理 SQL注入一般發生在動態網站URL地址里,當然也會發生在其它地發,如登錄框等等也會存在注入,只要是和資料庫打交道的地方都有可能存在。 如這里http://192.168. ......

    uj5u.com 2020-09-10 03:55:40 more
  • [GXYCTF2019]禁止套娃

    git泄露獲取原始碼 使用GET傳參,引數為exp 經過三層過濾執行 第一層過濾偽協議,第二層過濾帶引數的函式,第三層過濾一些函式 preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'] (?R)參考當前正則運算式,相當于匹配函式里的引數 因此傳遞 ......

    uj5u.com 2020-09-10 03:56:07 more
  • 等保2.0實施流程

    流程 結論 ......

    uj5u.com 2020-09-10 03:56:16 more
最新发布
  • 使用Django Rest framework搭建Blog

    在前面的Blog例子中我們使用的是GraphQL, 雖然GraphQL的使用處于上升趨勢,但是Rest API還是使用的更廣泛一些. 所以還是決定回到傳統的rest api framework上來, Django rest framework的官網上給了一個很好用的QuickStart, 我參考Qu ......

    uj5u.com 2023-04-20 08:17:54 more
  • 記錄-new Date() 我忍你很久了!

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 大家平時在開發的時候有沒被new Date()折磨過?就是它的諸多怪異的設定讓你每每用的時候,都可能不小心踩坑。造成程式意外出錯,卻一下子找不到問題出處,那叫一個煩透了…… 下面,我就列舉它的“四宗罪”及應用思考 可惡的四宗罪 1. Sa ......

    uj5u.com 2023-04-20 08:17:47 more
  • 使用Vue.js實作文字跑馬燈效果

    實作文字跑馬燈效果,首先用到 substring()截取 和 setInterval計時器 clearInterval()清除計時器 效果如下: 實作代碼如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta ......

    uj5u.com 2023-04-20 08:12:31 more
  • JavaScript 運算子

    JavaScript 運算子/運算子 在 JavaScript 中,有一些運算子可以使代碼更簡潔、易讀和高效。以下是一些常見的運算子: 1、可選鏈運算子(optional chaining operator) ?.是可選鏈運算子(optional chaining operator)。?. 可選鏈操 ......

    uj5u.com 2023-04-20 08:02:25 more
  • CSS—相對單位rem

    一、概述 rem是一個相對長度單位,它的單位長度取決于根標簽html的字體尺寸。rem即root em的意思,中文翻譯為根em。瀏覽器的文本尺寸一般默認為16px,即默認情況下: 1rem = 16px rem布局原理:根據CSS媒體查詢功能,更改根標簽的字體尺寸,實作rem單位隨螢屏尺寸的變化,如 ......

    uj5u.com 2023-04-20 08:02:21 more
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 08:01:50 more
  • 如何在 vue3 中使用 jsx/tsx?

    我們都知道,通常情況下我們使用 vue 大多都是用的 SFC(Signle File Component)單檔案組件模式,即一個組件就是一個檔案,但其實 Vue 也是支持使用 JSX 來撰寫組件的。這里不討論 SFC 和 JSX 的好壞,這個仁者見仁智者見智。本篇文章旨在帶領大家快速了解和使用 Vu ......

    uj5u.com 2023-04-20 08:01:37 more
  • 【Vue2.x原始碼系列06】計算屬性computed原理

    本章目標:計算屬性是如何實作的?計算屬性快取原理以及洋蔥模型的應用?在初始化Vue實體時,我們會給每個計算屬性都創建一個對應watcher,我們稱之為計算屬性watcher ......

    uj5u.com 2023-04-20 08:01:31 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:01:10 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:00:32 more