摘要:那默認(rèn)綁定到哪呢,一般是上,嚴(yán)格模式下是。這種情況下,函數(shù)里的默認(rèn)綁定為上下文對象,等價于打印故輸出。只接受兩個參數(shù),且第二個參數(shù)必須是數(shù)組,這個數(shù)組代表原函數(shù)的參數(shù)列表。即繼承原函數(shù)的原型將這個新對象綁定到此函數(shù)的上。
js 的 this 綁定問題,讓多數(shù)新手懵逼,部分老手覺得惡心,這是因為this的綁定 ‘難以捉摸’,出錯的時候還往往不知道為什么,相當(dāng)反邏輯。
讓我們考慮下面代碼:
var people = { name : "海洋餅干", getName : function(){ console.log(this.name); } }; window.onload = function(){ xxx.onclick = people.getName; };
在平時搬磚時比較常見的this綁定問題,大家可能也寫給或者遇到過,當(dāng)xxx.onclick觸發(fā)時,輸出什么呢 ?
為了方便測試,我將代碼簡化:
var people = { Name: "海洋餅干", getName : function(){ console.log(this.Name); } }; var bar = people.getName; bar(); // undefined
通過這個小例子帶大家感受一下this惡心的地方,我最開始遇到這個問題的時候也是一臉懵逼,因為代碼里的this在創(chuàng)建時指向非常明顯啊,指向自己 people 對象,但是實際上指向 window 對象,這就是我馬上要和大家說的 this 綁定規(guī)則。
1 . this什么是this ?在討論this綁定前,我們得先搞清楚this代表什么。
this是JavaScript的關(guān)鍵字之一。它是 對象 自動生成的一個內(nèi)部對象,只能在 對象 內(nèi)部使用。隨著函數(shù)使用場合的不同,this的值會發(fā)生變化。
this指向什么,完全取決于 什么地方以什么方式調(diào)用,而不是 創(chuàng)建時。(比較多人誤解的地方)(它非常語義化,this在英文中的含義就是 這,這個 ,但這其實起到了一定的誤導(dǎo)作用,因為this并不是一成不變的,并不一定一直指向當(dāng)前 這個)
2 . this 綁定規(guī)則掌握了下面介紹的4種綁定的規(guī)則,那么你只要看到函數(shù)調(diào)用就可以判斷 this 的指向了。
2 .1 默認(rèn)綁定考慮下面代碼:
function foo(){ var a = 1 ; console.log(this.a); // 10 } var a = 10; foo();
這種就是典型的默認(rèn)綁定,我們看看foo調(diào)用的位置,”光桿司令“,像 這種直接使用而不帶任何修飾的函數(shù)調(diào)用 ,就 默認(rèn)且只能 應(yīng)用 默認(rèn)綁定。
那默認(rèn)綁定到哪呢,一般是window上,嚴(yán)格模式下 是undefined。
2 .2 隱性綁定代碼說話:
function foo(){ console.log(this.a); } var obj = { a : 10, foo : foo } foo(); // ? obj.foo(); // ?
答案 : undefined 10
foo()的這個寫法熟悉嗎,就是我們剛剛寫的默認(rèn)綁定,等價于打印window.a,故輸出undefined ,
下面obj.foo()這種大家應(yīng)該經(jīng)常寫,這其實就是我們馬上要討論的 隱性綁定 。
函數(shù)foo執(zhí)行的時候有了上下文對象,即 obj。這種情況下,函數(shù)里的this默認(rèn)綁定為上下文對象,等價于打印obj.a,故輸出10 。
如果是鏈性的關(guān)系,比如 xx.yy.obj.foo();, 上下文取函數(shù)的直接上級,即緊挨著的那個,或者說對象鏈的最后一個。
2 .3 顯性綁定 2 .3 .1 隱性綁定的限制在我們剛剛的 隱性綁定中有一個致命的限制,就是上下文必須包含我們的函數(shù) ,例:var obj = { foo : foo },如果上下文不包含我們的函數(shù)用隱性綁定明顯是要出錯的,不可能每個對象都要加這個函數(shù) ,那樣的話擴展,維護(hù)性太差了,我們接下來聊的就是直接 給函數(shù)強制性綁定this。
2 .3 .2 call apply bind這里我們就要用到 js 給我們提供的函數(shù) call 和 apply,它們的作用都是改變函數(shù)的this指向,第一個參數(shù)都是 設(shè)置this對象。
兩個函數(shù)的區(qū)別:
call從第二個參數(shù)開始所有的參數(shù)都是 原函數(shù)的參數(shù)。
apply只接受兩個參數(shù),且第二個參數(shù)必須是數(shù)組,這個數(shù)組代表原函數(shù)的參數(shù)列表。
例如:
function foo(a,b){ console.log(a+b); } foo.call(null,"海洋","餅干"); // 海洋餅干 這里this指向不重要就寫null了 foo.apply(null, ["海洋","餅干"] ); // 海洋餅干
除了 call,apply函數(shù)以外,還有一個改變this的函數(shù) bind ,它和call,apply都不同。
bind只有一個函數(shù),且不會立刻執(zhí)行,只是將一個值綁定到函數(shù)的this上,并將綁定好的函數(shù)返回。例:
function foo(){ console.log(this.a); } var obj = { a : 10 }; foo = foo.bind(obj); foo(); // 10
(bind函數(shù)非常特別,下次和大家一起討論它的源碼)
2 .3 .2 顯性綁定開始正題,上代碼,就用上面隱性綁定的例子 :
function foo(){ console.log(this.a); } var obj = { a : 10 //去掉里面的foo } foo.call(obj); // 10
我們將隱性綁定例子中的 上下文對象 里的函數(shù)去掉了,顯然現(xiàn)在不能用 上下文.函數(shù) 這種形式來調(diào)用函數(shù),大家看代碼里的顯性綁定代碼foo.call(obj),看起來很怪,和我們之前所了解的函數(shù)調(diào)用不一樣。
其實call 是 foo 上的一個函數(shù),在改變this指向的同時執(zhí)行這個函數(shù)。
(想要深入理解 [call apply bind this硬綁定,軟綁定,箭頭函數(shù)綁定 ] 等更多黑科技 的小伙伴歡迎關(guān)注我或本文的評論,最近我會多帶帶做一期放到一起寫一篇文章)(不想看的小伙伴不用擔(dān)心,不影響對本文的理解)
2 .4 new 綁定 2 .4 .1 什么是 new學(xué)過面向?qū)ο蟮男』锇閷ew肯定不陌生,js的new和傳統(tǒng)的面向?qū)ο笳Z言的new的作用都是創(chuàng)建一個新的對象,但是他們的機制完全不同。
創(chuàng)建一個新對象少不了一個概念,那就是構(gòu)造函數(shù),傳統(tǒng)的面向?qū)ο?構(gòu)造函數(shù) 是類里的一種特殊函數(shù),要創(chuàng)建對象時使用new 類名()的形式去調(diào)用類中的構(gòu)造函數(shù),而js中就不一樣了。
js中的只要用new修飾的 函數(shù)就是"構(gòu)造函數(shù)",準(zhǔn)確來說是 函數(shù)的構(gòu)造調(diào)用,因為在js中并不存在所謂的"構(gòu)造函數(shù)"。
那么用new 做到函數(shù)的構(gòu)造調(diào)用后,js幫我們做了什么工作呢:
創(chuàng)建一個新對象。
把這個新對象的__proto__屬性指向 原函數(shù)的prototype屬性。(即繼承原函數(shù)的原型)
將這個新對象綁定到 此函數(shù)的this上 。
返回新對象,如果這個函數(shù)沒有返回其他對象。
第三條就是我們下面要聊的new綁定
2 .4 .2 new 綁定不嗶嗶,看代碼:
function foo(){ this.a = 10; console.log(this); } foo(); // window對象 console.log(window.a); // 10 默認(rèn)綁定 var obj = new foo(); // foo{ a : 10 } 創(chuàng)建的新對象的默認(rèn)名為函數(shù)名 // 然后等價于 foo { a : 10 }; var obj = foo; console.log(obj.a); // 10 new綁定
使用new調(diào)用函數(shù)后,函數(shù)會 以自己的名字 命名 和 創(chuàng)建 一個新的對象,并返回。
特別注意 : 如果原函數(shù)返回一個對象類型,那么將無法返回新對象,你將丟失綁定this的新對象,例:
function foo(){ this.a = 10; return new String("搗蛋鬼"); } var obj = new foo(); console.log(obj.a); // undefined console.log(obj); // "搗蛋鬼"2 .5 this綁定優(yōu)先級
過程是些無聊的代碼測試,我直接寫出優(yōu)先級了
new 綁定 > 顯示綁定 > 隱式綁定 > 默認(rèn)綁定3 . 總結(jié)
如果函數(shù)被new 修飾
this綁定的是新創(chuàng)建的對象,例:var bar = new foo(); 函數(shù) foo 中的 this 就是一個叫foo的新創(chuàng)建的對象 , 然后將這個對象賦給bar , 這樣的綁定方式叫 new綁定 .
如果函數(shù)是使用call,apply,bind來調(diào)用的
this綁定的是 call,apply,bind 的第一個參數(shù).例: foo.call(obj); , foo 中的 this 就是 obj , 這樣的綁定方式叫 顯性綁定 .
如果函數(shù)是在某個 上下文對象 下被調(diào)用
this綁定的是那個上下文對象,例 : var obj = { foo : foo }; obj.foo(); foo 中的 this 就是 obj . 這樣的綁定方式叫 隱性綁定 .
如果都不是,即使用默認(rèn)綁定
例:function foo(){...} foo() ,foo 中的 this 就是 window.(嚴(yán)格模式下默認(rèn)綁定到undefined). 這樣的綁定方式叫 默認(rèn)綁定 .4 . 面試題解析
1.
var x = 10; var obj = { x: 20, f: function(){ console.log(this.x); // ? var foo = function(){ console.log(this.x); } foo(); // ? } }; obj.f();
-----------------------答案---------------------
答案 : 20 10
解析 :考點 1. this默認(rèn)綁定 2. this隱性綁定
var x = 10; var obj = { x: 20, f: function(){ console.log(this.x); // 20 // 典型的隱性綁定,這里 f 的this指向上下文 obj ,即輸出 20 function foo(){ console.log(this.x); } foo(); // 10 //有些人在這個地方就想當(dāng)然的覺得 foo 在函數(shù) f 里,也在 f 里執(zhí)行, //那 this 肯定是指向obj 啊 , 仔細(xì)看看我們說的this綁定規(guī)則 , 對應(yīng)一下很容易 //發(fā)現(xiàn)這種"光桿司令",是我們一開始就示范的默認(rèn)綁定,這里this綁定的是window } }; obj.f();
2.
function foo(arg){ this.a = arg; return this }; var a = foo(1); var b = foo(10); console.log(a.a); // ? console.log(b.a); // ?
-----------------------答案---------------------
答案 : undefined 10
解析 :考點 1. 全局污染 2. this默認(rèn)綁定
這道題很有意思,問題基本上都集中在第一undefined上,這其實是題目的小陷阱,但是追棧的過程絕對精彩
讓我們一步步分析這里發(fā)生了什么:
foo(1)執(zhí)行,應(yīng)該不難看出是默認(rèn)綁定吧 , this指向了window,函數(shù)里等價于 window.a = 1,return window;
var a = foo(1) 等價于 window.a = window , 很多人都忽略了var a 就是window.a ,將剛剛賦值的 1 替換掉了。
所以這里的 a 的值是 window , a.a 也是window , 即window.a = window ; window.a.a = window;
foo(10) 和第一次一樣,都是默認(rèn)綁定,這個時候,將window.a 賦值成 10 ,注意這里是關(guān)鍵,原來window.a = window ,現(xiàn)在被賦值成了10,變成了值類型,所以現(xiàn)在 a.a = undefined。(驗證這一點只需要將var b = foo(10);刪掉,這里的 a.a 還是window)
var b = foo(10); 等價于 window.b = window;
本題中所有變量的值,a = window.a = 10 , a.a = undefined , b = window , b.a = window.a = 10;
3.
var x = 10; var obj = { x: 20, f: function(){ console.log(this.x); } }; var bar = obj.f; var obj2 = { x: 30, f: obj.f } obj.f(); bar(); obj2.f();
-----------------------答案---------------------
答案:20 10 30
解析:傳說中的送分題,考點,辨別this綁定
var x = 10; var obj = { x: 20, f: function(){ console.log(this.x); } }; var bar = obj.f; var obj2 = { x: 30, f: obj.f } obj.f(); // 20 //有上下文,this為obj,隱性綁定 bar(); // 10 //"光桿司令" 默認(rèn)綁定 ( obj.f 只是普通的賦值操作 ) obj2.f(); //30 //不管 f 函數(shù)怎么折騰,this只和 執(zhí)行位置和方式有關(guān),即我們所說的綁定規(guī)則
4. 壓軸題了
function foo() { getName = function () { console.log (1); }; return this; } foo.getName = function () { console.log(2);}; foo.prototype.getName = function () { console.log(3);}; var getName = function () { console.log(4);}; function getName () { console.log(5);} foo.getName (); // ? getName (); // ? foo().getName (); // ? getName (); // ? new foo.getName (); // ? new foo().getName (); // ? new new foo().getName (); // ?
-----------------------答案---------------------
答案:2 4 1 1 2 3 3
解析:考點 1. new綁定 2.隱性綁定 3. 默認(rèn)綁定 4.變量污染
function foo() { getName = function () { console.log (1); }; //這里的getName 將創(chuàng)建到全局window上 return this; } foo.getName = function () { console.log(2);}; //這個getName和上面的不同,是直接添加到foo上的 foo.prototype.getName = function () { console.log(3);}; // 這個getName直接添加到foo的原型上,在用new創(chuàng)建新對象時將直接添加到新對象上 var getName = function () { console.log(4);}; // 和foo函數(shù)里的getName一樣, 將創(chuàng)建到全局window上 function getName () { console.log(5);} // 同上,但是這個函數(shù)不會被使用,因為函數(shù)聲明的提升優(yōu)先級最高,所以上面的函數(shù)表達(dá)式將永遠(yuǎn)替換 // 這個同名函數(shù),除非在函數(shù)表達(dá)式賦值前去調(diào)用getName(),但是在本題中,函數(shù)調(diào)用都在函數(shù)表達(dá)式 // 之后,所以這個函數(shù)可以忽略了 // 通過上面對 getName的分析基本上答案已經(jīng)出來了 foo.getName (); // 2 // 下面為了方便,我就使用輸出值來簡稱每個getName函數(shù) // 這里有小伙伴疑惑是在 2 和 3 之間,覺得應(yīng)該是3 , 但其實直接設(shè)置 // foo.prototype上的屬性,對當(dāng)前這個對象的屬性是沒有影響的,如果要使 // 用的話,可以foo.prototype.getName() 這樣調(diào)用 ,這里需要知道的是 // 3 并不會覆蓋 2,兩者不沖突 ( 當(dāng)你使用new 創(chuàng)建對象時,這里的 // Prototype 將自動綁定到新對象上,即用new 構(gòu)造調(diào)用的第二個作用) getName (); // 4 // 這里涉及到函數(shù)提升的問題,不知道的小伙伴只需要知道 5 會被 4 覆蓋, // 雖然 5 在 4 的下面,其實 js 并不是完全的自上而下,想要深入了解的 // 小伙伴可以看文章最后的鏈接 foo().getName (); // 1 // 這里的foo函數(shù)執(zhí)行完成了兩件事, 1. 將window.getName設(shè)置為1, // 2. 返回window , 故等價于 window.getName(); 輸出 1 getName (); // 1 // 剛剛上面的函數(shù)剛把window.getName設(shè)置為1,故同上 輸出 1 new foo.getName (); // 2 // new 對一個函數(shù)進(jìn)行構(gòu)造調(diào)用 , 即 foo.getName ,構(gòu)造調(diào)用也是調(diào)用啊 // 該執(zhí)行還是執(zhí)行,然后返回一個新對象,輸出 2 (雖然這里沒有接收新 // 創(chuàng)建的對象但是我們可以猜到,是一個函數(shù)名為 foo.getName 的對象 // 且__proto__屬性里有一個getName函數(shù),是上面設(shè)置的 3 函數(shù)) new foo().getName (); // 3 // 這里特別的地方就來了,new 是對一個函數(shù)進(jìn)行構(gòu)造調(diào)用,它直接找到了離它 // 最近的函數(shù),foo(),并返回了應(yīng)該新對象,等價于 var obj = new foo(); // obj.getName(); 這樣就很清晰了,輸出的是之前綁定到prototype上的 // 那個getName 3 ,因為使用new后會將函數(shù)的prototype繼承給 新對象 new new foo().getName (); // 3 // 哈哈,這個看上去很嚇人,讓我們來分解一下: // var obj = new foo(); // var obj1 = new obj.getName(); // 好了,仔細(xì)看看, 這不就是上兩題的合體嗎,obj 有g(shù)etName 3, 即輸出3 // obj 是一個函數(shù)名為 foo的對象,obj1是一個函數(shù)名為obj.getName的對象5 . 箭頭函數(shù)的this綁定 (2017.9.18更新)
箭頭函數(shù),一種特殊的函數(shù),不使用function關(guān)鍵字,而是使用=>,學(xué)名 胖箭頭(2333),它和普通函數(shù)的區(qū)別:
箭頭函數(shù)不使用我們上面介紹的四種綁定,而是完全根據(jù)外部作用域來決定this。(它的父級是使用我們的規(guī)則的哦)
箭頭函數(shù)的this綁定無法被修改 (這個特性非常爽(滑稽))
先看個代碼鞏固一下:
function foo(){ return ()=>{ console.log(this.a); } } foo.a = 10; // 1. 箭頭函數(shù)關(guān)聯(lián)父級作用域this var bar = foo(); // foo默認(rèn)綁定 bar(); // undefined 哈哈,是不是有小伙伴想當(dāng)然了 var baz = foo.call(foo); // foo 顯性綁定 baz(); // 10 // 2. 箭頭函數(shù)this不可修改 //這里我們使用上面的已經(jīng)綁定了foo 的 baz var obj = { a : 999 } baz.call(obj); // 10
來來來,實戰(zhàn)一下,還記得我們之前第一個例子嗎,將它改成箭頭函數(shù)的形式(可以徹底解決惡心的this綁定問題):
var people = { Name: "海洋餅干", getName : function(){ console.log(this.Name); } }; var bar = people.getName; bar(); // undefined
====================修改后====================
var people = { Name: "海洋餅干", getName : function(){ return ()=>{ console.log(this.Name); } } }; var bar = people.getName(); //獲得一個永遠(yuǎn)指向people的函數(shù),不用想this了,豈不是美滋滋? bar(); // 海洋餅干
可能會有人不解為什么在箭頭函數(shù)外面再套一層,直接寫不就行了嗎,搞這么麻煩干嘛,其實這也是箭頭函數(shù)很多人用不好的地方
var obj= { that : this, bar : function(){ return ()=>{ console.log(this); } }, baz : ()=>{ console.log(this); } } console.log(obj.that); // window obj.bar()(); // obj obj.baz(); // window
我們先要搞清楚一點,obj的當(dāng)前作用域是window,如 obj.that === window。
如果不用function(function有自己的函數(shù)作用域)將其包裹起來,那么默認(rèn)綁定的父級作用域就是window。
用function包裹的目的就是將箭頭函數(shù)綁定到當(dāng)前的對象上。函數(shù)的作用域是當(dāng)前這個對象,然后箭頭函數(shù)會自動綁定函數(shù)所在作用域的this,即obj。
美滋滋,溜了溜了
參考書籍:你不知道的JavaScript<上卷> KYLE SIMPSON 著 (推薦)
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/51270.html
摘要:那默認(rèn)綁定到哪呢,一般是上,嚴(yán)格模式下是。這種情況下,函數(shù)里的默認(rèn)綁定為上下文對象,等價于打印故輸出。只接受兩個參數(shù),且第二個參數(shù)必須是數(shù)組,這個數(shù)組代表原函數(shù)的參數(shù)列表。即繼承原函數(shù)的原型將這個新對象綁定到此函數(shù)的上。 js 的 this 綁定問題,讓多數(shù)新手懵逼,部分老手覺得惡心,這是因為this的綁定 ‘難以捉摸’,出錯的時候還往往不知道為什么,相當(dāng)反邏輯。讓我們考慮下面代碼: ...
摘要:我的是忙碌的一年,從年初備戰(zhàn)實習(xí)春招,年三十都在死磕源碼,三月份經(jīng)歷了阿里五次面試,四月順利收到實習(xí)。因為我心理很清楚,我的目標(biāo)是阿里。所以在收到阿里之后的那晚,我重新規(guī)劃了接下來的學(xué)習(xí)計劃,將我的短期目標(biāo)更新成拿下阿里轉(zhuǎn)正。 我的2017是忙碌的一年,從年初備戰(zhàn)實習(xí)春招,年三十都在死磕JDK源碼,三月份經(jīng)歷了阿里五次面試,四月順利收到實習(xí)offer。然后五月懷著忐忑的心情開始了螞蟻金...
摘要:前端工程師學(xué)習(xí)資料,快速查找面試題,經(jīng)典技術(shù)文章的總結(jié),編程技巧,幫助學(xué)習(xí)者快速定位問題花點時間整理出一下前端工程師日常工作所需要的學(xué)習(xí)資料查找,幫助學(xué)習(xí)者快速掌握前端工程師開發(fā)的基本知識編程始于足下記住再牛逼的夢想也抵不住傻逼似的堅持蝴蝶 前端工程師學(xué)習(xí)資料,快速查找面試題,經(jīng)典技術(shù)文章的總結(jié),編程技巧,幫助學(xué)習(xí)者快速定位問題花點時間整理出一下web前端工程師日常工作所需要的學(xué)習(xí)資料...
閱讀 1048·2021-10-11 10:59
閱讀 3601·2021-09-26 09:55
閱讀 891·2019-08-30 15:55
閱讀 2650·2019-08-30 15:44
閱讀 434·2019-08-30 14:06
閱讀 680·2019-08-30 11:26
閱讀 3336·2019-08-30 10:49
閱讀 2466·2019-08-29 12:53