javascript
揭秘JavaScript中“神秘”的this关键字
當我開始學習JavaScript時,花了一些時間來理解JavaScript中的this關鍵字并且能夠快速識別this關鍵字所指向的對象。我發(fā)現(xiàn)理解this關鍵字最困難的事情是,您通常會忘記在您已閱讀或觀看過一些JavaScript課程或資源中解釋的不同案例情況。在ES6中引入箭頭函數(shù)后,事情變得更加混亂,因為箭頭函數(shù)this以不同的方式處理關鍵字。我想寫這篇文章來陳述我學到的東西,并嘗試以一種可以幫助任何正在學習JavaScript并且難以理解this關鍵字的人的方式來解釋它。
您可能知道,執(zhí)行任何JavaScript行的環(huán)境(或scope)稱為“執(zhí)行上下文”。Javascript運行時維護這些執(zhí)行上下文的堆棧,并且當前正在執(zhí)行存在于該堆棧頂部的執(zhí)行上下文。this變量引用的對象每次更改執(zhí)行上下文時都會更改。
默認情況下,執(zhí)行上下文是全局的,這意味著如果代碼作為簡單函數(shù)調(diào)用的一部分執(zhí)行,則該this變量將引用全局對象。在瀏覽器的情況下,全局對象是window對象。例如,在Node.js環(huán)境中,this值是一個特殊對象global。
例如,嘗試以下簡單的函數(shù)調(diào)用:
function foo () {console.log("Simple function call");console.log(this === window); } foo();調(diào)用foo(),得到輸出:
“Simple function call” true證明這里的this指向全局對象,此例中為window。
注意,如果實在嚴格模式下,this的值將是undefined,因為在嚴格模式下全局對象指向undefined而不是window。
試一下如下示例:
function foo () {'use strict';console.log("Simple function call");console.log(this === window); } foo();輸出:
“Simple function call” false我們再來試下有構造函數(shù)的:
function Person(first_name, last_name) {this.first_name = first_name;this.last_name = last_name;this.displayName = function() {console.log(`Name: ${this.first_name} ${this.last_name}`);}; }創(chuàng)建Person實例:
let john = new Person('John', 'Reid'); john.displayName();得到結果:
"Name: John Reid"這里發(fā)生了什么?當我們調(diào)用 new Person,JavaScript會在Person函數(shù)內(nèi)創(chuàng)建一個新對象并把它保存為this。接著,first_name, last_name 和 displayName 屬性會被添加到新創(chuàng)建的this對象上。如下:
你會注意到在Person的執(zhí)行上下文中創(chuàng)建了this對象,這個對象有first_name, last_name 和 displayName 屬性。希望您能根據(jù)上圖理解this對象是如何創(chuàng)建并添加屬性的。
我們已經(jīng)探討了兩種相關this綁定的普通案例我不得不提出下面這個更加困惑的例子,如下函數(shù):
function simpleFunction () {console.log("Simple function call")console.log(this === window); }我們已經(jīng)知道如果像下面這樣作為簡單函數(shù)調(diào)用,this關鍵字將指向全局對象,此例中為window對象。
simpleFunction()因此,得到輸出:
“Simple function call” true創(chuàng)建一個簡單的user對象:
let user = {count: 10,simpleFunction: simpleFunction,anotherFunction: function() {console.log(this === window);} }現(xiàn)在,我們有一個simpleFunction屬性指向simpleFunction函數(shù),同樣添加另一個屬性調(diào)用anotherFunction函數(shù)方法。
如果調(diào)用user.simpleFunction(),得到輸出:
“Simple function call” false為什么會這樣呢?因為simpleFunction()現(xiàn)在是user對象的一個屬性,所以this指向這個user對象而不是全局對象。
當我們調(diào)用user.anotherFunction,也是一樣的結果。this關鍵字指向user對象。所以,console.log(this === window);應該返回false:
false再來,以下操作會返回什么呢?
let myFunction = user.anotherFunction; myFunction();現(xiàn)在,得到結果:
true所以這又發(fā)生了什么?在這個例子中,我們發(fā)起普通函數(shù)調(diào)用。正如之前所知,如果一個方法以普通函數(shù)方式執(zhí)行,那么this關鍵字將指向全局對象(在這個例子中是window對象)。所以console.log(this === window);輸出true。
再看一個例子:
var john = {name: 'john',yearOfBirth: 1990,calculateAge: function() {console.log(this);console.log(2016 - this.yearOfBirth);function innerFunction() {console.log(this);}innerFunction();} }調(diào)用john.calculateAge()會發(fā)生什么呢?
{name: "john", yearOfBirth: 1990, calculateAge: ?} 26 Window {postMessage: ?, blur: ?, focus: ?, close: ?, parent: Window, …}calculateAge函數(shù)內(nèi)部, this 指向 john對象,但是,在innerFunction函數(shù)內(nèi)部,this指向全局對象(本例中為window),有些人認為這是JS的bug,但是規(guī)則告訴我們無論何時一個普通函數(shù)被調(diào)用時,那么this將指向全局對象。
…
我所學的JavaScript函數(shù)也是一種特殊的對象,每個函數(shù)都有call, apply, bind方法。這些方法被用來設置函數(shù)的執(zhí)行上下文的this值。
function Person(firstName, lastName) {this.firstName = firstName;this.lastName = lastName;this.displayName = function() {console.log(`Name: ${this.firstName} ${this.lastName}`);} }創(chuàng)建兩個實例:
let person = new Person("John", "Reed"); let person2 = new Person("Paul", "Adams");調(diào)用:
person.displayName(); person2.displayName();結果:
Name: John Reed Name: Paul Adamscall:
person.displayName.call(person2);上面所做的事情就是設置this的值為person2對象。因此,
Name: Paul Adamsapply:
person.displayName.apply([person2]);得到:
Name: Paul Adamscall,apply唯一的區(qū)別就是參數(shù)的傳遞形式,apply應該傳遞一個數(shù)組,call則應該單獨傳遞參數(shù)。
我們用bind來做同樣的事情,bind返回一個新的方法,這個方法中的this指向傳遞的第一個參數(shù)。
let person2Display = person.displayName.bind(person2);調(diào)用person2Display,得到Name: Paul Adams結果。
…
箭頭函數(shù)
ES6中,有一個新方法定義函數(shù)。如下:
let displayName = (firstName, lastName) => {console.log(Name: ${firstName} ${lastName}); };不像通常的函數(shù),箭頭函數(shù)沒有他們自身的this關鍵字。他們只是簡單的使用寫在函數(shù)里的this關鍵字。他們有一個this詞法變量。
ES5:
var box = {color: 'green', // 1position: 1, // 2clickMe: function() { // 3document.querySelector('body').addEventListener('click', function() {var str = 'This is box number ' + this.position + ' and it is ' + this.color; // 4alert(str);});} }如果調(diào)用:
box.clickMe()彈出框內(nèi)容將是This is box number undefined and it is undefined'.
我們一步一步來分析是怎么回事。在//1和//2行,this關鍵字能訪問到color和position屬性因為它指向box對象。在clickMe方法內(nèi)部,this關鍵字能訪問到color和position屬性因為它也指向box對象。但是,clickMe方法為querySelector方法定義了一個回調(diào)函數(shù),然后這個回調(diào)函數(shù)以普通函數(shù)的形式調(diào)用,所以this指向全局對象而非box對象。當然,全局對象沒有定義color和position屬性,所以這就是為什么我們得到了undefined值。
我們可以用ES5的方法來修復這個問題:
var box = {color: 'green',position: 1,clickMe: function() {var self = this;document.querySelector('body').addEventListener('click', function() {var str = 'This is box number ' + self.position + ' and it is ' + self.color;alert(str);});} }添加 var self = this,創(chuàng)建了一個可以使用指向box對象的this關鍵字的閉包函數(shù)的工作區(qū)。我們僅僅只需要在回調(diào)函數(shù)內(nèi)使用self變量。
調(diào)用:
box.clickMe();彈出框內(nèi)容This is box number 1 and it is green。
怎么使用箭頭函數(shù)能夠達到上述效果呢?我們將用箭頭函數(shù)替換點擊函數(shù)的回調(diào)函數(shù)。
var box = {color: 'green',position: 1,clickMe: function() {document.querySelector('body').addEventListener('click', () => {var str = 'This is box number ' + this.position + ' and it is ' + this.color;alert(str);});} }箭頭函數(shù)的神奇之處就是共享包裹它的this詞法關鍵字。所以,本例中外層函數(shù)的this共享給箭頭函數(shù),這個外層函數(shù)的this關鍵字指向box對象,因此,color和position屬性將是有正確的green和1值。
再來一個:
var box = {color: 'green',position: 1,clickMe: () => {document.querySelector('body').addEventListener('click', () => {var str = 'This is box number ' + this.position + ' and it is ' + this.color;alert(str);});} }oh!現(xiàn)在又彈出了‘This is box number undefined and it is undefined’.。為什么?
click事件監(jiān)聽函數(shù)閉包的this關鍵字共享了包裹它的this關鍵字。在本例中它被包裹的箭頭函數(shù)clickMe,clickMe箭頭函數(shù)的this關鍵字指向全局對象,本例中是window對象。所以this.color和this.position將會是undefined因為window對象沒有position和color屬性。
我想再給你看個在很多情況下都會有幫助的map函數(shù),我們定義一個Person構造函數(shù)方法如下:
function Person(firstName, lastName) {this.firstName = firstName;this.lastName = lastName;this.displayName = function() {console.log(`Name: ${this.firstName} ${this.lastName}`);} }Person的原型上添加myFriends方法:
Person.prototype.myFriends = function(friends) {var arr = friends.map(function(friend) {return this.firstName + ' is friends with ' + friend;});console.log(arr); }創(chuàng)建一個實例:
let john = new Person("John", "Watson");調(diào)用john.myFriends(["Emma", "Tom"]),結果:
["undefined is friends with Emma", "undefined is friends with Tom"]本例與之前的例子非常相似。myFriends函數(shù)體內(nèi)有this關鍵字指向回調(diào)對象。但是,map閉包函數(shù)內(nèi)是一個普通函數(shù)調(diào)用。所以map閉包函數(shù)內(nèi)this指向全局對象,本例中為window對象,因此this.firstNameundefined。現(xiàn)在,我們試著修復這個情況。
調(diào)用bind會返回一個map回調(diào)函數(shù)的副本,this關鍵字映射到外層的this關鍵字,也就是是調(diào)用myFriends方法,this指向這個對象。
現(xiàn)在,箭頭函數(shù)內(nèi)的this關鍵字將共享未曾包裹它的詞法作用域,也就是說實例myFriends。
所有以上解決方案都將輸出結果:
["John is friends with Emma", "John is friends with Tom"]…
在這一點上,我希望我已經(jīng)設法使this關鍵字概念對您來說有點平易近人。在本文中,我分享了我遇到的一些常見情況以及如何處理它們,但當然,在構建更多項目時,您將面臨更多情況。我希望我的解釋可以幫助您在接近this關鍵字綁定主題時保持堅實的基礎。如果您有任何問題,建議或改進,我總是樂于學習更多知識并與所有知名開發(fā)人員交流知識。請隨時寫評論,或給我留言!
總結
以上是生活随笔為你收集整理的揭秘JavaScript中“神秘”的this关键字的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c# emgucv 切图_自己积累的一些
- 下一篇: 【Docker】11、IDEA集成Doc