深入理解javascript中的this
深入理解Js中的this
JavaScript
作用域?yàn)殪o態(tài)作用域static scope
,但是在Js
中的this
卻是一個(gè)例外,this
的指向問(wèn)題就類似于動(dòng)態(tài)作用域,其并不關(guān)心函數(shù)和作用域是如何聲明以及在何處聲明的,只關(guān)心它們從何處調(diào)用,this
的指向在函數(shù)定義的時(shí)候是確定不了的,只有函數(shù)執(zhí)行的時(shí)候才能確定this
到底指向誰(shuí),當(dāng)然實(shí)際上this
的最終指向的是那個(gè)調(diào)用它的對(duì)象。
作用域
我們先來(lái)了解一下JavaScript
的作用域,以便理解為什么說(shuō)this
更類似于動(dòng)態(tài)作用域,通常來(lái)說(shuō),一段程序代碼中所用到的名字并不總是有效或可用的,而限定這個(gè)名字的可用性的代碼范圍就是這個(gè)名字的作用域scope
,當(dāng)一個(gè)方法或成員被聲明,他就擁有當(dāng)前的執(zhí)行上下文context
環(huán)境,在有具體值的context
中,表達(dá)式是可見也都能夠被引用,如果一個(gè)變量或者其他表達(dá)式不在當(dāng)前的作用域,則將無(wú)法使用。作用域也可以根據(jù)代碼層次分層,以便子作用域可以訪問(wèn)父作用域,通常是指沿著鏈?zhǔn)降淖饔糜蜴湶檎遥荒軓母缸饔糜蛞米幼饔糜蛑械淖兞亢鸵谩?br />
JavaScript
作用域?yàn)殪o態(tài)作用域static scope
,也可以稱為詞法作用域lexical scope
,其主要特征在于,函數(shù)作用域中遇到既不是參數(shù)也不是函數(shù)內(nèi)部定義的局部變量時(shí),去函數(shù)定義時(shí)上下文中查,而與之相對(duì)應(yīng)的是動(dòng)態(tài)作用域dynamic scope
則不同,其函數(shù)作用域中遇到既不是參數(shù)也不是函數(shù)內(nèi)部定義的局部變量時(shí),到函數(shù)調(diào)用時(shí)的上下文中去查。
var a = 1; var s = function(){ console.log(a); }; (function(){ var a = 2; s(); // 1 })();
調(diào)用s()是打印的a為1,此為靜態(tài)作用域,也就是聲明時(shí)即規(guī)定作用域,而假如是動(dòng)態(tài)作用域的話在此處會(huì)打印2?,F(xiàn)在大部分語(yǔ)言都采用靜態(tài)作用域,比如C
、C++
、Java
、PHP
、Python
等等,具有動(dòng)態(tài)作用域的語(yǔ)言有Emacs Lisp
、Common Lisp
、Perl
等。
全局作用域
直接聲明在頂層的變量或方法就運(yùn)行在全局作用域,借用函數(shù)的[[Scopes]]
屬性來(lái)查看作用域,[[Scopes]]
是保存函數(shù)作用域鏈的對(duì)象,是函數(shù)的內(nèi)部屬性無(wú)法直接訪問(wèn)但是可以打印來(lái)查看。
function s(){} console.dir(s); /* ... [[Scopes]]: Scopes[1] 0: Global ... */ // 可以看見聲明的s函數(shù)運(yùn)行的上下文環(huán)境是全局作用域
函數(shù)作用域
當(dāng)聲明一個(gè)函數(shù)后,在函數(shù)內(nèi)部聲明的方法或者成員的運(yùn)行環(huán)境就是此函數(shù)的函數(shù)作用域
(function localContext(){ var a = 1; function s(){ return a; } console.dir(s); })(); /* ... [[Scopes]]: Scopes[2] 0: Closure (localContext) {a: 1} 1: Global ... */ // 可以看見聲明的s函數(shù)運(yùn)行的上下文環(huán)境是函數(shù)localContext的作用域,也可以稱為局部作用域
塊級(jí)作用域
代碼塊內(nèi)如果存在let
或者const
,代碼塊會(huì)對(duì)這些命令聲明的變量從塊的開始就形成一個(gè)封閉作用域。
{ let a = 1; function s(){return a;} console.dir(s); /* ... [[Scopes]]: Scopes[2] 0: Block {a: 1} 1: Global ... */ } // 可以看見聲明的s函數(shù)運(yùn)行的上下文環(huán)境是Block塊級(jí)作用域,也是局部作用域
分析
我們?cè)谑褂?code>this之前有必要了解為什么在JavaScript
中要有this
這個(gè)設(shè)計(jì),在這之前我們先舉個(gè)小例子,通常我們使用this
時(shí)可能會(huì)遇到的典型問(wèn)題就類似于下面這樣,雖然我們運(yùn)行的都是同一個(gè)函數(shù),但是執(zhí)行的結(jié)果可能會(huì)不同。
var obj = { name: 1, say: function() { return this.name; } }; window.name = 2; window.say = obj.say; console.log(obj.say()); // 1 console.log(window.say()); // 2
產(chǎn)生這樣的結(jié)果的原因就是因?yàn)槭褂昧?code>this關(guān)鍵字,前文已經(jīng)提到了this
必須要在運(yùn)行時(shí)才能確定,在這里,對(duì)于obj.say()
來(lái)說(shuō),say()
運(yùn)行的環(huán)境是obj
對(duì)象,對(duì)于window.say()
來(lái)說(shuō),say()
運(yùn)行的環(huán)境是window
對(duì)象,所以兩者運(yùn)行的結(jié)果不同。
此時(shí)我們就來(lái)了解一下,為什么JavaScript
會(huì)有this
這樣一個(gè)設(shè)計(jì),我們首先來(lái)了解一下JavaScript
的內(nèi)存結(jié)構(gòu)中的堆棧,堆heap
是動(dòng)態(tài)分配的內(nèi)存,大小不定也不會(huì)自動(dòng)釋放,棧stack
為自動(dòng)分配的內(nèi)存空間,在代碼執(zhí)行過(guò)程中自動(dòng)釋放。JavaScript
在棧內(nèi)存中提供一個(gè)供Js
代碼執(zhí)行的環(huán)境,關(guān)于作用域以及函數(shù)的調(diào)用都是棧內(nèi)存中執(zhí)行的。Js
中基本數(shù)據(jù)類型String
、Number
、Boolean
、Null
、Undefined
、Symbol
,占用空間小且大小固定,值直接保存在棧內(nèi)存中,是按值訪問(wèn),對(duì)于Object
引用類型,其指針?lè)胖糜跅?nèi)存中,指向堆內(nèi)存的實(shí)際地址,是通過(guò)引用訪問(wèn)。
那么此時(shí)我們來(lái)看一下上邊的示例,在內(nèi)存中對(duì)于obj
對(duì)象是存放在堆內(nèi)存的,如果在對(duì)象中的屬性值是個(gè)基本數(shù)據(jù)類型,那么其會(huì)跟這個(gè)對(duì)象存儲(chǔ)在同一塊內(nèi)存區(qū)域,但是這個(gè)屬性值同樣可能是一個(gè)引用類型,那么對(duì)于say
這個(gè)函數(shù)也是存在于堆內(nèi)存中的,實(shí)際上在此處我們可以將其理解為這個(gè)函數(shù)的實(shí)際定義在一個(gè)內(nèi)存區(qū)域(以一個(gè)匿名函數(shù)的形式存在),而obj
這個(gè)對(duì)象同樣在其他的一個(gè)內(nèi)存區(qū)域,obj
通過(guò)say
這個(gè)屬性指向了這個(gè)匿名函數(shù)的內(nèi)存地址,obj --say--> funtion
,那么此時(shí)問(wèn)題來(lái)了,由于這種內(nèi)存結(jié)構(gòu),我們可以使任何變量對(duì)象等指向這個(gè)函數(shù),所以在JavaScript
的函數(shù)中是需要允許我們?nèi)〉眠\(yùn)行環(huán)境的值以供使用的,我們必須要有一種機(jī)制,能夠在函數(shù)體內(nèi)部獲得當(dāng)前的運(yùn)行環(huán)境context
,所以this
就出現(xiàn)了,它的設(shè)計(jì)目的就是在函數(shù)體內(nèi)部,指代函數(shù)當(dāng)前的運(yùn)行環(huán)境。
使用
我們需要記住,this
是在運(yùn)行時(shí)進(jìn)行綁定的,并不是在定義時(shí)綁定,它的context
取決于函數(shù)調(diào)用時(shí)的各種條件,簡(jiǎn)單來(lái)說(shuō)this
的綁定和函數(shù)聲明的位置沒有任何關(guān)系,只取決于函數(shù)的調(diào)用方式,再簡(jiǎn)單來(lái)說(shuō)this
永遠(yuǎn)指向調(diào)用者,但箭頭函數(shù)除外,接下來(lái)我們介紹一下五種this
的使用情況。
默認(rèn)綁定
最常用的函數(shù)調(diào)用類型即獨(dú)立函數(shù)調(diào)用,這個(gè)也是優(yōu)先級(jí)最低的一個(gè),此時(shí)this
指向全局對(duì)象,注意如果使用嚴(yán)格模式strict mode
,那么全局對(duì)象將無(wú)法使用默認(rèn)綁定,因此this
會(huì)變?yōu)?code>undefined。
var a = 1; // 變量聲明到全局對(duì)象中 function f1() { return this.a; } function f2() { "use strict"; return this; } console.log(f1()); // 1 // 實(shí)際上是調(diào)用window.f1()而this永遠(yuǎn)指向調(diào)用者即window console.log(f2()); // undefined // 實(shí)際上是調(diào)用 window.f2() 此時(shí)由于嚴(yán)格模式use strict所以在函數(shù)內(nèi)部this為undefined
隱式綁定
對(duì)象屬性引用鏈中只有最頂層或者說(shuō)最后一層會(huì)影響this
,同樣也是this
永遠(yuǎn)指向調(diào)用者,具體點(diǎn)說(shuō)應(yīng)該是指向最近的調(diào)用者,當(dāng)然箭頭函數(shù)除外,另外我們可能有意無(wú)意地創(chuàng)建間接引用地情況,這個(gè)情況下同樣也適用于this
指向調(diào)用者,在上文分析那部分使用的示例就屬于間接引用的情況。
function f() { console.log(this.a); } var obj1 = { a: 1, f: f }; var obj2 = { a: 11, obj1: obj1 }; obj2.obj1.f(); // 1 // 最后一層調(diào)用者即obj1
function f() { console.log(this.a); } var obj1 = { a: 1, f: f }; var obj2 = { a: 11, }; obj2.f = obj1.f; // 間接引用 obj2.f(); // 11 // 調(diào)用者即為obj2
顯示綁定
如果我們想把某個(gè)函數(shù)強(qiáng)制在某個(gè)環(huán)境即對(duì)象上,那么就可以使用apply
、call
、bind
強(qiáng)制綁定this
去執(zhí)行即可,每個(gè)Function
對(duì)象都存在apply()
、call()
、bind()
方法,其作用都是可以在特定的作用域中調(diào)用函數(shù),等于設(shè)置函數(shù)體內(nèi)this
對(duì)象的值,以擴(kuò)充函數(shù)賴以運(yùn)行的作用域,此外需要注意使用bind
綁定this
的優(yōu)先級(jí)是大于apply
和call
的,即使用bind
綁定this
后的函數(shù)使用apply
和call
是無(wú)法改變this
指向的。
window.name = "A"; // 掛載到window對(duì)象的name document.name = "B"; // 掛載到document對(duì)象的name var s = { // 自定義一個(gè)對(duì)象s name: "C" } var rollCall = { name: "Teacher", sayName: function(){ console.log(this.name); } } rollCall.sayName(); // Teacher // apply rollCall.sayName.apply(); // A // 不傳參默認(rèn)綁定window rollCall.sayName.apply(window); // A // 綁定window對(duì)象 rollCall.sayName.apply(document); // B // 綁定document對(duì)象 rollCall.sayName.apply(s); // C // 綁定自定義對(duì)象 // call rollCall.sayName.call(); // A // 不傳參默認(rèn)綁定window rollCall.sayName.call(window); // A // 綁定window對(duì)象 rollCall.sayName.call(document); // B // 綁定document對(duì)象 rollCall.sayName.call(s); // C // 綁定自定義對(duì)象 // bind // 最后一個(gè)()是為讓其執(zhí)行 rollCall.sayName.bind()(); //A // 不傳參默認(rèn)綁定window rollCall.sayName.bind(window)(); //A // 綁定window對(duì)象 rollCall.sayName.bind(document)(); //B // 綁定document對(duì)象 rollCall.sayName.bind(s)(); // C // 綁定自定義對(duì)象
new綁定
在JavaScript
中new
是一個(gè)語(yǔ)法糖,可以簡(jiǎn)化代碼的編寫,可以批量創(chuàng)建對(duì)象實(shí)例,在new
的過(guò)程實(shí)際上進(jìn)行了以下操作。
創(chuàng)建一個(gè)空的簡(jiǎn)單JavaScript
對(duì)象即{}
。鏈接該對(duì)象(即設(shè)置該對(duì)象的構(gòu)造函數(shù))到另一個(gè)對(duì)象。將步驟1
新創(chuàng)建的對(duì)象作為this
的上下文context
。如果該函數(shù)沒有返回對(duì)象,則返回步驟1
創(chuàng)建的對(duì)象。
function _new(base,...args){ var obj = {}; obj.__proto__ = base.prototype; base.apply(obj, args); return obj; } function Funct(a) { this.a = a; } var f1 = new Funct(1); console.log(f1.a); // 1 var f2 = _new(Funct, 1); console.log(f2.a); // 1
箭頭函數(shù)
箭頭函數(shù)沒有單獨(dú)的this
,在箭頭函數(shù)的函數(shù)體中使用this
時(shí),會(huì)取得其上下文context
環(huán)境中的this
。箭頭函數(shù)調(diào)用時(shí)并不會(huì)生成自身作用域下的this
,它只會(huì)從自己的作用域鏈的上一層繼承this
。由于箭頭函數(shù)沒有自己的this
指針,使用apply
、call
、bind
僅能傳遞參數(shù)而不能動(dòng)態(tài)改變箭頭函數(shù)的this
指向,另外箭頭函數(shù)不能用作構(gòu)造器,使用new
實(shí)例化時(shí)會(huì)拋出異常。
window.name = 1; var obj = { name: 11, say: function(){ const f1 = () => { return this.name; } console.log(f1()); // 11 // 直接調(diào)用者為window 但是由于箭頭函數(shù)不綁定this所以取得context中的this即obj對(duì)象 const f2 = function(){ return this.name; } console.log(f2()); // 1 // 直接調(diào)用者為window 普通函數(shù)所以 return this.name; } } console.log(obj.say()); // 11 // 直接調(diào)用者為obj 執(zhí)行過(guò)程中的函數(shù)內(nèi)context的this為obj對(duì)象
示例
function s(){ console.log(this); } // window中直接調(diào)用 // 非 use strict s(); // Window // 等同于window.s(),調(diào)用者為window // window是Window的一個(gè)實(shí)例 // window instanceof Window //true // 新建對(duì)象s1 var s1 = { t1: function(){ // 測(cè)試this指向調(diào)用者 console.log(this); // s1 s(); // Window // 此次調(diào)用仍然相當(dāng) window.s(),調(diào)用者為window }, t2: () => { // 測(cè)試箭頭函數(shù),this并未指向調(diào)用者 console.log(this); }, t3: { // 測(cè)試對(duì)象中的對(duì)象 tt1: function() { console.log(this); } }, t4: { // 測(cè)試箭頭函數(shù)以及非函數(shù)調(diào)用this并未指向調(diào)用者 tt1: () => { console.log(this); } }, t5: function(){ // 測(cè)試函數(shù)調(diào)用時(shí)箭頭函數(shù)的this的指向,其指向了上一層對(duì)象的調(diào)用者 return { tt1: () => { console.log(this); } } } } s1.t1(); // s1對(duì)象 // 此處的調(diào)用者為 s1 所以打印對(duì)象為 s1 s1.t2(); // Window s1.t3.tt1(); // s1.t3對(duì)象 s1.t4.tt1(); // Window s1.t5().tt1(); // s1對(duì)象
到此這篇關(guān)于深入理解Js中的this的文章就介紹到這了,更多相關(guān)深入理解Js中的this內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript入門之事件、cookie、定時(shí)等
本文從上一篇文章結(jié)束的地方開始,解釋其他的一些基本的JavaScript語(yǔ)言概念,繼續(xù)為初學(xué)者提供對(duì)語(yǔ)言的基礎(chǔ)理解2011-10-10Angularjs 設(shè)置全局變量的方法總結(jié)
這篇文章主要介紹了Angularjs 設(shè)置全局變量的方法總結(jié)的相關(guān)資料,需要的朋友可以參考下2016-10-10ES6學(xué)習(xí)總結(jié)之Set和Map的使用
這篇博客介紹了 ES6 中的 Set 和 Map 數(shù)據(jù)結(jié)構(gòu),Set 是一個(gè)存儲(chǔ)唯一值的集合,支持添加、刪除、檢查元素的方法,Map 則是用于存儲(chǔ)鍵值對(duì)的集合,鍵和值都可以是任何類型,文章詳細(xì)講解了兩者的主要方法和用法,并與傳統(tǒng)的數(shù)組和對(duì)象進(jìn)行了對(duì)比,突出 Set 和 Map 的獨(dú)特優(yōu)勢(shì)2024-08-08javaScript中的this示例學(xué)習(xí)詳解及工作原理
這篇文章主要介紹了javaScript中的this示例學(xué)習(xí)詳解及工作原理,大家參考使用吧2014-01-01javascript實(shí)現(xiàn)dom元素可拖動(dòng)
HTML DOM 是 W3C 標(biāo)準(zhǔn)(是 HTML 文檔對(duì)象模型的英文縮寫,Document Object Model for HTML)。通過(guò) DOM,可以訪問(wèn)所有的 HTML 元素,連同它們所包含的文本和屬性。可以對(duì)其中的內(nèi)容進(jìn)行修改和刪除,同時(shí)也可以創(chuàng)建新的元素。2016-03-03