徹底搞懂 JS 中 this 機制

徹底搞懂 JS 中 this 機制

摘要:本文屬于原創,歡迎轉載,轉載請保留出處:https://github.com/jasonGeng88/blog

目錄

  • this 是什么
  • this 的四種綁定規則
  • 綁定規則的優先級
  • 綁定例外
  • 擴展:箭頭函數

this 是什么

理解this之前, 先糾正一個觀點,this 既不指向函數自身,也不指函數的詞法作用域。如果僅通過this的英文解釋,太容易產生誤導了。它實際是在函數被調用時才發生的綁定,也就是說this具體指向什么,取決于你是怎么調用的函數。

this 的四種綁定規則

this的4種綁定規則分別是:默認綁定、隱式綁定、顯示綁定、new 綁定。優先級從低到高。

默認綁定

什么叫默認綁定,即沒有其他綁定規則存在時的默認規則。這也是函數調用中最常用的規則。

來看這段代碼:

function foo() { 
}       console.log( this.a );var a = 2; 
foo(); //打印的是什么?

foo() 打印的結果是2。

因為foo()是直接調用的(獨立函數調用),沒有應用其他的綁定規則,這里進行了默認綁定,將全局對象綁定this上,所以this.a 就解析成了全局變量中的a,即2。

注意:在嚴格模式下(strict mode),全局對象將無法使用默認綁定,即執行會報undefined的錯誤

function foo() { "use strict";console.log( this.a );
}var a = 2; 
foo(); // Uncaught TypeError: Cannot read property 'a' of undefined

隱式綁定

除了直接對函數進行調用外,有些情況是,函數的調用是在某個對象上觸發的,即調用位置上存在上下文對象。

function foo() { console.log( this.a );
}var a = 2;var obj = { a: 3,foo: foo 
};obj.foo(); // ?

obj.foo() 打印的結果是3。

這里foo函數被當做引用屬性,被添加到obj對象上。這里的調用過程是這樣的:

獲取obj.foo屬性 -> 根據引用關系找到foo函數,執行調用

所以這里對foo的調用存在上下文對象obj,this進行了隱式綁定,即this綁定到了obj上,所以this.a被解析成了obj.a,即3。

多層調用鏈

function foo() { console.log( this.a );
}var a = 2;var obj1 = { a: 4,foo: foo 
};var obj2 = { a: 3,obj1: obj1
};obj2.obj1.foo(); //?

obj2.obj1.foo() 打印的結果是4。

同樣,我們看下函數的調用過程:

先獲取obj2.obj1 -> 通過引用獲取到obj1對象,再訪問 obj1.foo -> 最后執行foo函數調用

這里調用鏈不只一層,存在obj1、obj2兩個對象,那么隱式綁定具體會綁哪個對象。這里原則是獲取最后一層調用的上下文對象,即obj1,所以結果顯然是4(obj1.a)。

隱式丟失(函數別名)

注意:這里存在一個陷阱,大家在分析調用過程時,要特別小心

先看個代碼:

function foo() { console.log( this.a );
}var a = 2;var obj = { a: 3,foo: foo 
};var bar = obj.foo;
bar(); //?

<font color="red">bar() 打印的結果是2。</font>

為什么會這樣,obj.foo 賦值給bar,那調用bar()為什么沒有觸發隱式綁定,使用的是默認綁定呢。

這里有個概念要理解清楚,obj.foo 是引用屬性,賦值給bar的實際上就是foo函數(即:bar指向foo本身)。

那么,實際的調用關系是:通過bar找到foo函數,進行調用。整個調用過程并沒有obj的參數,所以是默認綁定,全局屬性a。

隱式丟失(回調函數)

function foo() { console.log( this.a );
}var a = 2;var obj = { a: 3,foo: foo 
};setTimeout( obj.foo, 100 ); // ?

<font color="red">打印的結果是2。</font>

同樣的道理,雖然參傳是obj.foo,因為是引用關系,所以傳參實際上傳的就是foo對象本身的引用。對于setTimeout的調用,還是 setTimeout -> 獲取參數中foo的引用參數 -> 執行 foo 函數,中間沒有obj的參與。這里依舊進行的是默認綁定。


顯示綁定

相對隱式綁定,this值在調用過程中會動態變化,可是我們就想綁定指定的對象,這時就用到了顯示綁定。

