Web前端入門第 65 問:JavaScript 函數參數各種使用方式
函數參數是什么?
就是函數內部無法確定的一個東西,需要外部傳給函數內部的玩意兒,語法上就是寫在函數括號中的東東。比如:
function test(a) {}
其中的 a 就是 test 函數的參數,在函數體內部,a 作為一個變量存在,可以修改它。
JS 的函數參數,真的是可以傳入任意值,沒有任何限制,可以包括 原始類型、對象,數組,函數 等等,只要是 JS 語言支持的,都可以當做參數傳入。
原始類型
JS 原始類型參數(number、string、boolean、null、undefined、symbol、bigint)按值傳遞,傳入的是值的副本,在函數里面修改傳入的值不會影響外部變量。
function test(a) {
a = '前端路引'; // 修改原始類型不會影響 arg 變量值
}
let arg = '微信公眾號';
test(arg);
console.log(arg); // 輸出 '微信公眾號'(原值未改變)
引用類型
JS 引用類型參數(對象、數組、函數)按引用地址傳遞,如果函數里面修改了對象屬性,會影響外部變量,使用時需特別注意!!
function test (obj) {
obj.name = '前端路引' // 修改了對象屬性,會影響共享的對象
obj = { // 如果直接給 obj 參數賦值,不會影響共享的對象,因為 obj 已經變成了一個新的對象
test: '測試參數'
}
}
const weChat = {
type: '微信公眾號',
age: 1,
};
test(weChat);
console.log(weChat) // {type: '微信公眾號', age: 1, name: '前端路引'}
默認參數
ES6 版本為 JS 注入了一大堆活性,各種花活不斷,默認參數就是其中一個最常用的花活。當未傳入參數或者傳入的參數是 undefined,則使用默認參數。
function test(a = '前端路引') {
return a; // 將 a 變量值返回出去
}
let arg = '微信公眾號';
console.log(test(arg)); // 輸出 '微信公眾號'
// 沒傳入參數,使用默認值
console.log(test()); // 輸出 '前端路引'
// 傳入 undefined 也是用默認值
console.log(test(undefined)); // 輸出 '前端路引'
剩余參數
ES6 的又一花活之一,允許使用 ... 語法,將多余的參數合并為數組,在箭頭函數中可以代替 arguments 對象。
function sum(...numbers) {
return numbers.reduce((acc, num) => acc + num, 0);
}
sum(1, 2, 3); // 返回 6
function test(a, ...rest) {
console.log(a); // 獲得傳入的第一個參數,輸出 '公眾號'
console.log(rest); // 多余參數轉為數組,輸出 ['前端路引', '函數測試']
}
test('公眾號', '前端路引', '函數測試');
需特別注意,剩余參數只能放在最后,否則報語法錯誤 SyntaxError: Rest parameter must be last formal parameter。
比如:
function test(...rest, a) { // 報錯 SyntaxError: Rest parameter must be last formal parameter
}
解構賦值傳參
還是 ES6 的花活之一,用于解構對象或數組參數。
/**
* 對象解構
* name 為解構參數對象中的 name 屬性
* rest 為解構參數對象中剩余的屬性,也是對象
*/
function test1({ name, ...rest }) {
console.log(name); // 輸出 '前端路引'
// rest 獲得剩余為分配的對象屬性
console.log(rest); // 輸出 {age: 1}
}
test1({ name: '前端路引', age: 1 });
/**
* 數組解構
* first 為第一個數組值
* rest 為數組剩余值,也是一個數組
*/
function test2([first, ...rest]) {
console.log(first); // 輸出 '前端路引'
console.log(rest); // 輸出 [1, '微信公眾號']
}
test2(['前端路引', 1, '微信公眾號']);
參數使用使用解構賦值時,需特別注意,如果參數沒傳入參數,那么解構將會報錯:
// 報錯 TypeError: Cannot destructure property 'name' of 'undefined' as it is undefined.
function test({ name }) {
}
test();
原因是未傳入參數時,默認便是 undefined,對 undefined 解構便會報錯!!這時候可以使用函數默認參數進行解決:
// 表示未傳入參數時使用空對象進行解構
function test({ name } = {}) {}
test();
解構中也可以使用默認值:
// 對象解構默認值
function test1({ name = '前端路引' } = {}) {
console.log(name);
}
test1(); // 輸出 '前端路引'
test1({age: 1}); // 輸出 '前端路引'
// 需注意另一種寫法
function test2({ name } = { name: '前端路引' }) {
console.log(name);
}
test2(); // 輸出 '前端路引'
test2({age: 1}); // 輸出 undefined
// 數組解構默認值
function test3([first = '前端路引', ...rest] = []) {
console.log(first);
}
test3();
test2 中使用了一個默認對象,這個對象中有 name 屬性,如果傳入參數不存在時候,將會獲得 name 屬性值,但如果傳入參數存在時,并且傳入的對象中沒有 name 屬性,那么就只能是 undefined。
arguments 對象
使用 function 聲明的函數,可以使用 arguments 所有參數。需注意 arguments 在箭頭函數中不可用。
arguments 是類數組對象,不是所有數組方法都可以使用,可以使用 ... 展開運算符轉換為數組。
function test() {
// arguments.push('微信公眾號'); // 報錯 TypeError: arguments.push is not a function
const temp = [...arguments]; // 使用展開運算符轉換為數組
temp.push('微信公眾號'); // 轉為數組之后可以使用 push 方法
console.log(temp);
}
test(1, '前端路引', true); // 輸出 [1, '前端路引', true, '微信公眾號']
函數作為參數
雖然 回調函數 參數這種方式已經被 Promise 代替,但如果要實現各種鉤子函數,還是只能使用 function 作為參數傳遞。
使用函數作為參數的函數有一個專用名詞叫做 高階函數。多用于 事件處理、異步操作 等等。
function test1(url, callback) {
// 模擬異步操作
setTimeout(() => {
callback({ data: '前端路引' });
}, 1000);
}
test1('/api', (response) => {
console.log(response.data); // 輸出 '前端路引'
});
// 使用 Promise 改寫 test1 函數:
function test2(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ data: '前端路引' });
}, 1000)
})
}
test2('/api').then((response) => {
console.log(response.data); // 輸出 '前端路引'
})
當需要鉤子函數時候,便無法使用 Promise 替換了,比如:
function test({
url,
before, // 鉤子函數,在請求開始時調用
callback,
} = {}) {
before && before('請求開始');
setTimeout(() => {
// 執行完之后回調
callback && callback({ data: '前端路引' });
}, 1000)
}
test({
url: '/api',
before(msg) {
console.log(msg);
},
callback(response) {
console.log(response.data);
}
})
函數柯里化
通過閉包返回函數,分步傳遞參數,這種方式稱為 函數柯里化。
function test(a) {
return (b) => a * b; // 函數返回值是一個函數,用于二次調用
}
const double = test(2); // 第一次傳入參數,獲得一個返回函數
console.log(double(5)); // 第二次傳入參數,獲得結果,輸出 10
由于閉包中的變量一直在內存中,所以在使用時候需注意內存泄漏問題!!
bind 方法綁定參數
bind 這方法不僅可以綁定內部 this 指針,還能用于固定部分參數,生成新函數。
function test1(type, name) {
console.log(this);
return `${type}:${name}`;
}
const test2 = test1.bind({age: 1}, '微信公眾號');
console.log(test2('前端路引')); // 輸出 '微信公眾號:前端路引'
test1.bind({age: 1}, '微信公眾號') 作用是給 test1 綁定 this 指向 {age: 1} 對象,同時固定了第一個參數為 '微信公眾號',返回一個新的函數,此函數只有剩下的 name 參數。
也可以使用 test1.bind(null, '微信公眾號') 不綁定 this 指針,僅固定第一個參數。
隱式參數類型轉換
由于 JS 的參數靈活性,在使用時,需特別注意類型轉換問題。比如字符串 '5' 轉為數字 5:
function test(a, b) {
return a + b;
}
test('3', 5); // 返回 '35'(字符串拼接)
test(3, '5'); // 返回 '35'
test(3, 5); // 返回 8
如果無法確定傳入的參數類型,那么就有必要顯式轉換類型(如 Number(param)):
function test(a, b) {
return Number(a) + Number(b);
}
test('3', 5); // 返回 8
test(3, '5'); // 返回 8
test(3, 5); // 返回 8
或進行參數類型校驗,當輸入不合法時候,拋出異常:
function test(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new Error('參數類型錯誤');
}
return a + b;
}
test('3', 5); // 拋出異常
寫在最后
理解各種函數傳參方式,靈活運用可以在編程中玩出花來。各種優雅的設計模式、易于維護的高級代碼,都離不開函數的使用技巧~~
在使用函數參數時,也需特別注意參數合法性校驗,尤其是提供給外部調用的函數,必須做參數類型校驗,避免程序出現參數類型錯誤。

浙公網安備 33010602011771號