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