顯示綁定主要是通過改變對象的prototype關聯對象,這里不展開講。具體使用上,可以通過這兩個方法call(...)或apply(...)來實現(大多數函數及自己創建的函數默認都提供這兩個方法)。

call與apply是同樣的作用,區別只是其他參數的設置上

function foo() { console.log( this.a );
}var a = 2;var obj1 = { a: 3,
};var obj2 = { a: 4,
};
foo.call( obj1 ); // ?
foo.call( obj2 ); // ?

打印的結果是3, 4。

這里因為顯示的申明了要綁定的對象,所以this就被綁定到了obj上,打印的結果自然就是obj1.a 和obj2.a。

硬綁定

function foo() { console.log( this.a );
}var a = 2;var obj1 = { a: 3,
};var obj2 = { a: 4,
};var bar = function(){foo.call( obj1 );
}bar(); // 3
setTimeout( bar, 100 ); // 3bar.call( obj2 ); // 這是多少

前面兩個(函數別名、回調函數)打印3,因為顯示綁定了,沒什么問題。

最后一個打印是3。

這里需要注意下,雖然bar被顯示綁定到obj2上,對于bar,function(){...} 中的this確實被綁定到了obj2,而foo因為通過foo.call( obj1 )已經顯示綁定了obj1,所以在foo函數內,this指向的是obj1,不會因為bar函數內指向obj2而改變自身。所以打印的是obj1.a(即3)。


new 綁定

js中的new操作符,和其他語言中(如JAVA)的new機制是不一樣的。js中,它就是一個普通函數調用,只是被new修飾了而已。

使用new來調用函數,會自動執行如下操作:

  1. 如果函數沒有返回其他對象,那么new表達式中的函數調用會自動返回這個新對象。

從第三點可以看出,this指向的就是對象本身。

看個代碼:

function foo(a) { this.a = a;
}var a = 2;var bar1 = new foo(3);
console.log(bar1.a); // ?var bar2 = new foo(4);
console.log(bar2.a); // ?

最后一個打印是3, 4。

因為每次調用生成的是全新的對象,該對象又會自動綁定到this上,所以答案顯而易見。

綁定規則優先級

上面也說過,這里在重復一下。優先級是這樣的,以按照下面的順序來進行判斷:

 數是否在new中調用(new綁定)?如果是的話this綁定的是新創建的對象。數是否通過call、apply(顯式綁定)或者硬綁定調用?如果是的話,this綁定的是 指定的對象。數是否在某個上下文對象中調用(隱式綁定)?如果是的話,this綁定的是那個上下文對象。果都不是的話,使用默認綁定。如果在嚴格模式下,就綁定到undefined,否則綁定到 全局對象。var bar = foo()

規則例外

在顯示綁定中,對于null和undefined的綁定將不會生效。

代碼如下:

function foo() { console.log( this.a );
}
foo.call( null ); // 2
foo.call( undefined ); // 2

這種情況主要是用在不關心this的具體綁定對象(用來忽略this),而傳入null實際上會進行默認綁定,導致函數中可能會使用到全局變量,與預期不符。

所以對于要忽略this的情況,可以傳入一個空對象?,該對象通過Object.create(null)創建。這里不用{}的原因是,?是真正意義上的空對象,它不創建Object.prototype委托,{}和普通對象一樣,有原型鏈委托關系。

1. 這里傳null的一種具體使用場景是函數柯里化的使用

擴展:箭頭函數

最后,介紹一下ES6中的箭頭函數。通過“=>”而不是function創建的函數,叫做箭頭函數。它的this綁定取決于外層(函數或全局)作用域。

case 1 (正常調用)

  • 普通函數
function foo(){     console.log( this.a );
}var a = 2;var obj = { a: 3,foo: foo 
};obj.foo(); //3
  • 箭頭函數
var foo = () => {     console.log( this.a );
}var a = 2;var obj = { a: 3,foo: foo 
};obj.foo(); //2
foo.call(obj); //2 ,箭頭函數中顯示綁定不會生效

case 2 (函數回調)

  • 普通函數
function foo(){ return function(){console.log( this.a );}    
}var a = 2;var obj = { a: 3,foo: foo 
};var bar = obj.foo();
bar(); //2
  • 箭頭函數
function foo(){ return () => {console.log( this.a );}    
}var a = 2;var obj = { a: 3,foo: foo 
};var bar = obj.foo();
bar(); //3

