關於this的知識概括(通俗易懂版)

對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指向的是調用它的函數的對象即全局對象,所以應用了默認綁定。

三、顯示綁定(apply/call調用)

回顧一下隱式綁定,其關鍵是把函數看成引用屬性添加到了對象中,經過這個屬性間接引用這個函數,把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調用時,該函數就是構造函數。
(變身前:普通函數;使用地攤貨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...

相關文章
相關標籤/搜索