JavaScript基礎---函數

一、JavaScript函數沒有函數重載

1.函數參數arguments對象,類數組對象

正是由於函數體內使用arguments對象接收傳遞的參數,所以即便你定義的函數只接收兩個參數,在調用函數時也未必一定要傳遞兩個參數。

2.函數重載

在其他語言中如Java,所謂函數重載就是方法名相同參數不同的所有方法,因爲在Java中只要函數簽名(接受的參數類型和數量)不同,就認爲是不同的函數。但是在JavaScript中函數沒有簽名,所以做不到真正的函數重載

3.使用arguments對象來彌補沒有函數重載的缺憾

function doAdd(){
    if(arguments.length == 1){
        return arguments[0]+10;
    }else if(arguments.length == 2){
        return arguments[0]+arguments[1];
    }else{
        let sum = 0;
        for(let i = 0; i < arguments.length; i++){
            sum += arguments[i];
        }
        return sum;
    }
}

二、函數聲明與函數表達式

1.函數聲明語法

function 函數名(參數:可選) {
    函數體
}

如果函數在函數體內,或者位於程序的最頂部的話,那它就是一個函數聲明

2.函數表達式

function 函數名(可選)(參數:可選) {
    函數體
}

如果函數作爲表達式的一部分那麼該函數就是函數表達式;
創建一個function關鍵字後沒有標識符的函數,這種情況創建的函數叫匿名函數

3.函數聲明提升

解析器會率先讀取函數聲明,並使其在執行任何代碼之前可訪問;而函數表達式則必須等到執行器執行到它所在的代碼行,纔會真正被解釋執行

sayHello();  //hello
function sayHello(){
    console.log('hello');
}

上述代碼不會報錯,因爲在代碼開始執行之前,解析器就已經通過一個名爲函數聲明提升的過程讀取並將函數聲明添加到執行環境中

sayHello();  //報錯
var sayHello = function(){
    console.log('hello');
}

上述代碼會產生錯誤,因爲函數位於一個賦值語句中,在執行到函數所在語句之前,變量sayHello中不會保存對函數的引用

三、遞歸

1.什麼是遞歸函數

遞歸函數是在一個函數中調用自身所構成的

function recursion(num){
    if(num <= 1){
        return 1;
    }
    else{
        return num * recursion(num-1);
    }
}

上述函數表面上看沒什麼問題,但是如果做如下操作就會出問題

var temp = recursion;
recursion = null;
temp(10); //報錯:recursion不是個函數

第一句代碼temp指向了原始函數,recursion不再指向原始函數,調用temp(10)的時候用到了recursion(num-1),但是recursion已經不再指向原始函數,所以運行報錯

2.arguments.callee

上述情況使用arguments.callee來解決問題,arguments.callee是一個指向正在執行的函數的指針,使用arguments.callee來代替函數名更保險

function recursion(num){
    if(num <= 1){
        return 1;
    }
    else{
        return num * arguments.callee(num-1);
    }
}

3.命名函數表達式

var recursion = (function recur(num){
    if(num <= 1){
        return 1;
    }
    else{
        return num * recur(num-1);
    }
})

上述代碼中即便把函數賦值給另一個變量,函數的名字recur仍然有效,所以不會影響遞歸

四、閉包

1.閉包的作用域鏈

閉包是指有權訪問另一個函數作用域中的變量的函數

function createComparisonFunction(propertyName){
    return function(object1,object2){
        var value1 = object1[propertyName];
        var value2 = object2[propertyName];
        if(value1 < value2){
            return -1;
        } else if(value1 > value2){
            return 1;
        } else{
            return 0;
        }
    }
}
var compare = createComparisonFunction('name');
var result = compare({name:'Nicholas'},{name:'Greg'});
compare = null;

在匿名函數從createComparisonFunction()中被返回後,它的作用域鏈被初始化爲包含createComparisonFunction()函數的活動對象和全局變量對象。createComparisonFunction()函數執行完畢後,其執行環境的作用域鏈會被銷燬但是它的活動對象不會被銷燬,因爲匿名函數的作用域鏈仍然在引用這個活動對象;直到匿名函數被銷燬後,createComparisonFunction()的活動對象纔會被銷燬

2.閉包與變量

閉包只能取得包含函數中任何變量的最後一個值,閉包所保存的是整個變量對象,而不是某個特殊的變量

