函数的概念
- 函数定义:函数是一段可重复使用的代码块,可以通过函数名来调用。
- 函数的作用:封装代码、提高代码复用性、模块化编程。
函数的定义方式
1.1函数声明
function
命令声明的代码区块,就是一个函数。function
命令后面是函数名,函数名后面是一对圆括号,里面是传入函数的参数。函数体放在大括号里面
js
function print(s) {
console.log(s);
}
上面的代码命名了一个print
函数,以后使用print()
这种形式 就可以调用相应的代码。 这叫做函数的声明(Function Declaration)
1.2 函数表达式
除了用function
命令声明函数,还可以采用变量赋值的写法。
js
var print = function(s) {
console.log(s);
};
1.3 Function 构造函数
javascript
var add = new Function(
'x',
'y',
'return x + y'
);
// 等同于
function add(x, y) {
return x + y;
}
函数表达式和函数申明的区别
函数会变量提升
sql
console.log(sum(10, 10));
function sum(num1, num2) {
return num1 + num2;
}
javascript
console.log(sum(10, 10)); //会报错
let sum = function(num1, num2) {
return num1 + num2;
};
函数调用
普通函数调用
- 直接通过函数名加括号调用。
- 适用于普通函数声明和函数表达式。
js
const person = {
name: "Alice",
greet: function() {
console.log(`Hello, ${this.name}!`);
}
};
person.greet(); // 输出: Hello, Alice!
方法调用
- 当函数作为对象的方法时,通过对象调用。
- 此时,函数内部的
this
指向调用该方法的对象。
js
const person = {
name: "Alice",
greet: function() {
console.log(`Hello, ${this.name}!`);
}
};
person.greet(); // 输出: Hello, Alice!
kotlin
const calculator = {
value: 0,
add: function(num) {
this.value += num;
return this;
},
multiply: function(num) {
this.value *= num;
return this;
}
};
calculator.add(5).multiply(2);
console.log(calculator.value); // 输出: 10
间接调用( call
、 apply
、 bind
)
call()和apply()
、
语法 :显式指定 this
和参数:
func.call(thisArg, arg1, arg2...)
:参数逐个传递。func.apply(thisArg, [arg1, arg2...])
:参数以数组传递。
this
指向 :thisArg
参数指定的对象。
javascript
function showInfo(age, city) {
console.log(`${this.name}, ${age}, ${city}`);
}
const obj = { name: "Charlie" };
showInfo.call(obj, 25, "New York"); // Charlie, 25, New York
showInfo.apply(obj, [25, "New York"]); // 同上
bind()
语法 :const boundFn = func.bind(thisArg)
,返回一个绑定 this
的新函数。
this
指向 :永久绑定到 thisArg
,无法再修改。
ini
const boundShow = showInfo.bind(obj);
boundShow(30, "London"); // Charlie, 30, London
箭头函数调用
- 箭头函数没有自己的
this
,继承自外层作用域。 - 不能通过
call
、apply
、bind
改变this
。
ini
const outerThis = this; // 假设外层 this 是全局对象
const arrowFn = () => {
console.log(this === outerThis); // true
};
arrowFn.call({}); // 仍然指向外层 this(无法修改)
递归调用
递归指函数内部调用自身的方式。
- 主要用于数量不确定的循环操作
- 要有退出时机否则会陷入死循环
我们的第一个主题是 递归(recursion) 。
递归是一种编程模式,在一个任务可以自然地拆分成多个相同类型但更简单的任务的情况下非常有用。
在一个任务可以简化为一个简单的行为加上该任务的一个更简单的变体的时候可以使用。或者,就像我们很快会看到的那样,处理某些数据结构。
当一个函数解决一个任务时,在解决的过程中它可以调用很多其它函数。在部分情况下,函数会调用 自身 。这就是所谓的 递归。
简单起见,让我们写一个函数 pow(x, n)
,它可以计算 x
的 n
次方。换句话说就是,x
乘以自身 n
次。
有两种实现方式。
- 迭代思路:使用
for
循环:
ini
function pow(x, n) {
let result = 1;
// 在循环中,用 x 乘以 result n 次
for (let i = 0; i < n; i++) {
result *= x;
}
return result;
}
alert( pow(2, 3) ); // 8
2 递归思路:简化任务,调用自身:
scss
function pow(x, n) {
if (n == 1) {
return x;
} else {
return x * pow(x, n - 1);
}
}
alert( pow(2, 3) ); // 8
递归思路:简化任务,调用自身:
scss
function pow(x, n) {
if (n == 1) {
return x;
} else {
return x * pow(x, n - 1);
}
}
alert( pow(2, 3) ); // 8
递归遍历
递归的另一个重要应用就是递归遍历。假设我们有一家公司。人员结构可以表示为一个对象:
javascript
let company = { // 简洁起见被压缩了
sales: [{name: 'John', salary: 1000}, {name: 'Alice', salary: 1600 }],
development: {
sites: [{name: 'Peter', salary: 2000}, {name: 'Alex', salary: 1800 }],
internals: [{name: 'Jack', salary: 1300}]
}
};
// 用来完成任务的函数
function sumSalaries(department) {
if (Array.isArray(department)) { // 情况(1)
return department.reduce((prev, current) => prev + current.salary, 0); // 求数组的和
} else { // 情况(2)
let sum = 0;
for (let subdep of Object.values(department)) {
sum += sumSalaries(subdep); // 递归调用所有子部门,对结果求和
}
return sum;
}
}
alert(sumSalaries(company)); // 7700

递归结构递归(递归定义的)数据结构是一种部分复制自身的结构。
回调函数调用
参数是一个函数(通常是匿名函数),该函数会在行为(action)完成时运行。这是一个带有真实脚本的可运行的示例:
ini
// 这里我们加载一个脚本,设置一个回调函数,和我们说什么时候脚本加载完成
function loadScript(src, callback) {
let script = document.createElement('script');
script.src = src;
script.onload = () => callback(script);
document.head.append(script);
}
loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', script => {
alert(`酷,脚本 ${script.src} 加载完成`);
alert( _ ); // _ 是所加载的脚本中声明的一个函数
});
这被称为"基于回调"的异步编程风格。异步执行某项功能的函数应该提供一个 callback
参数用于在相应事件完成时调用。(译注:上面这个例子中的相应事件是指脚本加载)
回调地狱问题
加载多个异步请求怎么处理
lua
loadScript('1.js', function(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('2.js', function(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('3.js', function(error, script) {
if (error) {
handleError(error);
} else {
// ...加载完所有脚本后继续 (*)
}
});
}
});
}
});
为什么优先判断错误,第一次错了,就不用执行后续情况了,称为Error 优先回(errorfirstcallback 风格。

场景 :将函数作为参数传递给其他函数(如事件处理器、定时器)。
this
的陷阱 :回调函数中的 this
默认指向全局对象(需显式绑定)。
javascript
const button = document.querySelector("button");
const handler = {
message: "Clicked!",
handleClick() {
console.log(this.message); // 默认 this 指向 button 元素(需绑定)
},
};
// 错误示例:this 指向 button 元素
button.addEventListener("click", handler.handleClick);
// 正确示例:通过 bind 绑定 this
button.addEventListener("click", handler.handleClick.bind(handler));
- 回调函数中的
this
丢失 :
使用bind()
或箭头函数解决:
scss
setTimeout(handler.handleClick.bind(handler), 1000);
// 使用箭头函数包裹
setTimeout(() => handler.handleClick(), 1000);
ini
function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("Jack");
alert(user.name); // Jack
alert(user.isAdmin); // false
构造函数
构造函数在技术上是常规函数。不过有两个约定:
- 它们的命名以大写字母开头。
- 它们只能由
"new"
操作符来执行。
ini
function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("Jack");
alert(user.name); // Jack
alert(user.isAdmin); // false
当一个函数被使用 new
操作符执行时,它按照以下步骤:
- 一个新的空对象被创建并分配给
this
。 - 函数体执行。通常它会修改
this
,为其添加新的属性。 - 返回
this
的值。
换句话说,new User(...)
做的就是类似的事情:
ini
function User(name) {
// this = {};(隐式创建)
// 添加属性到 this
this.name = name;
this.isAdmin = false;
// return this;(隐式返回)
}
所以 new User("Jack") 的结果和以下操作的结果相同:
let user = {
name: "Jack",
isAdmin: false
};
构造器的 return
通常,构造器没有 return
语句。它们的任务是将所有必要的东西写入 this
,并自动转换为结果。
但是,如果这有一个 return
语句,那么规则就简单了:
- 如果
return
返回的是一个对象,则返回这个对象,而不是this
。 - 如果
return
返回的是一个原始类型,则忽略。
csharp
function BigUser() {
this.name = "John";
return { name: "Godzilla" }; // <-- 返回这个对象
}
alert( new BigUser().name ); // Godzilla,得到了那个对象
这里有一个 return 为空的例子(或者我们可以在它之后放置一个原始类型,没有什么影响):
function SmallUser() {
this.name = "John";
return; // <-- 返回 this
}
alert( new SmallUser().name ); // John
构造器中的方法
ini
function User(name) {
this.name = name;
this.sayHi = function() {
alert( "My name is: " + this.name );
};
}
let john = new User("John");
john.sayHi(); // My name is: John
调度:setTimeout 和 setInterval
setTimeout
允许我们将函数推迟到一段时间间隔之后再执行。
scss
let timerId = setTimeout(() => alert("never happens"), 1000);
alert(timerId); // 定时器标识符
clearTimeout(timerId);
alert(timerId); // 还是这个标识符(并没有因为调度被取消了而变成 null)
ini
我们要实现一个服务(server),每间隔 5 秒向服务器发送一个数据请求,但如果服务器过载了,
那么就要降低请求频率,比如将间隔增加到 10、20、40 秒等。
let delay = 5000;
let timerId = setTimeout(function request() {
...发送请求...
if (request failed due to server overload) {
// 下一次执行的间隔是当前的 2 倍
delay *= 2;
}
timerId = setTimeout(request, delay);
}, delay);
scss
// 每 2 秒重复一次
let timerId = setInterval(() => alert('tick'), 2000);
// 5 秒之后停止
setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000);
函数参数
概述
函数运行的时候,有时需要提供外部数据,不同的外部数据会得到不同的结果,这种外部数据就叫参数。
参数传递规则
- 基本类型:值传递(函数内修改不影响外部)
- 对象类型:引用传递(函数内修改属性影响外部)
形参实参
形参是在函数声明时设置的参数,实参指在调用函数时传递的值。
- 形参数量大于实参时,没有传参的形参值为 undefined
- 实参数量大于形参时,多于的实参将忽略并不会报错
javascript
// n1,n2 为形参
function sum(n1, n2) {
return n1+n2;
}
// 参数 2,3 为实参
console.log(sum(2, 3)); //5
当没传递参数时值为undefined
function sum(n1, n2) {
return n1 + n2;
}
console.log(sum(2)); //NaN
默认参数
sql
//total:总价 year:年数
function avg(total, year) {
year = year || 1;
return Math.round(total / year);
}
console.log(avg(2000, 3));
// 使用新版本默认参数方式如下
function avg(total, year = 1) {
return Math.round(total / year);
}
console.log(avg(2000, 3));
下面通过排序来体验新版默认参数的处理方式,下例中当不传递 type 参数时使用默认值 asc。
typescript
unction sortArray(arr, type = 'asc') {
return arr.sort((a, b) => type == 'asc' ? a - b : b - a);
}
console.log(sortArray([1, 3, 2, 6], 'desc'));
默认参数要放在最后面
javascript
//total:价格,discount:折扣,dis:折后折
function sum(total, discount = 0, dis = 0) {
return total * (1 - discount) * (1 - dis);
}
console.log(sum(2000, undefined, 0.3));
函数作为函数值进行传递
scss
function callSomeFunction(someFunction, someArgument) {
return someFunction(someArgument);
}
这个函数接收两个参数。第一个参数应该是一个函数,第二个参数应该是要传给这个函数的值。
任何函数都可以像下面这样作为参数传递:
function add10(num) {
return num + 10;
}
let result1 = callSomeFunction(add10, 10);
console.log(result1); // 20
function getGreeting(name) {
return "Hello, " + name;
}
let result2 = callSomeFunction(getGreeting, "Nicholas");
console.log(result2); // "Hello, Nicholas"
callSomeFunction()函数是通用的,第一个参数传
展开语法 . . .
展示语法或称点语法体现的就是收/放特性,做为值时是放,做为接收变量时是收。
ini
let hd = [1, 2, 3];
let [a, b, c] = [...hd];
console.log(a); //1
console.log(b); //2
console.log(c); //3
[...hd] = [1, 2, 3];
console.log(hd); //[1, 2, 3]
使用展示语法可以替代 arguments 来接收任意数量的参数
scss
function hd(...args) {
console.log(args);
}
hd(1, 2, 3,); //[1, 2, 3, ]
也可以用于接收部分参数
scss
function hd(site, ...args) {
console.log(site, args); // (3) [1, 2, 3]
}
hd( 1, 2, 3);
javascript
#### arguments
arguments 是函数获得到所有参数集合,下面是使用 arguments 求和的例子
function sum() {
return [...arguments].reduce((total, num) => { return (total += num); }, 0);
}
console.log(sum(2, 3, 4, 2, 6)); //17
更建议使用展示语法
function sum(...args) {
return args.reduce((a, b) => a + b);
}
console.log(sum(2, 3, 4, 2, 6)); //17