天天看點

JS學習筆記(九)函數JS學習筆記(九)函數

JS學習筆記(九)函數

文章目錄

  • JS學習筆記(九)函數
    • 一、函數的定義
      • 1.1 函數聲明與函數表達式
    • 二、箭頭函數
    • 三、參數
    • 四、沒有重載
    • 五、預設參數值
      • 5.1 預設參數作用域與暫時性死區
    • 六、參數擴充與收集
      • 6.1 擴充參數
      • 6.2 收集參數
    • 七、函數作為值
    • 八、函數内部
      • 8.1 arguments
      • 8.2 this(重點)
        • 标準函數
        • 箭頭函數
      • 8.3 new.target
    • 九、函數屬性和方法
    • 十、閉包
      • 10.1 this對象
    • 十一、立即調用的函數表達式

一、函數的定義

  • 利用function關鍵字的函數聲明
function sum(num1,num2) {
    return num1+num2;
}
           
  • 函數表達式(匿名函數)
let sum = function(num1,num2) {
    return num1+num2;
}
           
  • 箭頭函數
let sum = (num1,num2) => {
    return num1+num2;
}
           

1.1 函數聲明與函數表達式

       JS引擎在任何代碼執行之前,會先讀取函數聲明,并在執行上下文中生成函數定義。而函數表達式必須等到代碼執行到他那一行時,才會在執行上下文生成函數定義。

       函數聲明會在任何代碼執行之前先被讀取并添加到執行上下文。稱作“函數聲明提升”

// ok
console.log(sum(10,10));
function sum(num1,num2) {
    return num1+num2;
}
           
// 會出錯
console.log(sum(10,10));
let sum = function(num1,num2) {
    return num1+num2;
}
           

并不是因為用了let,使用var也會碰到同樣的問題

二、箭頭函數

  • 箭頭函數的簡潔文法時候嵌入函數的場景,若隻有一個參數,可以不用括号
  • 箭頭函數不能使用arguments、super 和 new.target,也不能用作構造函數
  • 箭頭函數沒有prototype屬性

雖然箭頭函數沒有arguments對象,當可以在包裝函數中把它提供給箭頭函數

function foo() {
    let bar = () => {
        console.log(arguments[0]); //5
    }
    bar();
}

foo(5);
           

三、參數

JS學習筆記(九)函數JS學習筆記(九)函數

       在使用function關鍵字定義(非箭頭)函數時,可以在函數内部通路arguments對象,從中取得傳進來的每個參數值,且他的值始終會與對應的命名參數同步

function howManyArgs() {
    console.log(arguments.length);
}

howManyArgs("string",45); //2 
howManyArgs(); //0
howManyArgs(12); //1
           

四、沒有重載

ES函數不能重載,因為ES函數沒有簽名(接收參數的類型和數量),因為參數是由包含零個或多個值的數組表示的。若定義了兩個同名函數,後定義的會覆寫先定義的

五、預設參數值

       在使用預設參數時,**arguments對象的值不反應參數的預設值,隻反映傳給函數的參數。**修改命名參數也不影響arguments對象,他始終以調用函數時傳入的值為準

function foo(name='niki') {
    name = 'Louis';
    return `King ${arguments[0]}`
}

console.log(foo()); //undefined
console.log(foo('Elvira')); //King Elvira
           

5.1 預設參數作用域與暫時性死區

       給多個參數定義預設值實際跟使用let順序聲明變量一樣,因為參數是按順序初始化的,所有後定義預設值的參數可以引用先定義的參數,也遵循“暫時性死區”規則,即前面定義的參數不能引用後面定義的

function foo(name='niki',numerals = name) {
    return `King ${name} ${numerals}`
}

console.log(foo()); //King niki niki
           

六、參數擴充與收集

       擴充操作符既可以用于調用函數時傳參,也可以用于定義函數

6.1 擴充參數

let values = [1, 2, 3, 4];

function getSum() {
    let sum = 0;
    for (let i = 0; i < arguments.length; ++i) {
        sum += arguments[i];
    }
    return sum;
}

