JavaScript中的this(你不知道的JavaScript)

來源:互聯網
上載者:User

標籤:javascript this   javascript this 綁定規則   javascript柯裡化   es6箭頭函數   

JavaScript中的this,剛接觸JavaScript時大家都在大肆渲染說其多麼多麼的靈巧重要,然而自己並不關心;隨著自己對JavaScript一步步深入瞭解,突然恍然大悟,原來它真的很重要!所以,自己花費了大約2周的時間去查貼、翻閱之前讀的書籍,將this的全貌展示如下。

一、this是什麼--基於調用位置的上下文;調用位置不同,this值不同。大家都JavaScript中this存在兩個誤解:
(1)this指向函數自身
(2)this指向函數的範圍

範圍無法通過JavaScript代碼訪問,它存在於JavaScript引擎內部。每當把this和詞法範圍的尋找混合使用時,一定要提醒自己,這是無法實現的!

this是在運行時進行綁定的,並不是在編寫時綁定,它的上下文取決於函數調用時的各種條件。this的綁定和函式宣告的位置沒有任何關係,只取決於函數的調用位置(也就是函數的調用方式)!

樣本:
var foo = "golbal foo";var myObj = {foo : 'myObj foo'};var say = function(){console.log(this.foo);}myObj.say = say;myObj.say();//結果:myObj foosay();//結果:golbal foo
二、為什麼使用this
var me = {name: "fenfei"};//不使用this,調用function speak(name){console.log("Hello, I'm "+ name);}speak(me.name);//Hello, I'm fenfei//使用this,調用function speak(){console.log("Hello, I'm "+ this.name);}speak.call(me);//Hello, I'm fenfei

this提供了一種更優雅的方式來隱式“傳遞”對象引用,因此可以將API設計得更加簡潔並易於複用。

三. this的四大綁定規則1. 預設綁定--函數調用類型:獨立函數調用,this指向全域對象。
var a = "foo";function foo(){console.log(this.a);}foo();// "foo"

在嚴格模式下,全域對象將無法使用預設綁定,因此this會綁定到undefined。

var a = "foo";function foo(){"use strict";console.log(this.a);}foo();// TypeError:this is undefined
2. 隱式綁定--調用位置是否有內容物件,或者說被某個對象擁有或者包含
function foo(){console.log(this.a);}var obj1 = {a : 2,foo : foo}var obj2 = {a : 1,obj1 : obj1}obj2.obj1.foo();//結果:2

當foo()被調用時,它的落腳點指向obj1對象,隱式綁定規則會把函數調用中的this綁定到這個內容物件。
對象屬性引用鏈中只有最頂層或者說最後一次層會影響調用位置。

注意:隱式丟失

常見的this綁定問題就是“隱式綁定”的函數會丟失綁定對象,也就是“預設綁定”,從而把this綁定到全域對象(嚴格模式下為undefined)。

var a = "foo";function foo(){console.log(this.a);}var obj = {a : 2,foo : foo}var bar = obj.foo;bar();//"foo"

雖然bar是obj.foo的一個引用,但是實際上,它引用的是foo函數本身,因此此時的bar()其實是不帶任何修飾的函數調用,因此應用了預設綁定。

