對this的理解,我一直都是用一句話歸納:誰調用它,它就指向誰。javascript
好像也沒有什麼問題,但仔細看了<你不知道的JavaScript>這本書和網上一些文章後,發現this的原理還挺講究的。因而決定寫個概括。
(對了,無知的我實在沒想到原來bind也和this扯上關係。。)html
this的綁定規則有4種。分別是:
一、默認綁定
二、隱式綁定
三、顯示綁定
四、new綁定java
須要明確:this的值雖然會隨着函數使用場合的不一樣而發生變化,但有一個原則,它指向的是調用它所在的函數的那個對象。數組
function test(){ console.log(this.a); } var a = 1; test();
當調用test()時,由於應用了this的默認綁定,this.a被解析成全局變量a,this指向全局對象window。因此結果爲1。網絡
怎麼知道應用了默認綁定呢?當前test()是直接使用不帶任何修飾的函數引用進行調用的。這個簡單來講就是沒有任何前綴啊等東西,很純粹!而其調用位置是全局做用域,更能肯定除了默認綁定,沒法應用其餘規則了。app
若是懂了,那麼下面的例子也就會作了函數
function test(){ this.a = 2; console.log(a); } test();
已知調用函數test()的對象是window,因此this指向window,即this.a===window.a。因爲window能夠省略,所以簡寫成a。
至關於在全局做用域聲明瞭變量a,而且賦值a=2。
this.a = 2
-->window.a = 2
-->a = 2
因此結果爲2。oop
可能說得太囉嗦了,直接說下一種綁定。this
通俗地說就是一個函數,被看成引用屬性添加到了一個對象中了,而後以 「對象名.函數名()」 形式進行調用,這時若是函數引用有上下文對象,隱式綁定規則會把函數調用中的this綁定到這個上下文對象。.net
下面看下例子。
function test(){ console.log(this.a); } var obj = { a:3, test:test }; obj.test();
對象obj中包含兩個屬性a和test,其值分別是3和一個函數test()。
obj.test()表示函數引用時有上下文對象(也就是這裏的obj)。
根據隱式綁定規則,會把test()中的this綁定到這個上下文對象,即this被綁定到obj,this.a===obj.a。
因此結果爲3。
若是懂了,那麼下面的例子也就會作了
function test(){ console.log(this.a); } var obj2 = { a:4, test:test }; var obj1 = { a:400, obj2:obj2 } obj1.obj2.test()
看起來複雜了些,不過只要記住下面這個準則就能夠了。
對象屬性引用鏈中,只有上一層或者說最後一層在調用位置中起做用
因此只有obj2這個上下文對象生效,結果爲4。
這個網上貌似不多說起,隱式丟失,通俗來說就是「變low」了!
本來應用隱式綁定的,由於丟失綁定對象,變回應用默認綁定了!把this綁定到全局對象或undefined。
當對象將其引用屬性給了新的引用,再次調用這個新的引用時,本來this指向的對象就會改成指向window。
到底在說什麼,看個例子。
function foo(){ console.log(this.a); } var obj = { a:5, foo:foo }; var bar = obj.foo; //這句就是關鍵 var a = "oops, global"; bar(); // "oops, global"
b引用了test()函數自己,此時的b()是一個不帶任何修飾的函數調用,所以應用了默認綁定。
若是懂了,那麼下面的例子也就會作了
function foo(){ console.log(this.a); } function doFoo(fn){ fn(); } var obj = { a:5, foo:foo } var a = "oops, global"; doFoo(obj.foo); // "oops, global"
其實和上面的沒什麼區別,只是這裏把函數做爲參數傳遞了,參數傳遞是一種隱式賦值。此時的this指向的是調用它的函數的對象即全局對象,所以應用了默認綁定。
回顧一下隱式綁定,其關鍵是把函數看成引用屬性添加到了對象中,經過這個屬性間接引用這個函數,把this簡介(隱式)綁定到這個對象上。
若是不想在對象內部包含函數引用,而想簡單粗暴地在某個對象強制調用一個函數,這時就用到了函數的call()和apply()方法。
call和apply,以往我單純理解爲「控制this的指向」,如今才發現是原來是this綁定規則中的一種。
call和apply區別
apply接收的是數組參數,call接收的是連續參數。因此當傳入的參數數目不肯定時,多使用apply。
(tips:這裏推薦個方法記憶apply和call各自接收的參數:apply爲a開頭,數組Array也是a開頭,因此apply接收的是數組參數)
看下面例子。
function test(){ console.log(this.a); } var obj = { a:6 }; test.call(obj);
當調用test時強制把它的this綁定到obj上。因此this.a===obj.a,結果爲6。
注意:後續參數傳入的是原始值的話,會被轉換成它的對象形式(如字符串類型-->new String()如此類推)
此次咱們不用call,用apply。
var a = 0; function test(){ console.log(this.a); } var obj = {}; obj.a = 7; obj.m = test; obj.m.apply(); obj.m.apply(obj);
一樣的,對象obj包含了兩個屬性a和m,m引用了函數test()。
obj.m.apply(obj)即把test()這個函數中的this綁定到對象obj上。即this指向的是obj。因此this.a===obj.a。
「apply沒有參數怎麼辦,基本語法都是包含參數的啊,至少給個對象,讓this有個指向啊。」
apply定義了當沒有參數時,全局對象會自動默認成爲其第一個參數,apply()等價於apply(window)。
所以obj.m.apply(),test()中的this就指向了全局對象window,結果爲0。
幹嗎用的?解決前面提到的隱式丟失問題。
回顧當初隱式丟失的第二個例子。
function foo(){ console.log(this.a); } function doFoo(fn){ fn(); } var obj = { a:5, foo:foo } var a = "oops, global"; doFoo(obj.foo); // "oops, global"
將其改一下變成:
function foo(){ console.log(this.a); } function doFoo(fn){ fn.call(obj); } var obj = { a:5, foo:foo } var a = "oops, global"; doFoo(obj.foo);
依舊是建立了doFoo()這個函數,但在其內部手動調用了 obj.foo.call(obj),
把foo()強制綁定到了obj對象,以後不管如何調用doFoo(),它總會手動在obj上調用foo。
對於硬綁定,ES5提供了一個內置方法Function.prototype.bind,咱們把上面的例子再改!
function foo(){ console.log(this.a); } function doFoo(fn){ fn.bind(obj); } var obj = { a:5, foo:foo } var a = "oops, global"; doFoo(obj.foo);
一執行,個人天!啥也沒有啊??
這就對了,這是bind的「效果」,也是和apply、call的主要區別——延遲調用。
也就是說bind其實只是函數的引用,要想執行須要進行回調,即fn.bing(obj)(),
此時就能輸出5了。
好,如今把隱式丟失的第一個例子改動
function foo(){ console.log(this.a); } var obj = { a:5, foo:foo }; var bar = obj.foo.bind(obj); var a = "oops, global"; bar(); //5
爲了能證實bind的特色,函數在回調時執行,把bind改爲call,最後3行代碼變成
var bar = obj.foo.call(obj); var a = "oops, global"; bar(); //Uncaught TypeError: bar is not a function
結果報錯了,對,由於call和apply都是綁定後馬上執行的,都執行完了,bar就只是一個沒有賦值的變量而已。
總結下「顯示綁定三人組」:
共同點: 一、都用於控制this指向; 二、第一個參數都是this須要指向的對象,也就是上下文; 三、均可之後續參數傳遞; 四、沒有任何參數時,this都指向全局對象window 區別: 一、call、apply綁定後馬上執行,bind是延遲執行。換言之,當你但願改變上下文環境以後並不是當即執行,而是回調執行的時候,就使用bind()方法吧。
什麼是構造函數,一個函數被new調用時,該函數就是構造函數。
(變身前:普通函數;使用地攤貨new變身器,變身後:構造函數)
function test(){ this.x = 8; } var obj = new test(); console.log(obj.x);
你能夠簡單粗暴理解爲:就至關於test()被對象obj調用,其this指向obj。
但貌似很不規範。。
實際上,調用函數的是new關鍵字。
使用new來調用函數,即函數的「構造調用」時,咱們會構造一個新對象,
並把它綁定到test()調用中的this上。因此在代碼中,this就指向了新對象obj
判斷this的4種規則根據優先級從高到低排序以下:
一、函數在 new 中調用,this綁定的是這個新對象
二、函數經過 call、apply或bind 調用,this綁定的是指定對象
三、函數在某上下文對象中調用,this綁定的是這個上下文對象
四、以上都不是,使用默認綁定。(在全局做用域調用,this綁定window對象)
關於this書中還說起到不少其餘知識,例如軟綁定、this詞法箭頭函數等,就不概括到這裏了
概括前看過的相關書籍或文章:
一、<你不知道的JavaScript>(上)第二部分第二章 this全面解析
二、阮一峯的網絡日誌-JavaScript的this用法
http://www.ruanyifeng.com/blo...
三、chanzen的我的博客-call,apply,bind用法和意義
http://ovenzeze.coding.me/use...
四、腳本之家-JS中的this變量的使用介紹
http://www.jb51.net/article/4...