、this是什么東東?
this是指包含它的函數作為方法被調用時所屬的對象。這句話理解起來跟卵一樣看不懂,但是如果你把它拆分開來變成這三句話后就好理解一點了。
1.包含它的函數
2.作為方法被調用時
3.所屬的對象
二、this的綁定規則
1、默認綁定
var x = 0; function num(){ this.x = 1; } console.log(this.x);//0 num(); console.log(this.x);//1
當沒有使用let或者var時,聲明的變量是全局變量,指向window,
在這一形態下,其函數內部的this是與全局作用域時一樣,直接指向window,執行完num()后,更改了x的值,所以其形態 依然等價于window。
function foo(){ console.log(this.a) } var a = 2; foo(); //2 console.log(this.document === document); // true // 在瀏覽器中,全局對象為 window 對象: console.log(this === window); // true this.a = 3; console.log(window.a); // 3
this指向全局對象。在非嚴格模式中,當funcion被不帶任何修飾的函數直接引用進行調用的,只能使用默認綁定,無法應用其他規則。
2、隱式綁定
先看兩段例子
function foo(){ console.log(this.a) } var obj = { a:2, foo:foo } obj.foo() //2
隱式綁定————調用位置使用obj上下文來引用函數,可以說函數被調用時obj對象“擁有”或者“包含”它,它就指向誰。
function foo(){ console.log(this.a) } var obj2 = { a:42, foo:foo } var obj1 = { a:2, obj2:obj2, foo:foo } obj1.foo() //2 obj1.obj2.foo() //42
此時可以控制臺查看obj1,obj2對象里究竟包含了什么。
當函數引用有上下文對象時,隱式綁定規則會把函數調用中的this綁定到這個上下文對象。
對象屬性引用鏈中只有最后一層在調用位置中起作用。
身為前端老司機,還是得分享些干貨精品學習資料的,前端資料獲取方式:
1.在你手機的右上角有【關注】選項,點擊關注!
2.關注后,手機客戶端點擊我的主頁面,右上角有私信,請私信回復:【學習】
已經設置好了關鍵詞自動回復,所以回復的時候請注意關鍵詞喲~
下面思考這一段會輸出什么呢?
function foo(){ console.log(this.a) } var obj = { a:2, foo:foo } var bar = obj.foo; //這里bar將引用foo函數本身,所以不帶有函數對象的上下文,this會直接指向window bar() //?
為什么沒有隱式綁定?這種情況稱為隱式丟失。
因為bar=obj.foo 而obj.foo指向foo 也就是bar = function foo(){console.log(this.a)} foo中的this指向window,在window中并沒有對a進行定義,so,結果是undefined
接下來再看一段會是什么結果呢?(參數傳遞時的隱式賦值)
function foo(){ console.log(this.a) } function doback(fn){ fn() } var obj = { a:2, foo:foo } var a = 'global'; doback(obj.foo) //? 顯然答案是global,但是為什么呢?請繼續往下看!
隱式丟失--被隱式綁定的函數會丟失綁定對象然后應用默認綁定。
最后函數執行 doback(obj.foo)時,會進行參數傳遞,也就是 fn = obj.foo,就和上一個例子一樣了。既this指向的是window。
3、顯示綁定
function foo(){ console.log(this.a) } var obj = { a:2 } foo.call(obj) //2
顯式綁定--第一個參數是一個對象,接著在調用函數時將其綁定到this,通過foo.call(obj),我們可以在調用foo時強制把他的this綁定到obj上。
function foo(){ console.log(this.a) } var obj = { a:2 } var a = '3333333'; var bar = function(){ foo.call(obj) } bar() // 2 bar.call(window) //2
硬綁定后bar無論怎么調用,都不會影響foo函數的this綁定。
通過創建函數bar(),并在內部調用foo.call(obj),強制把foo的this綁定到obj上。硬綁定的bar之后無論如何調用函數,都只會在obj上調用foo。
我們來看一下他的應用:
function foo(num) { console.log( this.a, num); return this.a + num; } var obj = { a: 2 }; var bar = function() { return foo.call( obj, ...arguments ); // 將obj對象硬編碼進去 //return foo.apply( obj, arguments ); // 也可以使用apply將obj對象硬編碼進去 }; var b = bar( 3 ); // 2 3 console.log( b ); // 5
function fn1(){ console.log(1); } function fn2(){ console.log(2); } fn1.call(fn2); //輸出 1 fn1.call.call(fn2); //輸出 2
4、new綁定
我們不去深入了解構造函數,但要知道new來調用函數,或者說發生構造函數調用時,執行了哪些
當代碼 new foo(...) 執行時:
(1) 創建一個新對象,它繼承自foo.prototype.
(2) 將構造函數的作用域賦給新對象(因此 this 就指向了這個新對象);new foo 等同于 new foo(),只能用在不傳遞任何參數的情況。
(3) 執行構造函數中的代碼(為這個新對象添加屬性) ;
(4) 返回新對象, 那么這個對象會取代整個new出來的結果。如果構造函數沒有返回對象,那么new出來的結果為步驟1創建的對象。
this 關鍵字作為 JavaScript 中自動定義的特殊標識符,是我們不得不去面對、了解的知識點,很多初學者對 this 關鍵字可能會有含糊不清的感覺,但其實稍微理一理, this 并不復雜、不混亂。
概述中我們說了 this 是 JavaScript 中的一個特殊關鍵字,this 和執行上下文相關,當一個函數被調用時,會建立一個活動記錄,也稱為執行環境。這個記錄包含函數是從何處(call-stack)被調用的,函數是如何被調用的,被傳遞了什么參數等信息。這個記錄的屬性之一,就是在函數執行期間將被使用的 this 引用。 但是本文中并不準備往深層次分析其到底是什么,而是說明在應用場景和淺層原理中分析 this 到底是什么。 在分析之前,我們先來看一看當初的我們為什么會用 this。
為什么用 this?
以下是一段代碼實例,其中用到了 this。
let me = { name: 'seymoe' } function toUpperCase() { return this.name.toUpperCase() } toUpperCase.call(me) // 'SEYMOE'
當然以上代碼也完全可以不用 this 而采用傳參的形式實現。
let me = { name: 'seymoe' } function toUpperCase(person) { return person.name.toUpperCase() } toUpperCase(me) // 'SEYMOE'
在這里為什么用 this 而不用傳參的形式,是因為 this 機制用更優雅的方式隱含的傳遞一個對象的引用,可以擁有更干凈的 API 設計和簡單復用。使用模式越復雜,通過明確參數傳遞執行環境和傳遞 this 執行環境相比,就越復雜,當然以上只是一個應用場景之一。
this 不是編寫時綁定,而是運行時綁定。它依賴于函數調用的上下文條件。this 綁定和函數聲明的位置無關,反而和函數被調用的方式有關,被調用的這個位置就叫調用點。所以我們分析 this 是什么的時候就必須分析調用棧(使我們到達當前執行位置而被調用的所有方法的堆棧)和調用點。
function baz() { // 調用棧是‘baz’,調用點是全局作用域 console.log('baz') bar() 2. // bar的調用點 } function bar() { // 調用棧是‘baz - bar’,調用點位于baz的函數作用域內 console.log('bar') } baz() // 1. baz的調用點
以上應該比較容易理解 baz 和 bar 函數相對應的調用點和調用棧。
重點!this 的指向規則
現在我們知道了調用點,而調用點決定了函數調用期間 this 指向哪里的四種規則,所以排好隊一個一個來分析吧~
1. 默認綁定
顧名思義,就是 this 沒有其他規則適用時的默認規則,獨立函數調用就是最常見的情況。
var a = 2 function foo() { console.log(this.a) } function bar() { foo() } foo() // 2 bar() // 2
foo 是一個直白的毫無修飾的函數引用調用,所以默認綁定了全局對象,當然如果是嚴格模式 "use strict" this 將會是 undefined。
注意: 雖然是基于調用點,但只要foo的內容沒在嚴格模式下,那就默認綁定全局對象。
var a = 2 function foo() { console.log(this.a) } (function (){ "use strict"; foo() // 2 })()
2. 隱含綁定
調用點是否擁有一個環境對象,或(擁有者、容器對象)。
function foo() { console.log(this.a) } let obj = { a: 2, foo: foo } obj.foo() // 2
當一個方法引用存在一個環境對象,隱含規則為該對象應該被用于這個函數調用的this綁定。
隱含綁定的情況下,容易出現丟失的情況!當隱含綁定丟失了它的綁定,意味著它會回退到默認綁定,下面是例子:
var a = 3 function foo() { console.log(this.a) } let obj = { a: 2, foo: foo } let bar = obj.foo bar() // 3 // 另一種微妙的情況 function doFoo(fn) { fn && fn() } doFoo(obj.foo) // 3
函數的參數傳遞只是一種隱含的賦值,fn是foo函數的一個引用,而調用fn則是毫無掩飾的調用一個函數,默認綁定規則。
3. 明確綁定
隱含綁定需要我們改變對象自身包含一個函數的引用來使 this 隱含的綁定到這個對象上,默認綁定也是不確定的情況,但是很多時候我們希望能夠明確的使一個函數調用時使用某個特定對象作為 this 綁定,而不在這個對象上放置一個函數引用屬性。
這個時候,call 和 apply 就該上場了。
JavaScript 中幾乎所有的函數都能訪問這兩個方法,這兩個方法接收的第一個參數都是一個用于 this 的對象,之后用這個指定的 this 來調用函數,這種方式就叫明確綁定。
function foo() { console.log(this.a) } let obj = { a: 2 } foo.call(obj) // 2
一種明確綁定的變種可以保證一個函數始終被obj調用,無論如何也不會改變,這種方式叫硬綁定,通過 bind 方法實現。
var obj = { a: 2 } function foo(something) { console.log(this.a, something) return this.a + something } var bar = foo.bind(obj) bar(' is a number.') // 2 ,'is a number.'
我們注意到采用 bind 方式進行硬綁定時,該方法返回一個函數,這和 call 和 apply 是有所區別的。
4. new 綁定
傳統面向對象語言中,通過 new 操作符調用構造函數會生成一個類實例。在 JavaScript 中其實沒有構造器、類的概念,new 調用的函數僅僅只是一個函數,只是被 new 調用時改變了行為。所以不存在構造器函數,只存在函數的構造器調用。
new 操作符調用時會創建一個全新對象,連接原型鏈,并將這個新創建的對象設置為函數調用的 this 綁定,(默認情況)自動返回這個全新對象。
function Foo(a) { this.a = a } let bar = new Foo(2) console.log(bar.a) // 2
以上的規則在適用時存在優先級,級別如下:
硬綁定 > new 綁定 > 明確綁定 > 隱含綁定 > 默認綁定
所以我們已經能夠總結出判定 this 的一般流程了。
判定 this 一般流程
單獨將箭頭函數中的 this 列出來是因為并不能因為 this 在箭頭函數中就有特殊的指向,而是因為箭頭函數不會像普通函數去使用 this, 箭頭函數的 this 和外層的 this 保持一致。這種保持一致是強力的,無法通過 call、apply 或 bind來改變指向。
const obj = { a: () => { console.log(this) } } obj.a() // window obj.a.bind({})() // window
最后,下面這個簡單的測驗,并不是很繞很難的面試題,有興趣不妨做做,評論區回復答案一起探討一下~
var a = 1 var obj = { a: 2, } var bar obj.foo = foo bar = obj.foo function foo() { var a = 3 console.log(this.a) } foo() // 1. ??? ;(function (a) { "use strict"; foo() // 2. ??? bar.bind(a) // 3. ??? })(this) obj.foo() // 4. ??? obj.foo.call(this) // 5. ??? bar() // 6. ??? bar.apply(obj) // 7. ??? var b = new foo() // 8. ??? console.log(b.a) // 9. ???
玩轉 JavaScript 系列
寫作是一個學習的過程,嘗試寫這個系列也主要是為了鞏固 JavaScript 基礎,并嘗試理解其中的一些知識點,以便能靈活運用。本篇同步發布在「端技」公眾號,如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝!
鏈接:https://juejin.im/post/5c9115ee6fb9a070f03cf309
作者:Seymoe
文轉載于網絡,侵權必刪
在 JavaScript 中,this 是一個相對難懂的特殊變量。因為它隨處可用,而不僅僅是面向對象的編程中。本文將解釋 this 是如何工作的,以及它可能導致問題的地方,并在文章的給出最佳實踐。
為了方便理解 this ,最好的方式是根據使用 this 的位置劃分三種類型:
我們來看看每個類別。
1.函數內部的 this
這是 this 最常用的使用場景,因為 JavaScript 中,以三種不同的角色代表了所有的可調用的結構形式:
在函數中,this 可以理解為一個額外隱含的參數。
1.1 真正函數中的 this
在真正函數中,this 的值取決于函數所處的模式:
JavaScript 代碼:
*請認真填寫需求信息,我們會在24小時內與您取得聯系。