function createFunctions(){
    var result = new Array();
    for(var i = 0; i < 10; i++){
        result[i] = function(){
            return i;
        };
    }
    return result;
}
var result = createFunctions();
result.forEach(function(func){
    console.log(func()); //10
})

上述代碼每個函數都會返回10,因爲每個函數的作用域鏈中都保存着createFunctions()函數的活動對象,所以它們引用的都是同一個變量i。當createFunctions()函數返回後,變量i的值是10,此時每個函數都引用着保存變量i的同一個變量對象,所以每一個函數內部的i的值都是10。如果上述代碼中的var i改成let i就會出現預期的效果,因爲let聲明的變量i屬於for循環塊級作用域中不屬於createFunctions()函數的活動對象

function createFunctions(){
    var result = new Array();
    for(var i = 0; i < 10; i++){
        result[i] = function(num){
            return function(){
                return num;
            }
        }(i);
    }
    return result;
}
var result = createFunctions();
result.forEach(function(func){
    console.log(func()); //10
})

把i作爲參數傳給num,使得result數組中的每個函數都有自己的num變量副本

五、關於this

1.this對象

this對象是在運行時基於函數的執行環境綁定的,在全局函數中this等於window,而當函數作爲某個對象的方法調用時,this等於那個對象。不過,匿名函數的執行環境具有全局性,因此其this對象通常指向window

var name = "The Window";
var object = {
    name: "The Object",
    getNameFunc: function(){
        return function(){
            return this.name;
        }
    }
}
console.log(object.getNameFunc()()); //The Window
var name = "The Window";
var object = {
    name: "The Object",
    getNameFunc: function(){
        var that = this;
        return function(){
            return that.name;
        }
    }
}
console.log(object.getNameFunc()()); //The Object

2.this綁定規則

默認綁定

最常用的函數調用類型:獨立函數調用

function foo(){
    console.log(this.a);
}
var a = 2;
foo(); //2

上述代碼中,this.a被解析成了全局變量a,這是因爲函數調用時應用了this的默認綁定,因此this指向全局對象。雖然this的綁定規則完全取決於調用位置,但是隻有foo()運行在非嚴格模式下,默認綁定才能綁定到全局對象;嚴格模式下與foo()調用位置無關,this會綁定到undefined

隱式綁定

function foo(){
    console.log(this.a);
}
var obj = {
    a:2,
    foo:foo
};
obj.foo(); //2

當foo函數被調用時,調用位置會使用obj上下文來引用函數,當函數引用有上下文對象時,隱式綁定規則會把函數調用中的this綁定到這個上下文對象,因此this.a和obj.a是一樣的

隱式丟失

一個常見的this綁定問題就是隱式綁定的函數會丟失綁定對象,從而應用默認綁定規則
 

function foo(){
    console.log(this.a);
}
var obj = {
    a:2,
    foo:foo
};
var bar = obj.foo; //函數別名
var a = "global";
bar(); //"global"

雖然bar是obj.foo的一個引用,但是實際上它引用的是foo函數本身,因此此時的bar()其實是一個不帶任何修飾的函數調用,因此應用了默認綁定

顯式綁定

使用call,apply以及bind方法進行顯示綁定
 

function foo(){
    console.log(this.a);
}
var obj = {
    a: 2
}
foo.call(obj); //2

new綁定

使用new來調用函數被稱爲構造函數調用,這種函數調用會自動執行如下操作:

  •  創建一個全新的對象
  •  這個新對象會被進行原型連接
  •  這個新對象會綁定到函數調用的this
  •  如果函數沒有返回其他對象,那麼new表達式中的函數調用會自動返回這個新對象
function foo(a){
    this.a = a;
}
var bar = new foo(2);
console.log(bar.a); //2

3.判斷this

  • 函數是否在new中調用(new綁定)?如果是的話,this綁定的是新創建的對象。var bar = new foo();
  • 函數是否通過call,apply,bind(顯示綁定)調用?如果是的話,this綁定的是指定的對象。var bar = foo.call(obj);
  • 函數是否在某個上下文對象中調用(隱式綁定)?如果是的話,this綁定的是那個上下文對象。var bar = obj.foo();
  • 如果都不是的話使用默認綁定。如果在嚴格模式下,就綁定到undefined,否則綁定到全局對象。var bar = foo();
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章