無人不識又無人不迷糊的this
當(dāng)前位置:點(diǎn)晴教程→知識管理交流
→『 技術(shù)文檔交流 』
本文分享自華為云社區(qū)《3月閱讀周·你不知道的JavaScript | 無人不識又無人不迷糊的this》,作者: 葉一一。 關(guān)于thisthis關(guān)鍵字是JavaScript中最復(fù)雜的機(jī)制之一。它是一個(gè)很特別的關(guān)鍵字,被自動(dòng)定義在所有函數(shù)的作用域中。 為什么要用this隨著開發(fā)者的使用模式越來越復(fù)雜,顯式傳遞上下文對象會(huì)讓代碼變得越來越混亂,使用this則不會(huì)這樣。 比如下面的例子: function identify() { return this.name.toUpperCase(); } function speak() { var greeting = "Hello, I'm " + identify.call(this); console.log(greeting); } var me = { name: 'Kyle', }; var you = { name: 'Reader', }; console.log(identify.call(me)); console.log(identify.call(you)); speak.call(me); speak.call(you); 打印一下結(jié)果: 上面的代碼可以在不同的上下文對象(me和you)中重復(fù)使用函數(shù)identify()和speak(),不用針對每個(gè)對象編寫不同版本的函數(shù)。如果不使用this,那就需要給identify()和speak()顯式傳入一個(gè)上下文對象。 誤解有兩種常見的對于this的解釋,但是它們都是錯(cuò)誤的。 1、指向自身 人們很容易把this理解成指向函數(shù)自身。 那么為什么需要從函數(shù)內(nèi)部引用函數(shù)自身呢?常見的原因是遞歸(從函數(shù)內(nèi)部調(diào)用這個(gè)函數(shù))或者可以寫一個(gè)在第一次被調(diào)用后自己解除綁定的事件處理器。 看下面這段代碼,思考foo會(huì)被調(diào)用了多少次? function foo(num) { console.log('foo: ' + num); // 記錄foo被調(diào)用的次數(shù) this.count++; } foo.count = 0; var i; for (i = 0; i < 10; i++) { if (i > 5) { foo(i); } } // foo被調(diào)用了多少次? console.log(foo.count); 打印結(jié)果: console.log語句產(chǎn)生了4條輸出,證明foo(..)確實(shí)被調(diào)用了4次,但是foo.count仍然是0。顯然從字面意思來理解this是錯(cuò)誤的。 執(zhí)行foo.count = 0時(shí),的確向函數(shù)對象foo添加了一個(gè)屬性count。但是函數(shù)內(nèi)部代碼this.count中的this并不是指向那個(gè)函數(shù)對象。 2、它的作用域 第二種常見的誤解是,this指向函數(shù)的作用域。這個(gè)問題有點(diǎn)復(fù)雜,因?yàn)樵谀撤N情況下它是正確的,但是在其他情況下它卻是錯(cuò)誤的。 this在任何情況下都不指向函數(shù)的詞法作用域。 function foo() { var a = 2; this.bar(); } function bar() { console.log(this.a); } foo(); 直接打印上面的代碼會(huì)得到一個(gè)報(bào)錯(cuò): 這段代碼試圖通過this.bar()來引用bar()函數(shù)。這是不可能實(shí)現(xiàn)的,使用this不可能在詞法作用域中查到什么。 每當(dāng)開發(fā)者想要把this和詞法作用域的查找混合使用時(shí),一定要提醒自己,這是無法實(shí)現(xiàn)的。 this到底是什么this的綁定和函數(shù)聲明的位置沒有任何關(guān)系,只取決于函數(shù)的調(diào)用方式。 當(dāng)一個(gè)函數(shù)被調(diào)用時(shí),會(huì)創(chuàng)建一個(gè)活動(dòng)記錄(有時(shí)候也稱為執(zhí)行上下文)。這個(gè)記錄會(huì)包含函數(shù)在哪里被調(diào)用(調(diào)用棧)、函數(shù)的調(diào)用方式、傳入的參數(shù)等信息。this就是這個(gè)記錄的一個(gè)屬性,會(huì)在函數(shù)執(zhí)行的過程中用到。 this全面解析調(diào)用位置在理解this的綁定過程之前,首先要理解調(diào)用位置:調(diào)用位置就是函數(shù)在代碼中被調(diào)用的位置(而不是聲明的位置)。 尋找調(diào)用位置就是尋找“函數(shù)被調(diào)用的位置”。最重要的是要分析調(diào)用棧(就是為了到達(dá)當(dāng)前執(zhí)行位置所調(diào)用的所有函數(shù))。調(diào)用位置就在當(dāng)前正在執(zhí)行的函數(shù)的前一個(gè)調(diào)用中。 通過下面的代碼來看什么是調(diào)用棧和調(diào)用位置: function baz() { // 當(dāng)前調(diào)用棧是:baz // 因此,當(dāng)前調(diào)用位置是全局作用域 console.log('baz'); bar(); // <-- bar的調(diào)用位置 } function bar() { // 當(dāng)前調(diào)用棧是baz -> bar // 因此,當(dāng)前調(diào)用位置在baz中 console.log('bar'); foo(); // <-- foo的調(diào)用位置 } function foo() { // 當(dāng)前調(diào)用棧是baz -> bar -> foo // 因此,當(dāng)前調(diào)用位置在bar中 console.log('foo'); } baz(); // <-- baz的調(diào)用位置 打印的結(jié)果如下: 綁定規(guī)則來看看在函數(shù)的執(zhí)行過程中調(diào)用位置如何決定this的綁定對象。 首先必須找到調(diào)用位置,然后判斷需要應(yīng)用下面四條規(guī)則中的哪一條。 充分理解四條規(guī)則之后,再理解多條規(guī)則都可用時(shí)它們的優(yōu)先級如何排列。 1、默認(rèn)綁定 首先要介紹的是最常用的函數(shù)調(diào)用類型:獨(dú)立函數(shù)調(diào)用??梢园堰@條規(guī)則看作是無法應(yīng)用其他規(guī)則時(shí)的默認(rèn)規(guī)則。 var a = 2; function foo() { console.log(this.a); } foo(); // 2 打印結(jié)果是2。也就是當(dāng)調(diào)用foo()時(shí),this.a被解析成了全局變量a。函數(shù)調(diào)用時(shí)應(yīng)用了this的默認(rèn)綁定,因此this指向全局對象。 2、隱式綁定 另一條需要考慮的規(guī)則是調(diào)用位置是否有上下文對象,或者說是否被某個(gè)對象擁有或者包含,不過這種說法可能會(huì)造成一些誤導(dǎo)。 思考下面的代碼: function foo() { console.log(this.a); } var obj = { a: 2, foo: foo, }; obj.foo(); // 2 當(dāng)foo()被調(diào)用時(shí),它的前面確實(shí)加上了對obj的引用。當(dāng)函數(shù)引用有上下文對象時(shí),隱式綁定規(guī)則會(huì)把函數(shù)調(diào)用中的this綁定到這個(gè)上下文對象。因?yàn)檎{(diào)用foo()時(shí)this被綁定到obj,因此this.a和obj.a是一樣的。 3、顯式綁定 JavaScript提供的絕大多數(shù)函數(shù)以及你自己創(chuàng)建的所有函數(shù)都可以使用call(..)和apply(..)方法。 它們的第一個(gè)參數(shù)是一個(gè)對象,是給this準(zhǔn)備的,接著在調(diào)用函數(shù)時(shí)將其綁定到this。 因?yàn)榭梢灾苯又付╰his的綁定對象,因此我們稱之為顯式綁定。 思考下面的代碼: function foo() { console.log(this.a); } var obj = { a: 2, }; foo.call(obj); // 2 通過foo.call(..),我們可以在調(diào)用foo時(shí)強(qiáng)制把它的this綁定到obj上。 4、new綁定 在傳統(tǒng)的面向類的語言中,“構(gòu)造函數(shù)”是類中的一些特殊方法,使用new初始化類時(shí)會(huì)調(diào)用類中的構(gòu)造函數(shù)。通常的形式是這樣的: something = new MyClass(..); 在JavaScript中,構(gòu)造函數(shù)只是一些使用new操作符時(shí)被調(diào)用的函數(shù)。它們并不會(huì)屬于某個(gè)類,也不會(huì)實(shí)例化一個(gè)類。實(shí)際上,它們甚至都不能說是一種特殊的函數(shù)類型,它們只是被new操作符調(diào)用的普通函數(shù)而已。 優(yōu)先級1、四條規(guī)則的優(yōu)先級 new綁定 > 顯式綁定 > 隱式綁定 > 默認(rèn)綁定。 2、判斷this 可以根據(jù)優(yōu)先級來判斷函數(shù)在某個(gè)調(diào)用位置應(yīng)用的是哪條規(guī)則??梢园凑障旅娴捻樞騺磉M(jìn)行判斷: (1)函數(shù)是否在new中調(diào)用(new綁定)?如果是的話this綁定的是新創(chuàng)建的對象。 var bar = new foo(); (2)函數(shù)是否通過call、apply(顯式綁定)或者硬綁定調(diào)用?如果是的話,this綁定的是指定的對象。 var bar = foo.call(obj2); (3)函數(shù)是否在某個(gè)上下文對象中調(diào)用(隱式綁定)?如果是的話,this綁定的是那個(gè)上下文對象。 var bar = obj1.foo(); (4)如果都不是的話,使用默認(rèn)綁定。如果在嚴(yán)格模式下,就綁定到undefined,否則綁定到全局對象。 var bar = foo(); 綁定例外在某些場景下this的綁定行為會(huì)出乎意料,你認(rèn)為應(yīng)當(dāng)應(yīng)用其他綁定規(guī)則時(shí),實(shí)際上應(yīng)用的可能是默認(rèn)綁定規(guī)則。 被忽略的this如果你把null或者undefined作為this的綁定對象傳入call、apply或者bind,這些值在調(diào)用時(shí)會(huì)被忽略,實(shí)際應(yīng)用的是默認(rèn)綁定規(guī)則: function foo() { console.log(this.a); } var a = 2; foo.call(null); // 2 那么什么情況下會(huì)傳入null呢? 一種非常常見的做法是使用apply(..)來“展開”一個(gè)數(shù)組,并當(dāng)作參數(shù)傳入一個(gè)函數(shù)。類似地,bind(..)可以對參數(shù)進(jìn)行柯里化(預(yù)先設(shè)置一些參數(shù)),這種方法有時(shí)非常有用。 間接引用另一個(gè)需要注意的是,你有可能(有意或者無意地)創(chuàng)建一個(gè)函數(shù)的“間接引用”,在這種情況下,調(diào)用這個(gè)函數(shù)會(huì)應(yīng)用默認(rèn)綁定規(guī)則。 間接引用最容易在賦值時(shí)發(fā)生: function foo() { console.log(this.a); } var a = 2; var o = { a: 3, foo: foo }; var p = { a: 4 }; o.foo(); // 3 (p.foo = o.foo)(); // 2 賦值表達(dá)式p.foo = o.foo的返回值是目標(biāo)函數(shù)的引用,因此調(diào)用位置是foo()而不是p.foo()或者o.foo()。根據(jù)我們之前說過的,這里會(huì)應(yīng)用默認(rèn)綁定。 軟綁定如果可以給默認(rèn)綁定指定一個(gè)全局對象和undefined以外的值,那就可以實(shí)現(xiàn)和硬綁定相同的效果,同時(shí)保留隱式綁定或者顯式綁定修改this的能力。 function foo() { console.log('name: ' + this.name); } var obj = { name: 'obj' }, obj2 = { name: 'obj2' }, obj3 = { name: 'obj3' }; var fooOBJ = foo.softBind(obj); fooOBJ(); // name: obj obj2.foo = foo.softBind(obj); obj2.foo(); // name: obj2 <---- 看!! ! fooOBJ.call(obj3); // name: obj3 <---- 看! setTimeout(obj2.foo, 10); // name: obj <---- 應(yīng)用了軟綁定 可以看到,軟綁定版本的foo()可以手動(dòng)將this綁定到obj2或者obj3上,但如果應(yīng)用默認(rèn)綁定,則會(huì)將this綁定到obj。 this詞法ES6中介紹了一種無法使用這些規(guī)則的特殊函數(shù)類型:箭頭函數(shù)。箭頭函數(shù)并不是使用function關(guān)鍵字定義的,而是使用被稱為“胖箭頭”的操作符=>定義的。箭頭函數(shù)不使用this的四種標(biāo)準(zhǔn)規(guī)則,而是根據(jù)外層(函數(shù)或者全局)作用域來決定this。 箭頭函數(shù)的詞法作用域: function foo() { // 返回一個(gè)箭頭函數(shù) return a => { //this繼承自foo() console.log(this.a); }; } var obj1 = { a: 2, }; var obj2 = { a: 3, }; var bar = foo.call(obj1); bar.call(obj2); // 2, 不是3! foo()內(nèi)部創(chuàng)建的箭頭函數(shù)會(huì)捕獲調(diào)用時(shí)foo()的this。由于foo()的this綁定到obj1,bar(引用箭頭函數(shù))的this也會(huì)綁定到obj1,箭頭函數(shù)的綁定無法被修改。 總結(jié)我們來總結(jié)一下本篇的主要內(nèi)容:
轉(zhuǎn)自博客園,作者華為云開發(fā)聯(lián)盟https://www.cnblogs.com/huaweiyun/p/18096240 該文章在 2024/3/28 17:20:08 編輯過 |
相關(guān)文章
正在查詢... |