var a = "foo";function foo(){console.log(this.a);}function doFoo(fn){//var fn = obj.foofn();}var obj = {a : 2,foo : foo}doFoo(obj.foo);//"foo"setTimeout(obj.foo, 100);//"foo"

參數傳遞其實就是一種隱式賦值,因此傳入函數式會被隱式賦值(LHS)

3. 顯示綁定(1)call、apply
(2)硬綁定
function foo(){console.log(this.a);}var obj = {a:2 };var bar = function(){foo.call(obj);};bar(); //2setTimeout( bar, 1000);//2bar.call( window );//2

建立了函數bar(),在其內部手動調用了foo.call(obj),因此強制把foo的this綁定到了obj上。無論之後如何調用函數bar,總會手動在obj上調用foo。這種顯示的強制綁定,稱為“硬綁定”。

4. new綁定new調用函數會自動執行下面操作:
(1)建立(或者說構造)一個全新的對象;
(2)這個新對象會被執行[[原型]]串連;
(3)這個新對象會綁定到函數調用的this;
(4)如果函數沒有返回其他對象,那麼new運算式中的函數調用會自動返回這個新對象。
四、優先順序

瞭解了函數調用中this綁定的四條規則,需要做的就是找到函數的調用位置並判斷對應哪條規則。

1. 函數是否是new綁定?如果是,this綁定的是新建立的對象。
var bar = new Foo();
2. 函數是否通過call、apply顯示綁定或硬綁定?如果是,this綁定的是指定的對象。
var bar = foo.call(obj);
3. 函數是否在某個內容物件中隱式調用?如果是,this綁定的是那個內容物件。
var bar = obj.foo();
4. 上述全不是,則使用預設綁定。如果在嚴格模式下,就綁定到undefined,否則綁定到全域window對象。
var bar = foo();
五、綁定this注意點1. 忽略this

把null或undefined作為this的綁定對象傳入call、apply、bind,調用時會被忽略,實際應用的是預設綁定規則!

function foo(){console.log(this.a);}var a = 1;foo.call(null, 2);//1foo.apply(undefined, [3]);//1
2. 間接引用
function foo(){console.log(this.a);}var a = 2;var o = { a : 3,foo : foo};var p = { a : 4};o.foo();//3(p.foo = o.foo)();//2 間接引用var pfoo = o.foo;pfoo();//2 隱式丟失
注意:同上述“隱式丟失”結果一樣,但是過程不太一樣,區分!!
3. ES6箭頭函數

箭頭函數不使用this的四種標準規則,而是根據外層(函數或者全域)範圍來決定this。
箭頭函數的綁定無法被修改。常用於回呼函數中,如事件處理器或定時器。和ES6之前代碼中的this = self機制一樣。

function foo(){setTimeout(()=>{console.log(this.a);},100);}var obj = { a : 2};foo.call(obj);
等價於:
function foo(){var self = this;setTimeout(function(){console.log(self.a);},100);}var obj = { a : 2};foo.call(obj);
六、詳解如何確定this1.(隱式綁定)如果某個對象中某個成員是個function,當從這個對象上調用這個方法時this指向當前對象。
var FenFei = {firstname:"li",lastname:"gang",timeTravel:function(year){console.log(this.firstname + " " + this.lastname + " is time traveling to " + year);}}FenFei.timeTravel(2014);//li gang is time traveling to 2014(父/擁有者對象:FenFei)
2.(隱私綁定)可以通過建立一個新的對象,來引用FenFei對象上的timeTravel方法。
var Camile = {firstname:"li",lastname:"yunxia"}Camile.timeTravel = FenFei.timeTravel;Camile.timeTravel(2014);//li yunxia is time traveling to 2014(父/擁有者對象:Camile)
注意:此樣本同上述“隱式丟失”、“間接引用”區分!!!
3.(隱式丟失)使用變數儲存FenFei.timeTravel方法的引用
var getTimeTravel = FenFei.timeTravel;getTimeTravel(2014);//undefined undefined is time traveling to 2014(父/擁有者對象:Window;window對象裡並沒有firstName和lastName屬性)

PS:謹記方法中的this將指向調用它的那個父/擁有者對象!無論何時,當一個函數被調用,我們必須看方括弧或者是圓括弧左邊緊鄰的位置,如果我們看到一個引用(reference),那麼傳到function裡面的this值就是指向這個方法所屬於的那個對象,如若不然,那它就是指向全域對象的。

4. 非同步呼叫的方法內部的this
<button id="async">點擊我</button>
var btnDom = document.getElementById("async");btnDom.addEventListener('click',FenFei.timeTravel);//undefined undefined is time traveling to [object MouseEvent](父/擁有者對象:button)btnDom.addEventListener('click',function(e){FenFei.timeTravel(2014);//li gang is time traveling to 2014(父/擁有者對象:FenFei)});
5.(new綁定)建構函式裡的this當使用建構函式建立一個對象的執行個體時,建構函式裡的this就是建立的執行個體。
var TimeTravel = function(fName, lName){this.firstname = fName;this.lastname = lName;}var FenFei = new TimeTravel("li", "gang");console.log(FenFei.firstname + " " + FenFei.lastname);//li gang
6.(顯示綁定)call和apply方法設定this值
var Camile = {firstname:"li",lastname:"yunxia"}FenFei.timeTravel.call(Camile,2014);//li yunxia is time traveling to 2014(指定this對象為Camile)FenFei.timeTravel.apply(Camile,[2014]);//li yunxia is time traveling to 2014(指定this對象為Camile)
PS:注意和上述“2”處做對比
7.(顯示綁定)bind將函數綁定至某個對象(ES5)
function f(y){return this.x + y;}var o = {x:1};/* f.bind(o)返回一個新函數,調用g(2)會把原始的函數f()當作o的方法來調用 */var g = f.bind(o);g(2);// 3

補充:ES5之前方法類比bind()方法

function myBind(f, o){if(f.bind){return f.bind(o);}else{return function(){return f.apply(o, arguments);}}}

bind的作用和apply,call類似都是改變函數的execute context,也就是runtime時this關鍵字的指向。但是使用方法略有不同。一個函數進行bind後可稍後執行。 

七、補充1. 使用new為函數建立多個執行個體的時候,這些執行個體會共用prototype。當在一個執行個體裡直接給this添加屬性的時,會隱藏prototype中與之同名的屬性。
如果想訪問prototype中的屬性值而不是自己的設定的屬性值:
(1)刪除執行個體自己添加的屬性: delete 執行個體名.屬性名稱
(2)直接存取prototype中的屬性:Thing.prototype.name
function Thing(){}Thing.prototype.name = "leegang";Thing.prototype.getName = function(){console.log(this.name);}Thing.prototype.setName = function(newName){this.name = newName;}Thing.prototype.deleteName = function(){delete this.name;}var thing = new Thing();/*thing:Thing {}*/thing.setName("liyunxia");/*thing:Thing {name: "liyunxia"}*/thing.getName();//結果:liyunxiathing.deleteName();/*thing:Thing {}*/thing.getName();//結果:leegangthing.__proto__為Thing {name: "leegang"}thing.name = "liyunxia";/*Thing {name: "liyunxia"}*/thing.getName();//結果:liyunxiadelete thing.name;/*thing:Thing {}*/thing.getName();//結果:leegang thing.__proto__為Thing {name: "leegang"}
2. 柯裡化把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,如果其他的參數是必要的,返回接受餘下的參數且返回結果的新函數
var sum = function(x, y){return x + y;}var succ = sum.bind(null, 1);succ(2);//3function f(y, z){return this.x + y +z;}var g = f.bind({x:1}, 2);g(3);//6
轉載請寫明出處:http://blog.csdn.net/ligang2585116

著作權聲明:本文為博主原創文章,未經博主允許不得轉載。轉載請標明出處:http://blog.csdn.net/ligang2585116!

JavaScript中的this(你不知道的JavaScript)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.