Javascript中函數調用和this的關系


例子先行:
var myObject={
    foo:"bar",
    func:function(){
        var self=this;
        console.log("outerfunc:this.foo="+this.foo);
        console.log("outerfunc:self.foo="+self.foo);
        (function(){
            console.log("innerfunc:this.foo="+this.foo);
            console.log("innerfunc:self.foo="+self.foo);
        }());
    }
}
myObject.func();

輸出結果為:

 

對於上面的結果,第一個和第二個我是不意外的,第三和第四個竟然不知道為什么,雖然之前總結了作用域和閉包,但是關於this的問題還是搞不清楚,所以准備寫一篇總結來強化一下自己的這些基本概念。

 

一、函數調用的形式

就像孔乙己回字有四種寫法,javascript中的函數調用同樣也有四種方式,分別如下:

1.作為一個函數進行的調用

2.作為一個對象的方法進行的調用

3.作為構造器進行的調用

4.通過apply()call()函數進行的調用

 

在進行C#或者java編程的時候,我們都知道如果函數在聲明的時候有定義了參數,那么函數再被調用的時候,也要相應的傳入參數,否則不能正確使用,javascript和上面一些高級語言不一樣的是,它的參數數目可以和聲明時候不一樣,參數不夠undefined代替,參數多了那么就會截斷。

 

有趣的是:javascript中所有的函數調用都會傳遞兩個隱式參數:argumentsthis

arguments 參數不是我想要詳細說的,所以這個就不展開學習了,它的特點是有length屬性、能夠被遍歷、有點像數組,但是卻不是數組。

this 參數也挺有意思,在四種調用中,傳入的this還不一樣

 

二、函數調用過程中傳入的this

Javascript中的thisC#java中的還是有區別的,C#this代表的意義可能是實例本身,而javascript只用函數作為方法的時候才和這個代表的意義差不多,其他三種可能會不一樣,javascript中的this依賴於函數的調用,而C#等則是依賴於函數的聲明,this也稱為函數的上下文

1)作為函數進行調用

function sum(){
alert(this);
return1+2+3;
}

alert(sum());

結果:

上面的代碼就是最常用函數調用方式,這種方式就是作為函數進行的調用,從上圖還可以看到函數內部的this參數就是全局的對象window對象。

2)作為對象的方法進行調用

var obj={
  name:"大橙子"
};

function sum(){
  alert(this);
  alert(this.name);
  return 1+2+3;

}

obj.func=sum;
obj.func();

結果:

和函數方式調用不同的是,這種調用方式傳遞的this參數就是這個對象本身,這就和C#當中一個方法所屬的對象在該方法體內部可以用this形式進行引用差不多。

上面的代碼也說明了這點,正因為thissum中被當作了obj來使用,才能打印出name這個屬性

 

3)作為構造器進行調用

對於構造器,要先理解javascript中的new操作是干嘛的.之前總結一篇關於原型的文章,里面的配圖也提到了constructor這個東西,但是沒有深入研究,正好這次把這部分補全。

先看如下的例子:

var person = function(){

this.name = "大橙子";

this.age = 26;

this.say = function(){

return "Hello!";

}

}

var p = new person();

console.log(p.name);

console.log(p.age);

console.log(p.say());

結果:

[Web瀏覽器] "大橙子"       

[Web瀏覽器] "26"       

[Web瀏覽器] "Hello!"   

 

上面是一個簡單的使用構造器進行實例化對象的例子,就像上次原型的文章中所畫的圖一樣,這個過程也可以這樣做:

 

這個過程就是用new創建一個實例的過程,new的過程是這樣的:

1)新建一個對象p=new Object();

2)設置原型鏈p.__proto__=person.prototype;

3)讓person中的this指向p,執行person的函數體。

4)判斷person的返回值類型:

如果是值類型,就丟棄它,還是返回p

如果是引用類型,就返回這個引用類型的對象,替換掉p

 

對於(3)和(4)不是很好理解:

3)的理解是:就像例子中的代碼,我雖然在person中寫了this,但是不調用person,我就不知道this是誰,所以new的第三步幫我做了這個,讓this指向了p,執行此時執行person函數體的時候,就相當於使用p這個對象,this.name就好比p.name……

4)理解:

  我這個例子的當中,person函數體內沒有返回值,所以返回的是undefinedundefined是值類型,所以就舍棄了,返回p

  如果返回值寫成了 return this因為第三步thisp的引用,所以這樣寫也是返回的p

  如果是其他的引用類型,就用其他來代替p返回。

上面的過程也就是使用構造器的方式來調用函數。

 

4)使用apply()call()來進行調用

上面的三種方式在進行使用的時候,可以說他們的this都是被固定化了的,window對象、調用對象,或者新創建的對象實例,但是如果想要自由指定函數的上下文,就要使用apply()或者call()函數。

例如:

function sum(){

var result = 0;

for(var n=0;n<arguments.length;n++){

result += arguments[n];

}

this.result = result;

}

var obj1 ={};

var obj2 ={};

sum.apply(obj1,[1,2,3]);

sum.call(obj2,4,5,6);

console.log(obj1.result);

console.log(obj2.result);

結果:

[Web瀏覽器] "6"        

[Web瀏覽器] "15"       

 

這個例子當作我們可以看到,作為sum函數的第一個參數的obj1obj2,分別被當成了sum函數內的this上下文。

apply()call()的區別在於,一個接受參數的數組,另一個是分離開的。

以后可以使用這樣的方式,指定this上下文,比較靈活。

 

再看new操作
var  p = new person( );
就相當於:
var p  = {};
person.apply(p);
p.__proto__ = person.prototype;

 

 

總結:函數的調用以及它和this上下文的關系,可以簡單描述為如下:

作為函數調用,this相當於window

作為對象的方法,this相當於對象

作為構造器調用,this相當於實例化的對象

apply()call()調用,this可以進行指定。

 

回頭再看看上面的例子:

調用函數方式是作為對象的方法,所以第一個和第二個輸出的myObjectfoo,也就是bar

第三個

 

(function(){
console.log("innerfunc:this.foo="+this.foo);
console.log("innerfunc:self.foo="+self.foo);
}());

關於這個為什么輸出的是undefined,原因是this在這里是window對象的引用,為什么可以看看下面的連接

https://www.zhihu.com/question/21958425

第四個:

上面是一個自調用函數,我的理解是首先javascript的作用域是函數級別的,所以上面的自調用函數和外層function不是一個作用域,但是他們在一條鏈上,所以第四個self.foo的時候,因為自身的作用域內沒有self這個對象,就向上找,上層有,於是就輸出上層的self.foo也就是bar。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM