學習記錄--《深入理解ES6》之函式(上)

chenjk4發表於2020-12-16

帶引數預設值的函式

ES6中的引數預設值

ES6能更容易地為引數提供預設值,它使用了初始化形式,以便在引數未被正式傳遞進來時使用。

function makeRequest(url, timeout =2000, callback = funtcion(){}){
		// 函式的剩餘部分
}

此函式只要求第一個引數始終要被傳遞。其餘兩個引數則都有預設值,這使得函式體更為小巧,因為不需要再新增更多程式碼來檢查缺失的引數值。

如果使用全部三個引數來呼叫makeRequest(), 那麼預設值將不會被使用

// 使用預設的timeout 與 callback
makeRequest("/foo");

//使用預設的callback
makeRequest("/foo",500);

//不使用預設值
makeRequest("/foo",500,function(body){
		dosomething(body);
})

在函式宣告中能指定任意一個引數的預設值,即使該引數排在未指定預設值的引數之前也是可以的。

function makeRequest(url, timeout = 2000, callback){
		//函式內容
}
// 使用預設的timeout
makeRequest("/foo",undefined, function(body){
		doSomething(body);
});

// 使用預設的timeout
makeRequest("/foo");

//不使用預設值
makeRequest("/foo", null, function(body){
		doSomething(body);
})

在關於引數預設值的這個例子中,null值被認為是有效的,意味著對於makeRequest()的第三次呼叫並不會使用timeout的預設值。

引數預設值如何影響arguments物件

需要記住的是,arguments 物件會在使用引數預設值時有不同的表現。在ES5的非嚴格模式下,arguments物件會反映出具名引數的變化。

function mixArgs(first, second){
		console.log(first === arguments[0]);
		console.log(second === arguments[1]);
		first = "c";
		second = "d";
		console.log(first === arguments[0]);
		console.log(second === arguments[1]);		
}
mixArgs("a","b");

輸出:

true
true
true
true

在非嚴格模式下, arguments 物件總是會被更新以反映出具名引數的變化。因此當 first與 second 變數被賦予新值時, arguments[0] 與 arguments[1] 也就相應地更新了,使得這裡所有的 === 比較的結果都為 true 。

然而在使用ES6引數預設值的函式中,arguments物件的表現總是會與ES5的嚴格模式一致,無論此時函式是否明確執行在嚴格模式下。引數預設值的存在觸發了arguments物件與具名引數的分離。這是個細微但重要的細節,因為arguments物件的使用方式發生了變化。

// 非嚴格模式
function mixArgs(first, second = "b"){
		console.log(arguments.length);
		console.log(first === arguments[0]);
		console.log(second === argument[1]);
		first = "c";
		second = "d";
		console.log(first === arguments[0]);
		console.log(second === arguments[1]);
}
mixArgs("a");

輸出

1
true
false
false
false

本例中arguments.length的值為1,因為只給 mixArgs() 傳遞了一個引數。這也意味著 arguments[1] 的值是undefined,符合將單個引數傳遞給函式時的預期;這同時意味著first與arguments[0]是相等的。改變first 和 second 的值不會對 arguments 物件造
成影響,無論是否在嚴格模式下,所以你可以始終依據 arguments 物件來反映初始呼叫狀態。

引數預設值的暫時性死區

前面介紹了let與const的暫時性死區,而引數預設值同樣有著無法訪問特定引數的暫時性死區。與let宣告相似,函式每個引數都會建立一個新的識別符號繫結,它在初始化之前不允許被訪問,否則會丟擲錯誤。引數初始化會在函式被呼叫時進行,無論是給引數傳遞一個值、還是使用了引數的預設值。

function ad(first = second, second){
	return first + second;
}
console.log(add(1,1)); //2
console.log(add(undefined,1));  //丟擲錯誤

本例中呼叫add(1,1)與add(undefined,1)對應著以下的後臺程式碼:

//JS呼叫add(1,1) 可表示為
let first = 1;
let second = 1;

//JS呼叫add(1)可表示為
let first  = second;
let second = 1;

本例中呼叫add(undefined,1)丟擲了錯誤,是因為在first被初始化時second尚未被初始化。此處的second 存在於暫時性死區內,對於second的引用就丟擲了錯誤。體現出let的繫結行為

函式引數擁有各自的作用域和暫時性死區,與函式體的作用域相分離,這意味著引數的預設值不允許訪問在函式體內部宣告的任意變數。

剩餘引數

剩餘引數(rest parameter)由三個點(…)與一個緊跟著的具名引數指定,它會是包含傳遞給函式的其餘引數的一個陣列,名稱中的“剩餘”也由此而來。

function pick(object,...keys){
	let result = object.create(null);
	for(let i = 0, len = keys.length; i < len; i++){
		result[keys[i]] = object[keys[i]];
	}
	return result;
}

剩餘引數的限制條件
剩餘引數受到兩點限制。
第一個限制是函式只能有一個剩餘引數,並且它必須被放在最後。例如,如下程式碼是無法工作的

// 語法錯誤:不能在剩餘引數後使用具名引數
function pick(object, ...keys, last){
	let result = object.create(null);
	for(let i = 0, len = keys.length; i < len; i++){
		result[keys[i]] = object[keys[i]];
	}
	return result;
}

第二個限制 剩餘引數不能再物件字面量的setter屬性中使用,這意味著如下程式碼導致語法錯誤

let object = {
	//語法錯誤:不能在setter中使用剩餘引數
	set name(...value)
}

存在此限制的原因是:物件字面量的setter被限定只能使用單個引數;而剩餘引數按照定義是不限制引數數量的,因此它在此處不被許可。

剩餘引數如何影響arguments物件

設計剩餘引數是為了替代ES中的arguments。原先在ES4中就移除了arguments並新增了剩餘引數,以便允許向函式傳入不限數量的引數。儘管ES4從未被實施,但這個想法被保持下來並在ES6中重新引入,雖然arguments仍未在語言中被移除。

function checkArgs(...args){
		console.log(args.length);
		console.log(arguments.length);
		console.log(args[0], arguments[0]);
		console.log(args[1], arguments[1]);
}

checkArgs("a" , "b");

函式構造器的增強能力

Function 構造器允許你動態建立一個新函式,但在JS中並不常用。傳給該構造器的引數都是字串,它們就是目標函式的引數與函式體。

var add = new Function("first", "“second”, "return first + second" ;
console.log(add(1 , 1));
)

ES6增強了Function構造器的能力,允許使用預設引數以及剩餘引數。對於預設引數來說,你只需為引數名稱新增等於符號以及預設值。如下例:

var add = new Function("first" , "second = first", "return first + second");

console.log(add(1,1));  //2
console.log(add(1));  //2

//剩餘引數
var pickFirst = new Function("...args","return args[0]");
console.log(pickFirst(1,2));

擴充套件運算子

與剩餘引數關聯最密切的就是擴充套件運算子。
剩餘引數允許你把多個獨立的引數合併到一個陣列中
擴充套件運算子允許將一個陣列分割,並將各個項作為分離的引數傳給函式。

let values = [25, 50, 75, 100];

// 等價於 console.log(Math.max(25, 50, 75, 100));
console.log(Math.max(...values));               //100

//可以將擴充套件運算子與其他引數混用。
let values = [-25, -50, -75 ,-100];
console.log(Math.max(...values, 0)); //0

本例中傳給Math.max() 的最後一個引數是0,它跟在使用擴充套件運算子的其他引數之後傳入。
用擴充套件運算子傳遞引數,使得更容易將陣列作為函式引數來使用,你會發現在大部分場景中擴充套件運算子都是apply() 方法的合適替代品。

相關文章