例子先行: 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中所有的函數調用都會傳遞兩個隱式參數:arguments和this。
arguments 參數不是我想要詳細說的,所以這個就不展開學習了,它的特點是有length屬性、能夠被遍歷、有點像數組,但是卻不是數組。
this 參數也挺有意思,在四種調用中,傳入的this還不一樣
二、函數調用過程中傳入的this
Javascript中的this和C#、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形式進行引用差不多。
上面的代碼也說明了這點,正因為this在sum中被當作了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函數體內沒有返回值,所以返回的是undefined,undefined是值類型,所以就舍棄了,返回p
②如果返回值寫成了 return this。因為第三步this是p的引用,所以這樣寫也是返回的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函數的第一個參數的obj1和obj2,分別被當成了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可以進行指定。
回頭再看看上面的例子:
調用函數方式是作為對象的方法,所以第一個和第二個輸出的myObject的foo,也就是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。