摘要:的四種綁定規(guī)則的種綁定規(guī)則分別是默認綁定隱式綁定顯示綁定綁定。綁定中的操作符,和其他語言中如的機制是不一樣的。規(guī)則例外在顯示綁定中,對于和的綁定將不會生效。它也是作為機制的一種替換,解決之前綁定過程各種規(guī)則帶來的復雜性。
徹底搞懂 JS 中 this 機制
摘要:本文屬于原創(chuàng),歡迎轉載,轉載請保留出處:https://github.com/jasonGeng88/blog目錄
this 是什么
this 的四種綁定規(guī)則
綁定規(guī)則的優(yōu)先級
綁定例外
擴展:箭頭函數(shù)
this 是什么理解this之前, 先糾正一個觀點,this 既不指向函數(shù)自身,也不指函數(shù)的詞法作用域。如果僅通過this的英文解釋,太容易產生誤導了。它實際是在函數(shù)被調用時才發(fā)生的綁定,也就是說this具體指向什么,取決于你是怎么調用的函數(shù)。
this 的四種綁定規(guī)則this的4種綁定規(guī)則分別是:默認綁定、隱式綁定、顯示綁定、new 綁定。優(yōu)先級從低到高。
默認綁定什么叫默認綁定,即沒有其他綁定規(guī)則存在時的默認規(guī)則。這也是函數(shù)調用中最常用的規(guī)則。
來看這段代碼:
function foo() { } console.log( this.a ); var a = 2; foo(); //打印的是什么?
foo() 打印的結果是2。
因為foo()是直接調用的(獨立函數(shù)調用),沒有應用其他的綁定規(guī)則,這里進行了默認綁定,將全局對象綁定this上,所以this.a 就解析成了全局變量中的a,即2。
注意:在嚴格模式下(strict mode),全局對象將無法使用默認綁定,即執(zhí)行會報undefined的錯誤
function foo() { "use strict"; console.log( this.a ); } var a = 2; foo(); // Uncaught TypeError: Cannot read property "a" of undefined隱式綁定
除了直接對函數(shù)進行調用外,有些情況是,函數(shù)的調用是在某個對象上觸發(fā)的,即調用位置上存在上下文對象。
function foo() { console.log( this.a ); } var a = 2; var obj = { a: 3, foo: foo }; obj.foo(); // ?
obj.foo() 打印的結果是3。
這里foo函數(shù)被當做引用屬性,被添加到obj對象上。這里的調用過程是這樣的:
獲取obj.foo屬性 -> 根據引用關系找到foo函數(shù),執(zhí)行調用
所以這里對foo的調用存在上下文對象obj,this進行了隱式綁定,即this綁定到了obj上,所以this.a被解析成了obj.a,即3。
多層調用鏈function foo() { console.log( this.a ); } var a = 2; var obj1 = { a: 4, foo: foo }; var obj2 = { a: 3, obj1: obj1 }; obj2.obj1.foo(); //?
obj2.obj1.foo() 打印的結果是4。
同樣,我們看下函數(shù)的調用過程:
先獲取obj2.obj1 -> 通過引用獲取到obj1對象,再訪問 obj1.foo -> 最后執(zhí)行foo函數(shù)調用
這里調用鏈不只一層,存在obj1、obj2兩個對象,那么隱式綁定具體會綁哪個對象。這里原則是獲取最后一層調用的上下文對象,即obj1,所以結果顯然是4(obj1.a)。
隱式丟失(函數(shù)別名)注意:這里存在一個陷阱,大家在分析調用過程時,要特別小心
先看個代碼:
function foo() { console.log( this.a ); } var a = 2; var obj = { a: 3, foo: foo }; var bar = obj.foo; bar(); //?
bar() 打印的結果是2。
為什么會這樣,obj.foo 賦值給bar,那調用bar()為什么沒有觸發(fā)隱式綁定,使用的是默認綁定呢。
這里有個概念要理解清楚,obj.foo 是引用屬性,賦值給bar的實際上就是foo函數(shù)(即:bar指向foo本身)。
那么,實際的調用關系是:通過bar找到foo函數(shù),進行調用。整個調用過程并沒有obj的參數(shù),所以是默認綁定,全局屬性a。
隱式丟失(回調函數(shù))function foo() { console.log( this.a ); } var a = 2; var obj = { a: 3, foo: foo }; setTimeout( obj.foo, 100 ); // ?
打印的結果是2。
同樣的道理,雖然參傳是obj.foo,因為是引用關系,所以傳參實際上傳的就是foo對象本身的引用。對于setTimeout的調用,還是 setTimeout -> 獲取參數(shù)中foo的引用參數(shù) -> 執(zhí)行 foo 函數(shù),中間沒有obj的參與。這里依舊進行的是默認綁定。
顯示綁定相對隱式綁定,this值在調用過程中會動態(tài)變化,可是我們就想綁定指定的對象,這時就用到了顯示綁定。
顯示綁定主要是通過改變對象的prototype關聯(lián)對象,這里不展開講。具體使用上,可以通過這兩個方法call(...)或apply(...)來實現(xiàn)(大多數(shù)函數(shù)及自己創(chuàng)建的函數(shù)默認都提供這兩個方法)。
call與apply是同樣的作用,區(qū)別只是其他參數(shù)的設置上
function foo() { console.log( this.a ); } var a = 2; var obj1 = { a: 3, }; var obj2 = { a: 4, }; foo.call( obj1 ); // ? foo.call( obj2 ); // ?
打印的結果是3, 4。
這里因為顯示的申明了要綁定的對象,所以this就被綁定到了obj上,打印的結果自然就是obj1.a 和obj2.a。
硬綁定function foo() { console.log( this.a ); } var a = 2; var obj1 = { a: 3, }; var obj2 = { a: 4, }; var bar = function(){ foo.call( obj1 ); } bar(); // 3 setTimeout( bar, 100 ); // 3 bar.call( obj2 ); // 這是多少
前面兩個(函數(shù)別名、回調函數(shù))打印3,因為顯示綁定了,沒什么問題。
最后一個打印是3。
這里需要注意下,雖然bar被顯示綁定到obj2上,對于bar,function(){...} 中的this確實被綁定到了obj2,而foo因為通過foo.call( obj1 )已經顯示綁定了obj1,所以在foo函數(shù)內,this指向的是obj1,不會因為bar函數(shù)內指向obj2而改變自身。所以打印的是obj1.a(即3)。
new 綁定js中的new操作符,和其他語言中(如JAVA)的new機制是不一樣的。js中,它就是一個普通函數(shù)調用,只是被new修飾了而已。
使用new來調用函數(shù),會自動執(zhí)行如下操作:
如果函數(shù)沒有返回其他對象,那么new表達式中的函數(shù)調用會自動返回這個新對象。
從第三點可以看出,this指向的就是對象本身。
看個代碼:
function foo(a) { this.a = a; } var a = 2; var bar1 = new foo(3); console.log(bar1.a); // ? var bar2 = new foo(4); console.log(bar2.a); // ?
最后一個打印是3, 4。
因為每次調用生成的是全新的對象,該對象又會自動綁定到this上,所以答案顯而易見。
綁定規(guī)則優(yōu)先級上面也說過,這里在重復一下。優(yōu)先級是這樣的,以按照下面的順序來進行判斷:
數(shù)是否在new中調用(new綁定)?如果是的話this綁定的是新創(chuàng)建的對象。 數(shù)是否通過call、apply(顯式綁定)或者硬綁定調用?如果是的話,this綁定的是 指定的對象。 數(shù)是否在某個上下文對象中調用(隱式綁定)?如果是的話,this綁定的是那個上下文對象。 果都不是的話,使用默認綁定。如果在嚴格模式下,就綁定到undefined,否則綁定到 全局對象。 var bar = foo()規(guī)則例外
在顯示綁定中,對于null和undefined的綁定將不會生效。
代碼如下:
function foo() { console.log( this.a ); } foo.call( null ); // 2 foo.call( undefined ); // 2
這種情況主要是用在不關心this的具體綁定對象(用來忽略this),而傳入null實際上會進行默認綁定,導致函數(shù)中可能會使用到全局變量,與預期不符。
所以對于要忽略this的情況,可以傳入一個空對象?,該對象通過Object.create(null)創(chuàng)建。這里不用{}的原因是,?是真正意義上的空對象,它不創(chuàng)建Object.prototype委托,{}和普通對象一樣,有原型鏈委托關系。
1. 這里傳null的一種具體使用場景是函數(shù)柯里化的使用
擴展:箭頭函數(shù)最后,介紹一下ES6中的箭頭函數(shù)。通過“=>”而不是function創(chuàng)建的函數(shù),叫做箭頭函數(shù)。它的this綁定取決于外層(函數(shù)或全局)作用域。
case 1 (正常調用)普通函數(shù)
function foo(){ console.log( this.a ); } var a = 2; var obj = { a: 3, foo: foo }; obj.foo(); //3
箭頭函數(shù)
var foo = () => { console.log( this.a ); } var a = 2; var obj = { a: 3, foo: foo }; obj.foo(); //2 foo.call(obj); //2 ,箭頭函數(shù)中顯示綁定不會生效case 2 (函數(shù)回調)
普通函數(shù)
function foo(){ return function(){ console.log( this.a ); } } var a = 2; var obj = { a: 3, foo: foo }; var bar = obj.foo(); bar(); //2
箭頭函數(shù)
function foo(){ return () => { console.log( this.a ); } } var a = 2; var obj = { a: 3, foo: foo }; var bar = obj.foo(); bar(); //3
通過上面兩個列子,我們看到箭頭函數(shù)的this綁定只取決于外層(函數(shù)或全局)的作用域,對于前面的4種綁定規(guī)則是不會生效的。它也是作為this機制的一種替換,解決之前this綁定過程各種規(guī)則帶來的復雜性。
注意:對于ES6之前,箭頭函數(shù)的替換版本是這樣的
// es6 function foo(){ return () => { console.log( this.a ); } } var a = 2; var obj = { a: 3, foo: foo }; var bar = obj.foo(); bar(); //3
通過上面兩個列子,我們看到箭頭函數(shù)的this綁定只取決于外層(函數(shù)或全局)的作用域,對于前面的4種綁定規(guī)則是不會生效的。它也是作為this機制的一種替換,解決之前this綁定過程各種規(guī)則帶來的復雜性。
注意:對于ES6之前,箭頭函數(shù)的替換版本是這樣的
// es6 function foo(){ return () => { console.log( this.a ); } } // es6之前的替代方法 function foo(){ var self = this; return () => { console.log( self.a ); } }總結
我們在使用js的過程中,對于this的理解往往覺得比較困難,再調試過程中有時也會出現(xiàn)一些不符合預期的現(xiàn)象。很多時候,我們都是通過一些變通的方式(如:使用具體對象替換this)來規(guī)避的問題。可問題一直存在那兒,我們沒有真正的去理解和解決它。
本文主要參考了《你不知道的JavaScript(上卷)》,對this到底是什么,具體怎么綁定的,有什么例外情況以及ES6中的一個優(yōu)化方向,來徹底搞清楚我們一直使用的this到底是怎么玩的。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/94043.html
摘要:徹底搞懂執(zhí)行機制首先我們大家都了解的是,是一門單線程語言,所以我們就可以得出是按照語句順序執(zhí)行的首先看這個顯然大家都知道結果,依次輸出,然而換一種這個時候再看代碼的順序執(zhí)行,輸出,,,。不過即使主線程為空,也是達不到的,根據標準,最低是。 徹底搞懂JavaScript執(zhí)行機制 首先我們大家都了解的是,JavaScript 是一門單線程語言,所以我們就可以得出: JavaScript 是...
摘要:瀏覽器是多進程的詳情看我上篇總結瀏覽器執(zhí)行機制的文章深入前端徹底搞懂瀏覽器運行機制瀏覽器每打開一個標簽頁,就相當于創(chuàng)建了一個獨立的瀏覽器進程。執(zhí)行異步操作事件完成,回調函數(shù)進入。主線程從讀取回調函數(shù)并執(zhí)行。 最近看了很多關于JS運行機制的文章,每篇都獲益匪淺,但各有不同,所以在這里對這幾篇文章里說的很精辟的地方做一個總結,參考文章鏈接見最后。本文博客地址 了解進程和線程 進程是應用...
摘要:瀏覽器是多進程的詳情看我上篇總結瀏覽器執(zhí)行機制的文章深入前端徹底搞懂瀏覽器運行機制瀏覽器每打開一個標簽頁,就相當于創(chuàng)建了一個獨立的瀏覽器進程。執(zhí)行異步操作事件完成,回調函數(shù)進入。主線程從讀取回調函數(shù)并執(zhí)行。 最近看了很多關于JS運行機制的文章,每篇都獲益匪淺,但各有不同,所以在這里對這幾篇文章里說的很精辟的地方做一個總結,參考文章鏈接見最后。本文博客地址 了解進程和線程 進程是應用...
摘要:網上看到過很多人寫的事件分發(fā)機制解析感覺表述都不是很清楚也可能沒有看到寫得好的文章所以自己重新看了一遍源碼來徹底搞清楚事件分發(fā)機制觸摸事件有哪些以及怎么從傳遞到大家可以上網查下,幾個重要方法的基本調用順序這些很容易搜到我們重點關注事件從到的 網上看到過很多人寫的事件分發(fā)機制解析,感覺表述都不是很清楚,也可能沒有看到寫得好的文章,所以自己重新看了一遍源碼,來徹底搞清楚Android事件分...
摘要:當這些異步任務發(fā)生的時候,它們將會被放入瀏覽器的事件任務隊列中去,等到運行時執(zhí)行線程空閑時候才會按照隊列先進先出的原則被一一執(zhí)行,但終究還是單線程。 瀏覽器是多進程的 showImg(https://segmentfault.com/img/remote/1460000019706956?w=815&h=517); Browser進程: 瀏覽器的主進程(負責協(xié)調、主控),只有一個。 負...
閱讀 2053·2021-11-11 16:55
閱讀 1395·2021-09-28 09:36
閱讀 1038·2019-08-29 15:21
閱讀 1571·2019-08-29 14:10
閱讀 2757·2019-08-29 14:08
閱讀 1628·2019-08-29 12:31
閱讀 3243·2019-08-29 12:31
閱讀 976·2019-08-26 16:47