一、什么是函数
在 JavaScript 中,函数是一种可重复使用的代码块,用于执行特定任务或计算。函数可以接受参数,执行特定的操作,并返回一个值。
二、函数的声明
函数可以通过函数声明、函数表达式或箭头函数来定义。
- 函数声明:
function functionName(parameters) {
// 函数体
return value; // 可选
}
- 函数表达式:
const functionName = function(parameters) {
// 函数体
return value; // 可选
};
- 箭头函数(ES6+):
const functionName = (parameters) => {
// 函数体
return value; // 可选
};
三、函数的调用
-
一般调用: 函数名([参数])
-
事件调用 触发事件后调用函数
-
可以多次调用,每次调用是独立不相关的
-
函数的执行跟定义位置无关,只与调用的位置有关
(1)无参函数
function func1(){
console.log('hello world')
}
func1() // 调用,加括号调用,跟Python是一样的
(2)有参函数
function func2(a,b){
console.log(a,b)
}
func2(1,2)
VM3223:2 1 2
func2(1,2,3,4,5,6,7) # 传多了没关系,只要对应的数据
VM3223:2 1 2
func2(1) # 少了也没关系
VM3223:2 1 undefined
四、函数参数
- 形式参数(形参):定义函数时所传递的参数
- 实际参数(实参): 调用函数时所传递的参数
注意:
参数传递: 只能将实参 传递给形参,即单向传递
形参只能是变量; 实参可以是变量,常量,表达式
实参的数量 小于形参的数量时,多余的形参值为undefined
实参的数量 大于形参的数量时,自动忽略多余的实参
函数可以接受零个或多个参数,参数在函数定义中的括号中指定。参数可以是任何有效的 JavaScript 表达式。
function add(a, b) {
return a + b;
}
五、函数返回值
在 JavaScript 中,函数可以通过 return
语句返回一个值。函数的返回值可以是任何数据类型,包括数字、字符串、布尔值、对象、数组等。
(1)返回单个值
函数可以返回单个值,该值可以是任何 JavaScript 数据类型。
function add(a, b) {
return a + b;
}
let result = add(3, 4); // result 等于 7
(2)返回对象
函数可以返回一个对象,对象可以包含多个属性。
function createPerson(name, age) {
return {
name: name,
age: age
};
}
let person = createPerson("Alice", 30);
console.log(person.name); // 输出 "Alice"
console.log(person.age); // 输出 30
(3)返回数组
函数可以返回一个数组。
function getNumbers() {
return [1, 2, 3, 4, 5];
}
let numbers = getNumbers();
console.log(numbers); // 输出 [1, 2, 3, 4, 5]
(4)返回函数
函数可以返回另一个函数。
function createMultiplier(multiplier) {
return function(x) {
return x * multiplier;
};
}
let double = createMultiplier(2);
let result = double(5); // result 等于 10
(5)返回 undefined
如果函数没有明确的 return
语句,它将返回 undefined
。
function noReturn() {
// 没有 return 语句
}
let value = noReturn(); // value 等于 undefined
(6)多个返回值
JavaScript 中函数只能返回一个值。如果需要返回多个值,可以使用对象、数组或其他数据结构来封装这些值。
function calculateCircle(radius) {
let circumference = 2 * Math.PI * radius;
let area = Math.PI * radius * radius;
return { circumference: circumference, area: area };
}
let circleInfo = calculateCircle(5);
console.log(circleInfo.circumference); // 输出圆周长
console.log(circleInfo.area); // 输出面积
(7)函数返回值的应用
- 函数的返回值不仅可以用作其他变量的赋值,还可以作为其他函数的参数。
- 例如,在排序算法中,我们可以创建一个比较函数并将其作为
Array.prototype.sort()
方法的参数,利用其返回值来决定数组元素的排列顺序
function compareNumbers(a, b) {
return a - b; // 返回a与b的差值,sort()会根据这个差值决定元素的顺序
}
const numbers = [5, 2, 9, 1, 5, 6];
numbers.sort(compareNumbers); // 排序后的数组:[1, 2, 5, 5, 6, 9]
(8)特殊之处
- 返回多个值且用逗号连接,则只能拿到最后一个
function index(){
return 666,777,888
}
res = index()
888
res
888 # 只能拿到最后一个
- JavaScript不支持解压赋值
function index(){
return [666,667,888]
}
res = index()
(3) [666, 667, 888]
res
(3) [666, 667, 888]
六、函数作用域
- 函数内部定义的变量在函数外部不可访问,这被称为函数作用域。
- 函数的参数是局部变量,只能在函数内部使用
- 函数内部的变量也是局部变量
- 可以理解为函数体本身是一个独立的作用域
(1)作用域的范围
全局作用域:在函数外定义的变量,在页面的任何一个地方都可以使用
局部作用域: 在函数内定义的变量,只能在函数内使用
注意:
- 在函数中声明变量不加var 就是全局变量;形参 是局部作用域
- 变量退出作用域会自动销毁,全局变量只有关闭浏览器才会销毁
(2)作用域链
作用域链:如果当前作用域没有,则向父级作用域查找,如果还没有,继续向父级作用域,一直找到全局作用域,如果还没有,报错
一旦进入作用域,就启动js解析器
1、预解析 var function 形参
找到var后,将var后的变量名提取出来,并给他初始化一个值undefined
变量和函数同名,丢变量,保函数
当有多个script标签时,从上到下依次解析并执行第一个script标签,然后下一个,依次类推
在上面script标签中声明的东西,下面script标签中都可以使用
下面script标签中声明的东西,上面使用会报错
2、逐行解读代码
执行表达式
函数调用
(3)遮蔽效应
- 程序在遇到一个变量时,使用时作用域查找顺序,不同层次的函数内都有可能定义相同名字的变量名,会优先从自己所在的层查找,如果没有查找到会依次往上找。
- 整个过程中会发生内层变量遮蔽外层变量的效果叫做遮蔽效应
七、匿名函数
匿名函数是没有函数名的函数,通常用作回调函数或立即执行函数。
- 匿名函数,也称为无名函数或者lambda表达式,是一种没有名称的JavaScript函数定义形式。
- 它们在编程中广泛用于一次性使用的简单计算、事件处理、高阶函数等场景,因为它们不需要被赋给特定变量或者作为对象的方法来使用。
(1)将匿名函数赋值给一个变量
var fn = function () {
alert('hello')
}
fn()
(2)将匿名函数绑定给一个事件
document.onclick = function () {
alert(0)
}
(3)匿名函数自我执行
(function () {
alert(123)
})()
(4)匿名函数的返回值(将一个匿名函数的返回值赋值给一个变量)
var num = (function () {
return 3
})()
alert(num)
(5)输出一个匿名函数的返回值
alert(
(function () {
return 'hello'
})()
)
(7)给匿名函数传递参数
var sum = (function (a, b) {
return a + b
})(3, 4)
alert(sum)
(8)特点与用途
-
无名/临时作用域:由于匿名函数没有名字,它们的变量和参数只在其所在的作用域内有效,不会污染全局命名空间,适用于一些短暂或局部需求的场合。
-
闭包:当匿名函数作为另一个函数的参数传递或返回时,它可以访问到外部函数的局部变量和参数,形成闭包。这对于实现数据封装、模块化编程以及实现私有变量等功能非常有用。
-
简便的函数创建:在某些情况下,如高阶函数应用、事件处理器等,使用匿名函数可以避免额外的声明和赋值操作,使得代码更加简洁高效。
-
立即执行:通过使用立即调用表达式(IIFE),匿名函数可以在定义的同时就被执行,比如初始化数据、防止变量污染全局命名空间等场景。
八、函数的递归
(1)递归介绍
函数自己调用自己,跟循环类似,他为了避免无限的递归,需要设置一个递归终止点
本质 :实现循环
- 函数的内部可以调用函数自己本身,这种做法称为递归
- 函数递归需要考虑好结束递归的条件,不然会出现死递归导致程序崩溃,内存溢出
(2)案例介绍
- 1~n之间所有数的和
function fnSum(n) {
// 结束条件
if (n === 1) {
return 1
}
return n + fnSum(n - 1)
}
console.log(fnSum(100))
- 计算斐波那契数列的第N项
斐波那契数列(Fibonacci sequence),又称黄金分割数列。因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”。指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上, 斐波那契数列以如下被以递推的方法定义:F(1)=1,F(2)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 3)。
function fibonacci(n){
if(n==1){
return 1;
}
if(n==2){
return 1;
}
//n = 3 :return fibonacci(2)+fibonacci(1)
//n = 4 :return fibonacci(3)+fibonacci(2)
return fibonacci(n-1)+fibonacci(n-2);
}
fibonacci(10)
(3)递归函数的优缺点
优点:结构清晰, 可读性强, 而且容易用数学归纳法来证明算法的正确性, 因此它为设计算法、 调试程序带来很大方便。
缺点:递归算法的运行效率较低, 无论是耗费的计算时间还是占用的存储空间都比非递归算法要多。
九、箭头函数
在 JavaScript 中,箭头函数是 ES6 引入的一种新的函数语法,提供了一种更简洁的函数定义方式。下面是关于 JavaScript 箭头函数的详细介绍:
(1)基本语法
箭头函数使用 =>
符号来定义,语法如下:
// 无参数箭头函数
const greet = () => {
return "Hello!";
};
// 单个参数箭头函数
var func1 = (v) => v;
// 或者等价的传统写法
var func1 = function(v) {
return v;
};
// 多个参数箭头函数
var func2 = (args1, args2) => args1 + args2;
// 或者等价的传统写法
var func2 = function(args1, args2) {
return args1 + args2;
};
(2)省略括号和 return
当函数只有一个参数时,可以省略参数的括号;如果函数体只有一条语句且是返回语句,可以省略花括号和 return
关键字。
const double = x => x * 2; // 省略括号和 return
const sayHello = name => `Hello, ${name}!`; // 省略花括号和 return
(3)箭头函数和 this
箭头函数没有自己的 this
上下文,它会继承外部作用域的 this
值。
const person = {
name: "Alice",
greet: function() {
setTimeout(() => {
console.log(`Hello, my name is ${this.name}`);
}, 1000);
}
};
person.greet(); // 输出 "Hello, my name is Alice"
(4)适用场景
- 简洁性:箭头函数通常比传统函数更简洁,特别是在回调函数或只包含一行代码的函数中。
- this 上下文:箭头函数的
this
绑定在定义时的上下文,避免了传统函数中this
指向的困扰。
(5)注意事项
- 箭头函数不能用作构造函数,不能使用
new
关键字实例化。 - 箭头函数没有自己的
arguments
对象,可以使用剩余参数(rest parameters)或展开语法替代。
(6)与传统函数的区别
- 没有
this
、arguments
、super
和new.target
绑定。 - 不能用作构造函数。
- 没有原型(prototype)属性。
箭头函数在简化代码、处理 this 上下文等方面提供了便利,但也需要注意其适用场景和限制。
补充:函数的全局变量与局部变量
- 跟python查找变量顺序一样
var city = "BeiJing";
function f() {
var city = "ShangHai";
function inner(){
var city = "ShenZhen";
console.log(city);
}
inner();
}
f(); //输出结果是?
-
输出结果是:"ShenZhen"。
-
在函数
inner
内部,变量city
的值被赋为 "ShenZhen"- 因此
console.log(city)
会输出 "ShenZhen"。
- 因此
-
由于函数
inner
是在函数f
中定义的,并且在函数f
中调用了inner
- 所以同样的规则适用于函数
f
。
- 所以同样的规则适用于函数
-
即使在函数
f
内部有一个与全局变量city
同名的局部变量city
- 但在执行
console.log(city)
时,它将访问最内层的局部变量city
的值 - 也就是 "ShenZhen"。
- 但在执行
var city = "BeiJing";
function Bar() {
console.log(city);
}
function f() {
var city = "ShangHai";
return Bar;
}
var ret = f();
ret(); // 打印结果是?
-
在你提供的代码中,输出结果将是:"BeiJing"。
-
首先,全局变量
city
被赋值为 "BeiJing"。 -
然后,在函数
f
内部定义了一个名为city
的局部变量,将其值设置为 "ShangHai"。 -
接下来,
f
函数返回了函数Bar
的引用。 -
在执行
var ret = f()
后,ret
变量被赋值为Bar
函数的引用- 即
ret
现在指向函数Bar
。
- 即
-
最后,执行
ret()
此时由于
ret
指向Bar
函数- 所以会执行
Bar
函数,并打印当前作用域内的变量city
的值,即输出 "BeiJing"。
- 所以会执行
-
注意,这是因为
Bar
函数在执行时访问的是其定义时所处的作用域,即全局作用域,而非f
函数内部的作用域。
var city = "BeiJing";
function f(){
var city = "ShangHai";
function inner(){
console.log(city);
}
return inner;
}
var ret = f();
ret();
-
输出结果将是:"ShangHai"。
-
首先,全局变量
city
被赋值为 "BeiJing"。- 然后,在函数
f
内部定义了一个名为city
的局部变量,将其值设置为 "ShangHai"。 - 接下来,
f
函数返回了函数inner
的引用。
- 然后,在函数
-
在执行
var ret = f()
后,ret
变量被赋值为inner
函数的引用- 即
ret
现在指向函数inner
。
-
最后,执行
ret()
- 此时由于
ret
指向inner
函数,所以会执行inner
函数,并打印当前作用域内的变量city
的值,即输出 "ShangHai"。
- 此时由于
-
注意,这是因为
inner
函数在执行时访问的是其定义时所处的作用域,即函数f
内部的作用域。在该作用域中,变量city
的值为 "ShangHai"。
补充:arguments对象
在 JavaScript 中,arguments
是一个类数组对象(array-like object),它包含了函数调用时传递给函数的所有参数。下面是关于 arguments
关键字的详细解释:
1. 访问参数
通过 arguments
可以访问函数接收到的所有参数,即使在函数定义时没有明确指定参数名。
普通用法
function func4(a,b){
if(arguments.length<2){
console.log('传少了')
}else if(arguments.length>2){
console.log('传多了')
}else{
console.log('正常执行')
}
}
func4(1)
VM3378:3 传少了
func4(1,2,34,8)
VM3378:5 传多了
func4(3,2)
VM3378:7 正常执行
进阶用法
function sum() {
let total = 0;
for (let i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
console.log(sum(1, 2, 3)); // 输出 6
2. 类数组对象
arguments
是一个类数组对象,它有一个 length
属性和按索引访问参数的能力,但它并不是一个真正的数组,因此不具备数组的方法(如 push
、pop
等)。
3. 与剩余参数语法的区别
在 ES6 中引入了剩余参数(rest parameters)语法,它更灵活且易于使用,与 arguments
相比具有以下优势:
- 剩余参数语法创建的是一个真正的数组,可以直接使用数组的方法。
- 剩余参数只包含函数定义时未命名的参数,而
arguments
包含所有传递给函数的参数。
4. 限制
arguments
对象是一个类数组对象,不是真正的数组,因此不能直接调用数组的方法。- 在严格模式下,无法对
arguments
对象进行赋值,即使对arguments
的修改也不会影响到对应的函数参数。
5. 使用建议
- 在 ES6 中,推荐使用剩余参数语法来代替
arguments
对象,因为它更灵活、易读且具有更好的数组支持。 - 如果需要访问所有传递给函数的参数,包括未命名的参数,可以继续使用
arguments
对象。
总的来说,arguments
对象提供了一种访问函数参数的方式,但随着 ES6 的引入,剩余参数语法通常被认为是更好的选择,因为它更灵活、易用且更符合现代 JavaScript 的发展趋势。