JavaScript ES5 函數從基礎到高級應用全解析
函數是 JavaScript(ES5)的核心語法單元,不僅承擔代碼復用功能,更是實現作用域隔離、異步編程和模塊化的基礎。
一、函數核心概念與專有名詞解析
| 專有名詞 | 定義與說明 |
|---|---|
| 函數聲明(Function Declaration) | 通過 function 關鍵字顯式定義函數名的方式,支持 “函數提升”,可在聲明前調用。 |
| 函數表達式(Function Expression) | 將匿名 / 命名函數賦值給變量的方式,不支持函數提升,需賦值后調用。 |
| arguments 對象 | 函數內部的類數組對象(具有 length 屬性但無數組方法),存儲所有傳入的實參。 |
| this 綁定 | 函數執(zhí)行時的 “上下文對象”,指向由調用方式決定(非定義位置),ES5 無箭頭函數綁定規(guī)則。 |
| IIFE(立即執(zhí)行函數) | 全稱 Immediately Invoked Function Expression,聲明后立即執(zhí)行,用于創(chuàng)建獨立作用域。 |
| 作用域鏈(Scope Chain) | 函數嵌套時形成的變量查找鏈,從當前函數作用域向上遍歷至全局作用域。 |
| 閉包(Closure) | 有權訪問另一個函數作用域中變量的函數,本質是作用域鏈的 “延長保留”。 |
| 回調函數(Callback) | 作為參數傳遞給另一個函數的函數,在特定時機(如異步操作完成)被調用。 |
二、函數定義與聲明:兩種方式的細節(jié)對比
函數有 “函數聲明” 和 “函數表達式” 兩種定義方式,二者在提升特性、作用域表現上存在關鍵差異,需結合實例理解。
2.1 函數聲明(支持函數提升)
語法與實例
// 函數聲明:function 關鍵字 + 函數名 + 參數列表 + 函數體
function calculateSum(a, b) {
// 函數體:實現求和邏輯
return a + b;
}
// 關鍵特性:函數提升(聲明前調用仍可執(zhí)行)
console.log(calculateSum(3, 5)); // 輸出:8(正常執(zhí)行,無報錯)專有名詞補充:函數提升(Function Hoisting)
ES5 解析代碼時,會將函數聲明的 “函數名 + 函數體” 整體提升至當前作用域頂部,因此可在聲明前調用。但需注意:僅函數聲明支持提升,函數表達式不支持。
2.2 函數表達式(不支持函數提升)
匿名函數表達式即 “無函數名的函數”,賦值后通過變量名調用,是回調函數、一次性邏輯封裝的常用形式。
// 1. 聲明變量 fn,賦值為一個匿名函數(函數表達式的核心語法)
// 右側 function(){} 是匿名函數,無函數名,作為值賦值給 fn
var fn = function(a, b) {
// 函數內部邏輯:計算兩數之和
// 內部可訪問參數 a、b,以及外部作用域的變量(后續(xù)詳解)
var sum = a + b;
console.log("匿名函數內部:a + b =", sum); // 輸出計算結果
return sum; // 返回結果給外部調用者
};
// 2. 調用函數表達式:通過變量名 fn 調用,傳入實參
// 外部通過變量名觸發(fā)函數執(zhí)行,獲取返回值
var result = fn(3, 5);
console.log("外部訪問函數返回值:", result); // 輸出:外部訪問函數返回值:8
// 關鍵注意:匿名函數本身無名字,無法在內部通過函數名遞歸(需用 arguments.callee)
var factorial = function(n) {
if (n === 1) return 1;
// 匿名函數內部無法通過“函數名”遞歸,需用 arguments.callee 指向當前函數
return n * arguments.callee(n - 1);
};
console.log("匿名函數遞歸(階乘):", factorial(5)); // 輸出:120命名函數表達式(帶函數名)
命名函數表達式是 “給函數本身指定名字的表達式”,函數名僅在函數內部可用,外部仍通過變量名調用,適用于需要在函數內部遞歸、調試(函數名會顯示在調用棧中)的場景。
// 1. 聲明變量 calculate,賦值為一個“帶名字的函數”(函數名:multiply)
// 右側 function multiply(a, b){} 是命名函數,函數名 multiply 僅內部可用
var calculate = function multiply(a, b) {
// 函數內部可通過函數名 multiply 訪問自身(用于遞歸)
console.log("命名函數內部:函數名 =", multiply.name); // 輸出:命名函數內部:函數名 = multiply
return a * b;
};
// 2. 外部調用:只能通過變量名 calculate 調用,無法直接用 multiply
console.log("外部調用命名函數表達式:", calculate(4, 6)); // 輸出:24
// console.log(multiply(4, 6)); // 報錯:multiply is not defined(外部無法訪問函數內部的名字)
// 3. 命名函數表達式的核心優(yōu)勢:內部遞歸更清晰(無需依賴 arguments.callee)
var fibonacci = function fib(n) {
if (n <= 2) return 1;
// 內部通過函數名 fib 遞歸,語義更清晰,避免 arguments.callee 的歧義
return fib(n - 1) + fib(n - 2);
};
console.log("命名函數遞歸(斐波那契):", fibonacci(6)); // 輸出:8(1,1,2,3,5,8)函數表達式的作用域訪問規(guī)則:內部與外部能否互相訪問?
函數表達式的 “內外訪問權限” 由 ES5 作用域規(guī)則決定:ES5 僅支持 “全局作用域” 和 “函數作用域”,無塊級作用域,訪問權限遵循 “內層可訪問外層,外層不可訪問內層” 的原則。
核心規(guī)則總結
| 訪問方向 | 是否允許 | 底層邏輯 |
|---|---|---|
| 函數內部訪問外部變量 | 允許 | 作用域鏈規(guī)則:內部作用域會向上遍歷外層作用域,直到找到變量或到達全局作用域。 |
| 函數外部訪問內部變量 | 不允許 | 函數作用域隔離:內部變量僅在函數內部生效,外部無訪問路徑。 |
語法與實例(匿名 / 命名兩種形式)
// 1. 匿名函數表達式:無函數名,賦值給變量
var calculateMultiply = function(a, b) {
return a * b;
};
// 2. 命名函數表達式:函數名僅在內部可用(用于遞歸或調試)
var calculateDivide = function divide(a, b) {
console.log(divide.name); // 輸出:divide(內部訪問函數名)
if (b === 0) return "除數不能為0";
return a / b;
};
// 關鍵特性:無函數提升(聲明前調用會報錯)
// console.log(calculateMultiply(4, 6)); // 報錯:calculateMultiply is not a function(若放在賦值前)
console.log(calculateMultiply(4, 6)); // 輸出:24(賦值后調用正常)
console.log(calculateDivide(10, 2)); // 輸出:5兩種定義方式的核心差異
| 對比維度 | 函數聲明 | 函數表達式 |
|---|---|---|
| 提升支持 | 支持(整體提升至作用域頂部) | 不支持(變量提升但值為 undefined) |
| 函數名 | 必須顯式定義 | 可選(匿名 / 命名,命名僅內部可用) |
| 適用場景 | 獨立函數(如工具函數) | 賦值給變量、作為參數(如回調函數) |
三、函數內部核心屬性:arguments 與 this
函數內部有兩個特殊屬性:arguments(處理參數)和 this(處理上下文),二者是實現靈活邏輯的關鍵。
3.1 arguments 對象:類數組的參數集合
定義與特性
arguments 是函數內部的類數組對象,存儲所有傳入的實參,支持:
- 通過索引訪問參數(如
arguments[0]獲取第一個實參); length屬性獲取實參個數;callee屬性指向當前函數(用于匿名函數遞歸)。
實踐實例
// 實例1:處理不確定個數的參數(求和)
function sum() {
var total = 0;
// 遍歷 arguments(類數組,需用 for 循環(huán),無 forEach 方法)
for (var i = 0; i < arguments.length; i++) {
// 過濾非數字參數(增強魯棒性)
if (typeof arguments[i] === "number") {
total += arguments[i];
}
}
return total;
}
console.log(sum(1, 2, 3)); // 輸出:6
console.log(sum(10, 20, "a", 30)); // 輸出:60(忽略非數字參數)
// 實例2:arguments.callee 實現匿名函數遞歸(計算階乘)
var factorial = function(n) {
if (n === 1) return 1;
// callee 指向當前函數,避免依賴外部變量名
return n * arguments.callee(n - 1);
};
console.log(factorial(5)); // 輸出:120(5! = 5*4*3*2*1)3.2 this 綁定:動態(tài)上下文對象
定義與核心規(guī)則
this 是函數執(zhí)行時的 “上下文對象”,其指向由調用方式決定,與函數定義位置無關,ES5 中常見綁定場景如下:
實踐實例(4 種核心綁定場景)
// 場景1:全局調用 -> this 指向全局對象(瀏覽器:window,Node:global)
function getGlobalContext() {
console.log(this === window); // 瀏覽器環(huán)境輸出:true
console.log(this === global); // Node 環(huán)境輸出:true
}
getGlobalContext();
// 場景2:對象方法調用 -> this 指向調用對象
var user = {
name: "張三",
age: 25,
introduce: function() {
return `我是${this.name},今年${this.age}歲`;
}
};
console.log(user.introduce()); // 輸出:我是張三,今年25歲
// 場景3:call/apply 強制綁定 -> this 指向指定對象
var anotherUser = { name: "李四", age: 30 };
// call:參數列表傳遞(逗號分隔)
console.log(user.introduce.call(anotherUser)); // 輸出:我是李四,今年30歲
// apply:參數數組傳遞
console.log(user.introduce.apply(anotherUser)); // 輸出:我是李四,今年30歲
// 場景4:bind 延遲綁定 -> 返回 this 綁定后的新函數
var boundIntroduce = user.introduce.bind(anotherUser);
setTimeout(boundIntroduce, 1000); // 1秒后輸出:我是李四,今年30歲關鍵提醒
- 避免在全局作用域濫用
this(易導致變量污染); - 函數作為回調時(如定時器、數組
forEach),this默認指向全局對象(需通過bind手動綁定)。
四、立即執(zhí)行函數(IIFE):作用域隔離的核心工具
IIFE 的核心作用是 “創(chuàng)建獨立作用域,避免變量污染全局”,尤其適用于模塊化開發(fā)和循環(huán)變量隔離。
4.1 IIFE 語法與原理
核心語法(兩種常用形式)
// 形式1:外層括號包裹函數體 + 執(zhí)行括號(推薦,語義清晰)
(function(a, b) {
console.log("IIFE 形式1:" + (a + b)); // 輸出:IIFE 形式1:15
})(10, 5);
// 形式2:函數體括號 + 執(zhí)行括號(省略外層括號,利用運算符優(yōu)先級)
(function(a, b) {
console.log("IIFE 形式2:" + (a * b)); // 輸出:IIFE 形式2:50
}(25, 2));
// 簡化形式(利用一元運算符,如 !、+、-,省略外層括號)
!function(a) {
console.log("IIFE 簡化形式:" + a); // 輸出:IIFE 簡化形式:8
}(8);原理:為什么需要括號?
ES5 解析器會將以 function 開頭的語句視為 “函數聲明”,而函數聲明不能直接調用(需通過函數名)。通過括號包裹,可將其轉為 “函數表達式”,從而支持立即執(zhí)行。
4.2 IIFE 核心應用:解決循環(huán)變量污染
問題場景(未用 IIFE 的缺陷)
// 問題:循環(huán)中 var 聲明的變量共享,導致定時器輸出異常
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log("未用 IIFE:" + i); // 輸出:3 3 3(循環(huán)結束后 i 已變?yōu)?3)
}, 100);
}解決方案(IIFE 隔離作用域)
// 方案:通過 IIFE 將每次迭代的 i 傳入獨立作用域
for (var i = 0; i < 3; i++) {
(function(j) {
// j 是 IIFE 的參數,每次迭代獨立保存 i 的當前值
setTimeout(function() {
console.log("用 IIFE:" + j); // 輸出:0 1 2(正常輸出每次迭代的 i)
}, 100);
})(i); // 將當前 i 作為實參傳入
}五、閉包(Closure):函數與作用域的進階融合
閉包定義為 “有權訪問另一個函數作用域中變量的函數”,其本質是作用域鏈的延長保留,是 ES5 中實現 “狀態(tài)保存” 和 “模塊化” 的核心手段。
5.1 閉包的創(chuàng)建與底層原理
核心條件(滿足以下 3 點即形成閉包)
- 函數嵌套(內層函數嵌套在外層函數中);
- 內層函數引用外層函數的變量 / 參數;
- 外層函數返回內層函數(或內層函數被外部引用)。
實踐實例(計數器:閉包保存狀態(tài))
// 外層函數:定義局部變量 count
function createCounter() {
var count = 0; // 被閉包引用的外層變量,不會被垃圾回收
var step = 1; // 額外參數,演示多變量保存
// 內層函數:引用 count 和 step,形成閉包
return {
increment: function() {
count += step;
return count;
},
setStep: function(newStep) {
step = newStep; // 可通過閉包修改外層變量
}
};
}
// 調用外層函數,獲取閉包對象(保存 count 和 step 的狀態(tài))
var counter1 = createCounter();
var counter2 = createCounter(); // 獨立閉包,狀態(tài)不共享
// 操作 counter1
console.log(counter1.increment()); // 輸出:1(count=1,step=1)
counter1.setStep(2); // 修改 step 為 2
console.log(counter1.increment()); // 輸出:3(count=1+2=3)
// 操作 counter2(與 counter1 狀態(tài)獨立)
console.log(counter2.increment()); // 輸出:1(count=1,step=1)原理補充:垃圾回收機制
正常情況下,函數執(zhí)行完后,其局部變量會被垃圾回收(釋放內存)。但閉包中,由于內層函數引用外層變量,外層函數的作用域會被保留,變量不會被回收,從而實現 “狀態(tài)持久化”。
5.2 閉包的核心應用場景
場景 1:實現私有變量(模塊化基礎)
// 模塊:通過閉包隱藏內部變量,僅暴露指定方法
var userModule = (function() {
// 私有變量:僅內部可訪問,外部無法直接修改
var privateName = "張三";
var privateAge = 25;
// 暴露的公共方法(閉包,引用私有變量)
return {
getName: function() {
return privateName;
},
setAge: function(newAge) {
// 可在方法中添加校驗邏輯,保證數據安全
if (typeof newAge === "number" && newAge > 0) {
privateAge = newAge;
}
return privateAge;
},
getInfo: function() {
return `姓名:${privateName},年齡:${privateAge}`;
}
};
})();
// 外部訪問:只能通過暴露的方法操作,無法直接修改私有變量
console.log(userModule.getName()); // 輸出:張三
console.log(userModule.setAge(28)); // 輸出:28
console.log(userModule.getInfo()); // 輸出:姓名:張三,年齡:28
console.log(userModule.privateName); // 輸出:undefined(私有變量不可直接訪問)場景 2:異步操作中的變量綁定
// 場景:異步請求中保留請求參數(如 AJAX 回調)
function fetchData(url) {
// 模擬 AJAX 異步請求
setTimeout(function() {
// 閉包引用外層變量 url,即使 fetchData 執(zhí)行完,url 仍被保留
console.log(`請求地址:${url},模擬返回數據成功`);
}, 1000);
}
fetchData("https://api.example.com/user"); // 1秒后輸出:請求地址:https://api.example.com/user,模擬返回數據成功
fetchData("https://api.example.com/order"); // 1秒后輸出:請求地址:https://api.example.com/order,模擬返回數據成功5.3 閉包的注意事項
- 內存占用問題:閉包會保留外層作用域,濫用可能導致內存泄漏(如 DOM 事件綁定后未解綁),建議不用時手動解除引用(如
counter = null); - 變量共享問題:若閉包引用循環(huán)變量,需通過 IIFE 隔離(參考前文循環(huán)變量示例);
- this 指向問題:閉包中的
this默認指向全局對象,需通過bind或變量保存(如var self = this)。
六、函數調用的進階:回調函數與異步編程
“函數可作為返回值或參數”,其中 “作為參數” 即回調函數,是 ES5 中實現異步編程的核心方式(如定時器、AJAX、事件綁定)。
6.1 回調函數的定義與分類
核心定義
回調函數是 “作為參數傳遞給另一個函數的函數”,在被調用函數執(zhí)行到特定時機時觸發(fā),分為同步回調和異步回調。
實踐實例
// 1. 同步回調(立即執(zhí)行,如數組遍歷)
var numbers = [1, 2, 3, 4];
// forEach 的參數是同步回調,遍歷完成后立即執(zhí)行
numbers.forEach(function(item, index) {
console.log(`索引${index}:${item * 2}`); // 輸出:索引0:2 索引1:4 索引2:6 索引3:8
});
// 2. 異步回調(延遲執(zhí)行,如定時器)
function loadData(callback) {
console.log("開始加載數據...");
// 模擬異步操作(如 AJAX 請求)
setTimeout(function() {
var data = { name: "李四", age: 30 };
callback(data); // 異步完成后調用回調,傳遞數據
}, 1500);
}
// 調用函數并傳入回調
loadData(function(result) {
console.log("加載完成,數據:", result); // 1.5秒后輸出:加載完成,數據:{ name: '李四', age: 30 }
});
console.log("加載請求已發(fā)送(不阻塞后續(xù)代碼)"); // 先輸出:加載請求已發(fā)送(不阻塞后續(xù)代碼)6.2 回調函數的常見應用
- DOM 事件綁定:如
button.onclick = function() {}; - AJAX 請求:如
XMLHttpRequest的onreadystatechange事件; - 定時器:如
setTimeout、setInterval; - 工具函數:如
Array.prototype.map、filter、reduce。
七、補充:函數的其他關鍵細節(jié)
7.1 函數的 length 屬性
“function.length 表示函數希望接受的命名參數個數”,即形參個數(不包含剩余參數)。
function add(a, b) {
return a + b;
}
function multiply(a, b, c) {
return a * b * c;
}
console.log(add.length); // 輸出:2(形參個數為 2)
console.log(multiply.length); // 輸出:3(形參個數為 3)7.2 函數作為對象(Function 類型)
ES5 中函數是 Function 類型的實例,因此具有屬性和方法(如 name、call、apply、bind)。
function greet(name) {
return `Hello, ${name}`;
}
console.log(greet instanceof Function); // 輸出:true(函數是 Function 實例)
console.log(greet.name); // 輸出:greet(函數名屬性)
console.log(greet.toString()); // 輸出函數的源代碼:function greet(name) { return `Hello, ${name}`; }總結
ES5 函數的核心知識點可歸納為 “三大基礎 + 兩大進階”:
- 三大基礎:函數定義(聲明 / 表達式)、內部屬性(arguments/this)、調用方式(普通 / 方法 /call/apply/bind);
- 兩大進階:IIFE(作用域隔離)、閉包(狀態(tài)保存與模塊化)。
函數是 JavaScript 編程的 “基石”,掌握以上內容不僅能應對 ES5 環(huán)境的開發(fā)需求,也能為理解 ES6+ 的箭頭函數、解構賦值等特性奠定基礎。建議結合實例反復調試,重點突破 this 綁定和閉包的細節(jié)~
到此這篇關于JavaScript ES5 函數從基礎到高級應用全解析的文章就介紹到這了,更多相關JavaScript ES5 函數內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
使用JSX 建立組件 Parser(解析器)開發(fā)的示例
這篇文章主要介紹了使用JSX 建立組件 Parser(解析器)開發(fā)的示例(前端組件化)本文重點講述我們如何從0開始搭建一個組件系統(tǒng),基于標記語言的Parser的一種風格2021-04-04
Layui table field初始化加載時進行隱藏的方法
今天小編就為大家分享一篇Layui table field初始化加載時進行隱藏的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-09-09