通過上面兩個列子,我們看到箭頭函數的this綁定<font color="red">只取決于外層(函數或全局)的作用域</font>,對于前面的4種綁定規則是不會生效的。它也是作為this機制的一種替換,解決之前this綁定過程各種規則帶來的復雜性。

注意:對于ES6之前,箭頭函數的替換版本是這樣的

// es6
function foo(){ return () => {console.log( this.a );}   
}var a = 2;var obj = { a: 3,foo: foo 
};var bar = obj.foo();
bar(); //3

通過上面兩個列子,我們看到箭頭函數的this綁定<font color="red">只取決于外層(函數或全局)的作用域</font>,對于前面的4種綁定規則是不會生效的。它也是作為this機制的一種替換,解決之前this綁定過程各種規則帶來的復雜性。

注意:對于ES6之前,箭頭函數的替換版本是這樣的

// es6
function foo(){ return () => {console.log( this.a );}   
}// es6之前的替代方法
function foo(){ var self = this;return () => {console.log( self.a );}   
}

總結

我們在使用js的過程中,對于this的理解往往覺得比較困難,再調試過程中有時也會出現一些不符合預期的現象。很多時候,我們都是通過一些變通的方式(如:使用具體對象替換this)來規避的問題。可問題一直存在那兒,我們沒有真正的去理解和解決它。

本文主要參考了《你不知道的JavaScript(上卷)》,對this到底是什么,具體怎么綁定的,有什么例外情況以及ES6中的一個優化方向,來徹底搞清楚我們一直使用的this到底是怎么玩的。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/390741.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/390741.shtml
英文地址,請注明出處:http://en.pswp.cn/news/390741.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

?如何在2分鐘內將GraphQL服務器添加到RESTful Express.js API

You can get a lot done in 2 minutes, like microwaving popcorn, sending a text message, eating a cupcake, and hooking up a GraphQL server.您可以在2分鐘內完成很多工作&#xff0c;例如微波爐爆米花&#xff0c;發送短信&#xff0c; 吃蛋糕以及連接GraphQL服務器 。 …

leetcode 1744. 你能在你最喜歡的那天吃到你最喜歡的糖果嗎?

給你一個下標從 0 開始的正整數數組 candiesCount &#xff0c;其中 candiesCount[i] 表示你擁有的第 i 類糖果的數目。同時給你一個二維數組 queries &#xff0c;其中 queries[i] [favoriteTypei, favoriteDayi, dailyCapi] 。 你按照如下規則進行一場游戲&#xff1a; 你…

回歸分析_回歸

回歸分析Machine learning algorithms are not your regular algorithms that we may be used to because they are often described by a combination of some complex statistics and mathematics. Since it is very important to understand the background of any algorith…

ruby nil_Ruby中的數據類型-True,False和Nil用示例解釋

ruby niltrue, false, and nil are special built-in data types in Ruby. Each of these keywords evaluates to an object that is the sole instance of its respective class.true &#xff0c; false和nil是Ruby中的特殊內置數據類型。 這些關鍵字中的每一個都求值為一個對…

淺嘗flutter中的動畫(淡入淡出)

在移動端開發中&#xff0c;經常會有一些動畫交互&#xff0c;比如淡入淡出,效果如圖&#xff1a; 因為官方封裝好了AnimatedOpacity Widget&#xff0c;開箱即用&#xff0c;所以我們用起來很方便&#xff0c;代碼量很少&#xff0c;做少量配置即可&#xff0c;所以&#xff0…

數據科學還是計算機科學_何時不使用數據科學

數據科學還是計算機科學意見 (Opinion) 目錄 (Table of Contents) Introduction 介紹 Examples 例子 When You Should Use Data Science 什么時候應該使用數據科學 Summary 摘要 介紹 (Introduction) Both Data Science and Machine Learning are useful fields that apply sev…

空間復雜度 用什么符號表示_什么是大O符號解釋:時空復雜性

空間復雜度 用什么符號表示Do you really understand Big O? If so, then this will refresh your understanding before an interview. If not, don’t worry — come and join us for some endeavors in computer science.您真的了解Big O嗎&#xff1f; 如果是這樣&#xf…

leetcode 523. 連續的子數組和

