摘要:任何一個函數都可以使用來調用,因此其實并不存在構造函數,而只有對于函數的構造調用。不可以當作構造函數,也就是說,不可以使用命令,否則會拋出一個錯誤。
this關鍵字是JavaScript中最復雜的機制之一,是一個特別的關鍵字,被自動定義在所有函數的作用域中,但是相信很多JsvaScript開發者并不是非常清楚它究竟指向的是什么。聽說你很懂this,是真的嗎?
請先回答第一個問題:如何準確判斷this指向的是什么?【面試的高頻問題】
【圖片來源于網絡,侵刪】
再看一道題,控制臺打印出來的值是什么?【瀏覽器運行環境】
var number = 5; var obj = { number: 3, fn1: (function () { var number; this.number *= 2; number = number * 2; number = 3; return function () { var num = this.number; this.number *= 2; console.log(num); number *= 3; console.log(number); } })() } var fn1 = obj.fn1; fn1.call(null); obj.fn1(); console.log(window.number);
如果你思考出來的結果,與在瀏覽中執行結果相同,并且每一步的依據都非常清楚,那么,你可以選擇繼續往下閱讀,或者關閉本網頁,愉快得去玩耍。如果你有一部分是靠蒙的,或者對自己的答案并不那么確定,那么請繼續往下閱讀。
畢竟花一兩個小時的時間,把this徹底搞明白,是一件很值得事情,不是嗎?
本文將細致得講解this的綁定規則,并在最后剖析前文兩道題。
為什么要學習this?首先,我們為什么要學習this?
this使用頻率很高,如果我們不懂this,那么在看別人的代碼或者是源碼的時候,就會很吃力。
工作中,濫用this,卻沒明白this指向的是什么,而導致出現問題,但是自己卻不知道哪里出問題了?!驹诠?,我至少幫10個以上的開發人員處理過這個問題】
合理的使用this,可以讓我們寫出簡潔且復用性高的代碼。
面試的高頻問題,回答不好,抱歉,出門右拐,不送。
不管出于什么目的,我們都需要把this這個知識點整的明明白白的。
OK,Let"s go!
this是什么?言歸正傳,this是什么?首先記住this不是指向自身!this 就是一個指針,指向調用函數的對象。這句話我們都知道,但是很多時候,我們未必能夠準確判斷出this究竟指向的是什么?這就好像我們聽過很多道理 卻依然過不好這一生。今天咱們不探討如何過好一生的問題,但是呢,希望閱讀完下面的內容之后,你能夠一眼就看出this指向的是什么。
為了能夠一眼看出this指向的是什么,我們首先需要知道this的綁定規則有哪些?
默認綁定
隱式綁定
硬綁定
new綁定
上面的名詞,你也許聽過,也許沒聽過,但是今天之后,請牢牢記住。我們將依次來進行解析。
默認綁定默認綁定,在不能應用其它綁定規則時使用的默認規則,通常是獨立函數調用。
function sayHi(){ console.log("Hello,", this.name); } var name = "YvetteLau"; sayHi();
在調用Hi()時,應用了默認綁定,this指向全局對象(非嚴格模式下),嚴格模式下,this指向undefined,undefined上沒有this對象,會拋出錯誤。
上面的代碼,如果在瀏覽器環境中運行,那么結果就是 Hello,YvetteLau
但是如果在node環境中運行,結果就是 Hello,undefined.這是因為node中name并不是掛在全局對象上的。
本文中,如不特殊說明,默認為瀏覽器環境執行結果。
隱式綁定函數的調用是在某個對象上觸發的,即調用位置上存在上下文對象。典型的形式為 XXX.fun().我們來看一段代碼:
function sayHi(){ console.log("Hello,", this.name); } var person = { name: "YvetteLau", sayHi: sayHi } var name = "Wiliam"; person.sayHi();
打印的結果是 Hello,YvetteLau.
sayHi函數聲明在外部,嚴格來說并不屬于person,但是在調用sayHi時,調用位置會使用person的上下文來引用函數,隱式綁定會把函數調用中的this(即此例sayHi函數中的this)綁定到這個上下文對象(即此例中的person)
需要注意的是:對象屬性鏈中只有最后一層會影響到調用位置。
function sayHi(){ console.log("Hello,", this.name); } var person2 = { name: "Christina", sayHi: sayHi } var person1 = { name: "YvetteLau", friend: person2 } person1.friend.sayHi();
結果是:Hello, Christina.
因為只有最后一層會確定this指向的是什么,不管有多少層,在判斷this的時候,我們只關注最后一層,即此處的friend。
隱式綁定有一個大陷阱,綁定很容易丟失(或者說容易給我們造成誤導,我們以為this指向的是什么,但是實際上并非如此).
function sayHi(){ console.log("Hello,", this.name); } var person = { name: "YvetteLau", sayHi: sayHi } var name = "Wiliam"; var Hi = person.sayHi; Hi();
結果是: Hello,Wiliam.
這是為什么呢,Hi直接指向了sayHi的引用,在調用的時候,跟person沒有半毛錢的關系,針對此類問題,我建議大家只需牢牢繼續這個格式:XXX.fn();fn()前如果什么都沒有,那么肯定不是隱式綁定,但是也不一定就是默認綁定,這里有點小疑問,我們后來會說到。
除了上面這種丟失之外,隱式綁定的丟失是發生在回調函數中(事件回調也是其中一種),我們來看下面一個例子:
function sayHi(){ console.log("Hello,", this.name); } var person1 = { name: "YvetteLau", sayHi: function(){ setTimeout(function(){ console.log("Hello,",this.name); }) } } var person2 = { name: "Christina", sayHi: sayHi } var name="Wiliam"; person1.sayHi(); setTimeout(person2.sayHi,100); setTimeout(function(){ person2.sayHi(); },200);
結果為:
Hello, Wiliam Hello, Wiliam Hello, Christina
第一條輸出很容易理解,setTimeout的回調函數中,this使用的是默認綁定,非嚴格模式下,執行的是全局對象
第二條輸出是不是有點迷惑了?說好XXX.fun()的時候,fun中的this指向的是XXX呢,為什么這次卻不是這樣了!Why?
其實這里我們可以這樣理解: setTimeout(fn,delay){ fn(); },相當于是將person2.sayHi賦值給了一個變量,最后執行了變量,這個時候,sayHi中的this顯然和person2就沒有關系了。
第三條雖然也是在setTimeout的回調中,但是我們可以看出,這是執行的是person2.sayHi()使用的是隱式綁定,因此這是this指向的是person2,跟當前的作用域沒有任何關系。
讀到這里,也許你已經有點疲倦了,但是答應我,別放棄,好嗎?再堅持一下,就可以掌握這個知識點了。
顯式綁定顯式綁定比較好理解,就是通過call,apply,bind的方式,顯式的指定this所指向的對象。(注意:《你不知道的Javascript》中將bind多帶帶作為了硬綁定講解了)
call,apply和bind的第一個參數,就是對應函數的this所指向的對象。call和apply的作用一樣,只是傳參方式不同。call和apply都會執行對應的函數,而bind方法不會。
function sayHi(){ console.log("Hello,", this.name); } var person = { name: "YvetteLau", sayHi: sayHi } var name = "Wiliam"; var Hi = person.sayHi; Hi.call(person); //Hi.apply(person)
輸出的結果為: Hello, YvetteLau. 因為使用硬綁定明確將this綁定在了person上。
那么,使用了硬綁定,是不是意味著不會出現隱式綁定所遇到的綁定丟失呢?顯然不是這樣的,不信,繼續往下看。
function sayHi(){ console.log("Hello,", this.name); } var person = { name: "YvetteLau", sayHi: sayHi } var name = "Wiliam"; var Hi = function(fn) { fn(); } Hi.call(person, person.sayHi);
輸出的結果是 Hello, Wiliam. 原因很簡單,Hi.call(person, person.sayHi)的確是將this綁定到Hi中的this了。但是在執行fn的時候,相當于直接調用了sayHi方法(記住: person.sayHi已經被賦值給fn了,隱式綁定也丟了),沒有指定this的值,對應的是默認綁定。
現在,我們希望綁定不會丟失,要怎么做?很簡單,調用fn的時候,也給它硬綁定。
function sayHi(){ console.log("Hello,", this.name); } var person = { name: "YvetteLau", sayHi: sayHi } var name = "Wiliam"; var Hi = function(fn) { fn.call(this); } Hi.call(person, person.sayHi);
此時,輸出的結果為: Hello, YvetteLau,因為person被綁定到Hi函數中的this上,fn又將這個對象綁定給了sayHi的函數。這時,sayHi中的this指向的就是person對象。
至此,革命已經快勝利了,我們來看最后一種綁定規則: new 綁定。
new 綁定javaScript和C++不一樣,并沒有類,在javaScript中,構造函數只是使用new操作符時被調用的函數,這些函數和普通的函數并沒有什么不同,它不屬于某個類,也不可能實例化出一個類。任何一個函數都可以使用new來調用,因此其實并不存在構造函數,而只有對于函數的“構造調用”。
使用new來調用函數,會自動執行下面的操作:
創建一個新對象
將構造函數的作用域賦值給新對象,即this指向這個新對象
執行構造函數中的代碼
返回新對象
因此,我們使用new來調用函數的時候,就會新對象綁定到這個函數的this上。
function sayHi(name){ this.name = name; } var Hi = new sayHi("Yevtte"); console.log("Hello,", Hi.name);
輸出結果為 Hello, Yevtte, 原因是因為在var Hi = new sayHi("Yevtte");這一步,會將sayHi中的this綁定到Hi對象上。
綁定優先級我們知道了this有四種綁定規則,但是如果同時應用了多種規則,怎么辦?
顯然,我們需要了解哪一種綁定方式的優先級更高,這四種綁定的優先級為:
new綁定 > 顯式綁定 > 隱式綁定 > 默認綁定
這個規則時如何得到的,大家如果有興趣,可以自己寫個demo去測試,或者記住上面的結論即可。
綁定例外凡事都有例外,this的規則也是這樣。
如果我們將null或者是undefined作為this的綁定對象傳入call、apply或者是bind,這些值在調用時會被忽略,實際應用的是默認綁定規則。
var foo = { name: "Selina" } var name = "Chirs"; function bar() { console.log(this.name); } bar.call(null); //Chirs
輸出的結果是 Chirs,因為這時實際應用的是默認綁定規則。
箭頭函數箭頭函數是ES6中新增的,它和普通函數有一些區別,箭頭函數沒有自己的this,它的this繼承于外層代碼庫中的this。箭頭函數在使用時,需要注意以下幾點:
(1)函數體內的this對象,繼承的是外層代碼塊的this。
(2)不可以當作構造函數,也就是說,不可以使用new命令,否則會拋出一個錯誤。
(3)不可以使用arguments對象,該對象在函數體內不存在。如果要用,可以用 rest 參數代替。
(4)不可以使用yield命令,因此箭頭函數不能用作 Generator 函數。
(5)箭頭函數沒有自己的this,所以不能用call()、apply()、bind()這些方法去改變this的指向.
OK,我們來看看箭頭函數的this是什么?
var obj = { hi: function(){ console.log(this); return ()=>{ console.log(this); } }, sayHi: function(){ return function() { console.log(this); return ()=>{ console.log(this); } } }, say: ()=>{ console.log(this); } } let hi = obj.hi(); //輸出obj對象 hi(); //輸出obj對象 let sayHi = obj.sayHi(); let fun1 = sayHi(); //輸出window fun1(); //輸出window obj.say(); //輸出window
那么這是為什么呢?如果大家說箭頭函數中的this是定義時所在的對象,這樣的結果顯示不是大家預期的,按照這個定義,say中的this應該是obj才對。
我們來分析一下上面的執行結果:
obj.hi(); 對應了this的隱式綁定規則,this綁定在obj上,所以輸出obj,很好理解。
hi(); 這一步執行的就是箭頭函數,箭頭函數繼承上一個代碼庫的this,剛剛我們得出上一層的this是obj,顯然這里的this就是obj.
執行sayHi();這一步也很好理解,我們前面說過這種隱式綁定丟失的情況,這個時候this執行的是默認綁定,this指向的是全局對象window.
fun1(); 這一步執行的是箭頭函數,如果按照之前的理解,this指向的是箭頭函數定義時所在的對象,那么這兒顯然是說不通。OK,按照箭頭函數的this是繼承于外層代碼庫的this就很好理解了。外層代碼庫我們剛剛分析了,this指向的是window,因此這兒的輸出結果是window.
obj.say(); 執行的是箭頭函數,當前的代碼塊obj中是不存在this的,只能往上找,就找到了全局的this,指向的是window.
你說箭頭函數的this是靜態的?依舊是前面的代碼。我們來看看箭頭函數中的this真的是靜態的嗎?
我要說:非也
var obj = { hi: function(){ console.log(this); return ()=>{ console.log(this); } }, sayHi: function(){ return function() { console.log(this); return ()=>{ console.log(this); } } }, say: ()=>{ console.log(this); } } let sayHi = obj.sayHi(); let fun1 = sayHi(); //輸出window fun1(); //輸出window let fun2 = sayHi.bind(obj)();//輸出obj fun2(); //輸出obj
可以看出,fun1和fun2對應的是同樣的箭頭函數,但是this的輸出結果是不一樣的。
所以,請大家牢牢記住一點: 箭頭函數沒有自己的this,箭頭函數中的this繼承于外層代碼庫中的this.
總結關于this的規則,至此,就告一段落了,但是想要一眼就能看出this所綁定的對象,還需要不斷的訓練。
我們來回顧一下,最初的問題。
1. 如何準確判斷this指向的是什么?
函數是否在new中調用(new綁定),如果是,那么this綁定的是新創建的對象。
函數是否通過call,apply調用,或者使用了bind(即硬綁定),如果是,那么this綁定的就是指定的對象。
函數是否在某個上下文對象中調用(隱式綁定),如果是的話,this綁定的是那個上下文對象。一般是obj.foo()
如果以上都不是,那么使用默認綁定。如果在嚴格模式下,則綁定到undefined,否則綁定到全局對象。
如果把Null或者undefined作為this的綁定對象傳入call、apply或者bind,這些值在調用時會被忽略,實際應用的是默認綁定規則。
如果是箭頭函數,箭頭函數的this繼承的是外層代碼塊的this。
2. 執行過程解析
var number = 5; var obj = { number: 3, fn: (function () { var number; this.number *= 2; number = number * 2; number = 3; return function () { var num = this.number; this.number *= 2; console.log(num); number *= 3; console.log(number); } })() } var myFun = obj.fn; myFun.call(null); obj.fn(); console.log(window.number);
我們來分析一下,這段代碼的執行過程。
1.在定義obj的時候,fn對應的閉包就執行了,返回其中的函數,執行閉包中代碼時,顯然應用不了new綁定(沒有出現new 關鍵字),硬綁定也沒有(沒有出現call,apply,bind關鍵字),隱式綁定有沒有?很顯然沒有,如果沒有XX.fn(),那么可以肯定沒有應用隱式綁定,所以這里應用的就是默認綁定了,非嚴格模式下this綁定到了window上(瀏覽器執行環境)?!具@里很容易被迷惑的就是以為this指向的是obj,一定要注意,除非是箭頭函數,否則this跟詞法作用域是兩回事,一定要牢記在心】
window.number * = 2; //window.number的值是10(var number定義的全局變量是掛在window上的) number = number * 2; //number的值是NaN;注意我們這邊定義了一個number,但是沒有賦值,number的值是undefined;Number(undefined)->NaN number = 3; //number的值為3
2.myFun.call(null);我們前面說了,call的第一個參數傳null,調用的是默認綁定;
fn: function(){ var num = this.number; this.number *= 2; console.log(num); number *= 3; console.log(number); }
執行時:
var num = this.number; //num=10; 此時this指向的是window this.number * = 2; //window.number = 20 console.log(num); //輸出結果為10 number *= 3; //number=9; 這個number對應的閉包中的number;閉包中的number的是3 console.log(number); //輸出的結果是9
3.obj.fn();應用了隱式綁定,fn中的this對應的是obj.
var num = this.number;//num = 3;此時this指向的是obj this.number *= 2; //obj.number = 6; console.log(num); //輸出結果為3; number *= 3; //number=27;這個number對應的閉包中的number;閉包中的number的此時是9 console.log(number);//輸出的結果是27
4.最后一步console.log(window.number);輸出的結果是20
因此組中結果為:
10 9 3 27 20
嚴格模式下結果,大家可以根據今天所學,自己分析,鞏固一下知識點。
最后,恭喜堅持讀完的小伙伴們,你們成功get到了this這個知識點,但是想要完全掌握,還是要多回顧和練習。如果你有不錯的this練習題,歡迎在評論區留言哦,大家一起進步!
謝謝您花費寶貴的時間閱讀本文,如果本文給了您一點幫助或者是啟發,那么不要吝嗇你的贊和Star哈,您的肯定是我前進的最大動力。https://github.com/YvetteLau/...
關注小姐姐的公眾號,和小姐姐一起學前端。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/102885.html
摘要:記錄下我遇到的面試題,都有大佬分享過,附上各個大佬的文章,總結出其中的主要思想即可。推薦黑金團隊的文章前端緩存最佳實踐推薦名揚的文章淺解強緩存和協商緩存狀態碼重點是等,要給面試官介紹清楚。前言 在這互聯網的寒冬臘月時期,雖說過了金三銀四,但依舊在招人不斷。更偏向于招聘高級開發工程師。本人在這期間求職,去了幾家創業,小廠,大廠廝殺了一番,也得到了自己滿意的offer。 整理一下自己還記得的面試...
摘要:阿里云嘛,出了名的云服務商了,淘寶,阿里巴巴。為什么他會有那么多的節點實力上,阿里云不輸網上的服務商,服務器也不虛。。工單那邊也是極其差,你提交個工單,他半天才理你,可能是因為工單多吧那也說不通吧,那么大的阿里云服務商,會缺技術嗎。 作者/咖啡 咖啡無厘頭博客站長 今天給大家帶來的是博主親自體驗過的CDN,做一次評測,在此期間,博主賠了不少老婆本進去了…… 花了無數冤枉錢,然后今天我就...
摘要:阿里云嘛,出了名的云服務商了,淘寶,阿里巴巴。為什么他會有那么多的節點實力上,阿里云不輸網上的服務商,服務器也不虛。。工單那邊也是極其差,你提交個工單,他半天才理你,可能是因為工單多吧那也說不通吧,那么大的阿里云服務商,會缺技術嗎。 作者/咖啡 咖啡無厘頭博客站長 今天給大家帶來的是博主親自體驗過的CDN,做一次評測,在此期間,博主賠了不少老婆本進去了…… 花了無數冤枉錢,然后今天我就...
摘要:函數聲明根據規則,進入執行上下文會自動聲明形參并且賦值,但是同名的函數聲明會替換這個變量。的函數調用得到其返回值。的意義在于將執行的結果通知給代理函數進行及時處理。回調函數的傳遞傳遞的方式有兩種,函數引用和函數表達式。 函數聲明方式 匿名函數 function后面直接跟括號,中間沒有函數名的就是匿名函數。 let fn = function() { console.log(我是...
摘要:基本類型有六種,,,,,。類型是類數組,具有基礎類型檢測檢測基礎類型用只適合檢測基礎類型基礎類型轉換基本類型轉換時,首先會調用,然后調用。該方法在轉基本類型時調用優先級最高。 夯實Javascript基礎。 基本類型有六種: null,undefined,boolean,number,string,symbol。 基本類型的值是保存在棧內存中的簡單數據段 基礎類型特性 基礎類型最重要的...
閱讀 2611·2021-11-18 10:02
閱讀 2639·2021-11-15 11:38
閱讀 3719·2021-11-12 10:36
閱讀 709·2021-11-12 10:34
閱讀 2910·2021-10-21 09:38
閱讀 1499·2021-09-29 09:48
閱讀 1509·2021-09-29 09:34
閱讀 1103·2021-09-22 10:02