console.log(getSum(-1, ...values, 5)); //14
console.log(getSum(...values, ...[5, 6, 7])); //28
           

       因為數組長度已知,是以在使用擴充操作符傳參時,并不妨礙在前面或後面再傳其他值,包括使用擴充操作符傳其他參數

6.2 收集參數

在函數定義時,可以使用擴充操作符把不同長度的獨立參數組合為一個數組。收集參數的結果會得到一個Array執行個體

function getSum(...values) {
    return values.reduce((x,y) => x+y,0);
}

console.log(getSum(1,2,3));
           

因為收集參數的結果可變,是以隻能把它當作最後一個參數

//不可以
function(...values,lastvalue) {
    ...
}

//可以
function(firstvalue,...values) {
    ...
}
           

七、函數作為值

因為函數名在ES中就是變量,是以函數可以用在任何可以使用變量的地方。不僅可以把函數作為參數傳給另一個函數,還可以在另一個函數中傳回另一個函數

八、函數内部

8.1 arguments

       arguments是一個類數組對象,包含調用函數時傳入的所有參數。arguments含有callee屬性,他是一個指向arguments對象所在函數的指針

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

      這個階乘呢個函數要正确執行必須保證函數名時factorial,進而導緻緊密耦合。使用arguments.callee就可以讓函數邏輯與函數名解耦。

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

此時無論函數叫什麼名稱,都可以引用正确的函數

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

let trueFactorial = factorial;
factorial = function () {
    return 0;
}
console.log(trueFactorial(5)); //120
console.log(factorial(5)); //0
           

8.2 this(重點)

this 在标準函數和箭頭函數中有不同的行為

标準函數

标準函數中,this引用的是把函數當成方法調用的上下文對象,在網頁的全局上下文中調用函數時,this指向windows

window.color = 'red';
let o ={
    color:'blue'
};

function sayColor() {
    console.log(this.color);
}

sayColor(); //'red'
o.sayColor = sayColor;
o.sayColor(); //'blue'
           

箭頭函數

箭頭函數中,this引用的是定義箭頭函數的上下文。

window.color = 'red';
let o ={
    color:'blue'
};

let sayColor = () => console.log(this.color);

sayColor(); //'red'
o.sayColor = sayColor;
o.sayColor(); //'red'
           

       在事件回調或定時回調中調用某個函數時,this的指向可能并非想象的對象,此時将回調函數寫成箭頭函數就可以解決問題,這是因為箭頭函數中的this會保留定義該函數時的上下文:

function King() {
    this.royaltyName = 'Henry';
    //this 引用 King 執行個體
    setTimeout(() => console.log(this.royaltyName),1000);
}

function Queen() {
    this.royaltyName = 'Niki';
    // this 引用window對象
    setTimeout(function() { console.log(this.royaltyName);},1000);
}
new King(); //Henry
new QUeen();//undefined
           

8.3 new.target

ES6 新增了檢測函數是否使用new關鍵字調用的new.target屬性。若正常調用,new.target 為undefined;若使用new關鍵字,則new.target将引用被調用的構造函數

九、函數屬性和方法

       每個函數都有兩個屬性:length 和 prototype

       prototype時儲存引用類型所有執行個體方法的地方,意味着toString()、valueOf()等方法實際上都儲存在prototype上。proyotype 屬性不可枚舉,用for-in循環不會傳回這個屬性

       函數都有兩個方法:apply() 和 call(),這兩個方法都會以指定的this值來調用函數,即會設定調用函數時函數體内this對象的值。

       apply()方法接收兩個參數:函數内this的值和一個參數數組。第二個參數可以是Array的執行個體,但也可以是arguments對象

       call()第一個參數與apply()一樣,而剩下的要傳給被調用的函數的參數則是逐個傳遞的。換句話說,通過call()向函數傳參時,必須将參數一個一個的列出來。

function sum (num1,num2) {
    return num1 +num2;
}

function callSum(num1,num2) {
    return sum.call(this,num1,num2);
}