給你一個整數數組 nums 和一個整數 k &#xff0c;編寫一個函數來判斷該數組是否含有同時滿足下述條件的連續子數組&#xff1a; 子數組大小 至少為 2 &#xff0c;且 子數組元素總和為 k 的倍數。 如果存在&#xff0c;返回 true &#xff1b;否則&#xff0c;返回 false 。 …

Docker學習筆記 - Docker Compose

一、概念 Docker Compose 用于定義運行使用多個容器的應用&#xff0c;可以一條命令啟動應用&#xff08;多個容器&#xff09;。 使用Docker Compose 的步驟&#xff1a; 定義容器 Dockerfile定義應用的各個服務 docker-compose.yml啟動應用 docker-compose up二、安裝 Note t…

創建shell腳本

1.寫一個腳本 a) 用touch命令創建一個文件&#xff1a;touch my_script b) 用vim編輯器打開my_script文件&#xff1a;vi my_script c) 用vim編輯器編輯my_script文件,內容如下&#xff1a; #!/bin/bash 告訴shell使用什么程序解釋腳本 #My first script l…

線性回歸算法數學原理_線性回歸算法-非數學家的高級數學

線性回歸算法數學原理內部AI (Inside AI) Linear regression is one of the most popular algorithms used in different fields well before the advent of computers. Today with the powerful computers, we can solve multi-dimensional linear regression which was not p…

您應該在2020年首先學習哪種編程語言? ????d???s????:???su?

Most people’s journey toward learning to program starts with a single late-night Google search.大多數人學習編程的旅程都是從一個深夜Google搜索開始的。 Usually it’s something like “Learn ______”通常它類似于“學習______” But how do they decide which la…

Linux 概述

UNIX發展歷程 第一個版本是1969年由Ken Thompson&#xff08;UNIX之父&#xff09;在AT& T貝爾實驗室實現Ken Thompson和Dennis Ritchie&#xff08;C語言之父&#xff09;使用C語言對整個系統進行了再加工和編寫UNIX的源代碼屬于SCO公司&#xff08;AT&T ->Novell …

課程一(Neural Networks and Deep Learning),第四周(Deep Neural Networks)—— 0.學習目標...

Understand the key computations underlying deep learning, use them to build and train deep neural networks, and apply it to computer vision. 學習目標 See deep neural networks as successive blocks put one after each otherBuild and train a deep L-layer Neura…

使用ActionTrail Python SDK

ActionTrail提供官方的Python SDK。本文將簡單介紹一下如何使用ActionTrail的Python SDK。 安裝Aliyun Core SDK。 pip install aliyun-python-sdk-core 安裝ActionTrail Python SDK。 pip install aliyun-python-sdk-actiontrail 下面是測試的代碼。調用LookupEventsRequest獲…

泰坦尼克:機器從災難中學習_用于災難響應的機器學習研究:什么才是好的論文?...

泰坦尼克:機器從災難中學習For the first time in 2021, a major Machine Learning conference will have a track devoted to disaster response. The 16th Conference of the European Chapter of the Association for Computational Linguistics (EACL 2021) has a track on…

github持續集成的設置_如何使用GitHub Actions和Puppeteer建立持續集成管道

github持續集成的設置Lately Ive added continuous integration to my blog using Puppeteer for end to end testing. My main goal was to allow automatic dependency updates using Dependabot. In this guide Ill show you how to create such a pipeline yourself. 最近&…

shell與常用命令

虛擬控制臺 一臺計算機的輸入輸出設備就是一個物理的控制臺 &#xff1b; 如果在一臺計算機上用軟件的方法實現了多個互不干擾獨立工作的控制臺界面&#xff0c;就是實現了多個虛擬控制臺&#xff1b; Linux終端的工作方式是字符命令行方式&#xff0c;用戶通過鍵盤輸入命令進…

C中的malloc:C中的動態內存分配

什么是C中的malloc()&#xff1f; (What is malloc() in C?) malloc() is a library function that allows C to allocate memory dynamically from the heap. The heap is an area of memory where something is stored.malloc()是一個庫函數&#xff0c;它允許C從堆動態分配…

Linux文本編輯器

Linux文本編輯器 Linux系統下有很多文本編輯器。 按編輯區域&#xff1a; 行編輯器 ed 全屏編輯器 vi 按運行環境&#xff1a; 命令行控制臺編輯器 vi X Window圖形界面編輯器 gedit ed 它是一個很古老的行編輯器&#xff0c;vi這些編輯器都是ed演化而來。 每次只能對一…