一:函數內部的對象:arguments和this
我們都知道js中arguments對象主要是保存函數的參數(如果不知道arguments,在有參數的函數中輸出一下就知道了),但是這個對象還有一個屬性爲callee,它是一個指針,指向擁有這個arguments對象的函數。例如階乘函數:
function factorial(num){
if(num <= 1){
return 1;
}else{
return num * factorial(num-1);
}
}
console.log(factorial(5));//120
由於函數的執行和函數名factorial緊緊耦合在了一起,爲了消除這種緊密耦合的現象,可以使用arguments.callee:
function factorial2(num){
if(num <= 1){
return 1;
}else{
return num * arguments.callee(num-1);
}
}
console.log(factorial2(5)); //120
兩者的區別如下:
var trueFactorial = factorial2;
factorial = function () {
return 0;
}
console.log(trueFactorial(6)); //720
console.log(factorial(6)); //0
上面的trueFactorial實際上是在另一個位置上保存了一個函數的指針。然後又將一個簡單的函數賦值給factorial變量。如果不用arguments.callee,那麼,調用trueFactorial(6)的時候就返回0.但是上面的例子解除了代碼和函數的耦合狀態,因此trueFactorial仍然可以正常的計算階乘。
二:this的意義?
this引用的是函數執行的環境對象(當我們在網頁的全局作用域中調用函數的時候,this對象就是window對象)。也就是說,在全局作用域中的所有變量和函數都是window對象的屬性和方法,例如:
var color = 'green'; //相當於window.color = 'green'
var obj1 = {color:"blue"};
function saycolor(){
console.log(this.color);
}
saycolor(); //green,這裏的this代表window
window.saycolor() //green
obj1.saycolor = saycolor;
obj1.saycolor(); //blue
三:caller屬性是什麼?
caller屬性保存着調用當前函數的函數的引用。如果是在全局作用域中調用當前的函數,它的值就是null。例子如下:
function a(){
console.log(a.caller); //返回正在調用俺的函數
}
function b(){
a();
}
a(); //null
b(); //function b(){a();}
但是爲了實現更鬆散的耦合,也可以通過arguments.callee.caller來達到相同的目的:
function a(){
console.log(arguments.callee.caller); //返回正在調用俺的函數b
}
function b(){
a();
}
a(); //null
b(); //function b(){a();}
不過arguments.callee在嚴格模式下會導致錯誤。
四:call()和apply()的應用
每個函數都包含兩個屬性:length和prototype。其中length表示函數希望接受地參數的個數,例如:
function a(){
console.log(000);
}
function b(arg1){
console.log(111);
}
function c(arg1,arg2){
console.log(222)
}
console.log(a.length); //0
console.log(b.length); //1
console.log(c.length); //2
prototype屬性是非常重要的,它指向一個對象,包含了所有它生成實例下的所有屬性和方法,再次不再贅述,我的prototype博文中另有詳細講解。
每個函數都包含兩個非繼承而來的方法:apply()和call()。這兩個方法的用途都是在特定的作用域中調用函數,其實就是所在函數體內this對象的值。
1.apply()接受兩個參數:一個是在其中運行函數的作用域,另一個是參數數組。其中:第二個參數可以是array的實例,也可以是arguments對象。實例如下:
function fun(arg1,arg2){
return arg1 + arg2;
}
function callfun1(arg1,arg2){
return fun.apply(this,arguments); //傳入arguments對象
}
function callfun2(arg1,arg2){
return fun.apply(this,[arg1,arg2])//傳入參數數組
}
console.log(callfun1(3,7)); //10
console.log(callfun2(3,2)); //5
2.call()方法和apply()方法的作用相同,曲別在於接收參數的方式不同。用call方法傳遞參數的時候必須直接傳遞,也就是傳遞給函數的參數必須一一列舉出來,例子如下:
function fun(arg1,arg2){
return arg1 + arg2;
}
function callfun3(arg1,arg2){
return fun.call(this,arg1,arg2);
}
console.log(callfun3(2,6)); //8
但是傳遞參數並非apply()和call()用的最多的地方,他們真正強大的地方時能夠擴充函數賴以運行的作用域。例子如下:
var age = 18;
var person = {age:17};
function say(){
console.log(this.age);
}
say();
say.call(this); //18
say.call(window) //18
say.call(person) //17,此時運行的作用域爲person,相當於把person的作用域傳給了say函數裏面
3.bind()用法。bind()方法會創建一個函數的實例,其this值會被綁定到傳給bind()函數的值。例如:
var age = 18;
var person = {age:17};
function say(){
console.log(this.age);
}
var psay = say.bind(person);
console.log(psay) //function say(){console.log(this.age);}
psay(); //17,相當於把person作用域傳給了say()構造函數裏面,它和apply和call的區別就是,bind必須還要創建一個函數實例,也就是bind不能直接執行傳參後的函數。
上面代碼中person對象的作用域傳到了bind()新創建的psay()函數執行環境裏面,所以執行完輸出this.age=17.