function callSum2 (num1,num2) {
    return sum.apply(this,arguments);
}
           

       apply 和 call 真正強大的地方是控制函數調用上下文即函數體内this的能力

window.color = 'red';
let o ={
    color:'blue'
};

function sayColor() { //全局函數
    console.log(this.color);
}

sayColor(); //'red'
sayColor.call(this);  //red
sayColor.call(window);//red
sayColor.call(o);	//blue
           

十、閉包

閉包是指那些引用了另一個函數作用域中變量的函數,通常是在嵌套函數中實作的

function createComparisonFunc(propertyName) {
    return function (obj1, obj2) {
        let val1 = obj1[propertyName];
        let val2 = obj2[propertyName];

        if (val1 < val2) {
            return -1;
        } else if (val1 > val2) {
            return 1;
        } else {
            return 0;
        }
    };
}
           

       内部的匿名函數引用了外部函數的變量propertyName,在這個内部函數傳回并在其他地方被使用後,仍然引用着那個變量。

       在調用一個函數時,會為這個函數建立一個執行上下文,并建立一個作用域鍊。然後用arguments和其他命名參數來初始化這個函數的活動對象。外部函數的活動對象是内部函數的作用域鍊上的第二個對象

       函數執行時,每個執行上下文中都會由一個包含變量的對象。全局上下文中的角變量對象,他在代碼執行期間始終存在,而函數局部上下文中的叫活動對象。隻在函數執行期間存在

function compare(val1,val2) {
    if (val1 < val2) {
            return -1;
        } else if (val1 > val2) {
            return 1;
        } else {
            return 0;
        }
	}
}
           
JS學習筆記(九)函數JS學習筆記(九)函數
let compare = createComparisonFunc('name');
let result = copmare({name:'niki'},{name:'jhon'});
           
JS學習筆記(九)函數JS學習筆記(九)函數

       在函數内部定義的函數會把其包含函數的活動對象添加到自己的作用域鍊中。是以,在createComparisonFunc()中,匿名函數的作用域鍊中實際上包含createComparisonFunc()的活動對象.createComparisonFunc()的活動對象并不能在他執行後銷毀,因為匿名函數的作用域鍊中仍然有對他的引用。在createComparisonFunc()執行完畢後,其執行上下文的作用域鍊會被銷毀,但他的活動對象仍然會保留在記憶體中,直到匿名函數被銷毀後才會被銷毀。

//建立比較函數
let compare = createComparisonFunc('name');
//調用函數
let result = copmare({name:'niki'},{name:'jhon'});
//接觸對函數的引用,就可以釋放記憶體了
compare = null;
           

10.1 this對象

若内部函數沒有使用箭頭函數定義,則this對象會在運作時綁定到執行函數的上下文。

  • 若在全局函數中調用,則this在非嚴格模式下等于window,嚴格模式下等于undefined
  • 若作為某個對象的方法調用,則this等于這個對象。
  • 若是匿名函數,其不會綁定到某個對象,this指向window
window.id = 'The window';
let obj = {
    id: 'My obj',
    getId() {
        return function () {
            return this.id;
        }
    }
}
console.log(obj.getId()()); //'The window'
           

       每個函數在調用時都會自動建立兩個變量:this和argument。内部函數永遠不可能直接通路外部函數的這兩個變量。但若吧this儲存到閉包可以通路的另一個變量裡是可以的。

window.id = 'The window';
let obj = {
    id: 'My obj',
    getId() {
        let that = this;
        return function () {
            return that.id;
        }
    }
}
console.log(obj.getId()()); //'My obj'
           

       在定義匿名函數之前,先把外部函數的this儲存到變量that中

十一、立即調用的函數表達式

       立即調用的匿名函數又稱作立即調用的函數表達式(IIFE:Immediately invoked function expression),類似于函數聲明,被包含在括号中,是以會被解釋為函數表達式,緊跟在第一組括号後面的第二組括号會立即調用前面的函數表達式。

(function() {
    //塊級作用域
})
           

繼續閱讀