1.1 函数的概念
函数是一个被命名为执行特定任务的代码块。它可以将一段代码组织起来,使其可以多次使用,而不必重复编写相同的代码。
为什么需要函数
- 代码复用性:通过将重复的代码逻辑封装在函数中,可以避免代码的冗余。当我们需要在多个地方执行相同的操作时,只需要调用该函数即可,而不需要重新编写相同的代码。
- 提高代码的可读性和可维护性:通过将代码逻辑封装在函数中,并为其赋予有意义的名称,可以使代码更加易于理解和维护。当其他开发者阅读代码时,可以通过函数名快速了解其功能,而不需要深入研究具体的实现细节。
- 模块化编程:函数可以作为代码模块的基本单元,通过将相关的函数组织在一起,可以形成具有特定功能的代码模块。这种模块化编程的方式有利于代码的模块化和组件化,提高代码的复用性和可维护性。
1.2 函数使用
1.2.1 函数定义
在JavaScript中,函数使用 function
关键字进行定义,后跟函数名和括号 ()
,函数体则在大括号 {}
内。
function 函数名() {
// 函数体
}
例如,下面定义了一个名为 sayHi
的函数,它会在被调用时向文档写入 ‘hai~~’。
function sayHi() {
document.write('hai~~');
}
1.2.2 函数命名规范
- 推荐使用小驼峰式命名法(lowerCamelCase)。
- 函数名前缀通常为动词,以清晰表达函数的功能。
- 常用动词如
get
、set
、load
等,有助于理解函数作用。
1.2.3 函数调用
函数在被定义后,必须被调用才会执行其中的代码。调用函数时,只需要写出函数名,后跟括号 ()
。
例如,上面定义的 sayHi
函数可以通过以下方式调用:
sayHi();
1.2.4 函数体
函数体是函数的主体部分,其中包含了实现函数功能的代码。只有当函数被调用时,这些代码才会被执行。所有的功能代码都应该写在函数体内。
1.3 函数传参
1.3.1 参数传递的重要性
当函数需要完成某些功能,而这些功能依赖于外部提供的数据时,就需要使用参数。参数使得函数更加灵活,能够适应不同的场景和需求。
1.3.2 声明语法
在函数名后的括号中可以声明参数,多个参数之间用逗号隔开。
function 函数名(参数列表) {
函数体
}
例如,下面的函数 getSum
用于计算两个数的和,它接受两个参数 num1
和 num2
:
function getSum(num1, num2) {
document.write(num1 + num42);
}
1.3.3 调用语法
调用函数时,需要传入与参数列表对应的实际参数。实际参数的个数和顺序应与形参一致。
例如,调用上面定义的 getSum
函数:
getSum(10, 20); // 输出 30
1.3.4 形参与实参的理解
- 形参(Formal Parameters):在函数声明时定义的参数,它们是函数内部的局部变量,用于接收调用者传递的数据。
- 实参(Actual Parameters):在函数调用时传递给函数的实际值,这些值被赋给函数内部的形参。
可以把形参理解为函数内部的变量声明,而实参则是给这些变量赋值。在开发过程中,为了保证数据的正确传递和处理,通常应确保形参和实参的个数一致。
1.3.5 参数默认值
在JavaScript中,如果函数的形参没有被赋予实参值,那么这个形参的值默认是 undefined
。在某些情况下,这可能会导致不可预期的结果,比如在执行数学运算时。为了解决这个问题,我们可以给形参设置默认值。
①设置默认值的方法
在声明函数时,可以直接给形参赋值,这个值就是这个形参的默认值。如果调用函数时没有为这个形参传递实参,那么它就会使用这个默认值。
例如:
function getSum(x = 0, y = 0) {
document.write(x + y);
}
在这个例子中,x
和 y
的默认值都是 0
。所以,如果我们调用 getSum()
,没有传递任何实参,那么就会输出 0
。如果传递了实参,比如 getSum(1, 2)
,那么就会输出 3
。
②默认值的作用时机
需要注意的是,默认值只会在缺少实参传递时才会被执行。也就是说,如果有实参传递,那么就会优先执行传递过来的实参,否则才会使用默认值。
案例:求学生总分
现在有一个需求:学生的分数是一个数组,我们需要计算每个学生的总分。这个需求可以通过函数封装来实现。我们可以定义一个函数 getArraySum
,它接受一个数组作为参数,然后计算这个数组中所有元素的和。如果调用这个函数时没有传递数组,那么就使用一个空数组作为默认值。
例如:
let a = [1, 2, 3, 4, 6];
function getArraySum(arr = []) {
let sum = 0;
for (let i = 0; i < arr.length; i++) {
sum += arr[i];
}
alert(sum);
}
getArraySum(a); // 输出 16
getArraySum([10, 20, 30]); // 输出 60
getArraySum(); // 输出 0
1.4 函数返回值
1.4.1 返回值的概念
在JavaScript中,函数可以返回一个值。这个值可以是任何数据类型,比如数字、字符串、对象等。当函数执行到 return
语句时,它会立即停止执行,并将 return
后面的值返回给调用者。
1.4.2 使用 return
关键字
要使用返回值,需要在函数体内使用 return
关键字,后跟要返回的值。
例如:
function getSum(num1, num2) {
return num1 + num2;
}
在这个例子中,getSum
函数会返回两个参数的和。
1.4.3 接收返回值
当调用一个有返回值的函数时,需要用一个变量来接收返回的值。例如:
let result = getSum(1, 2); // result 的值为 3
return
关键字的作用
return
关键字不仅会返回一个值,还会立即结束当前函数的执行。所以,return
后面的代码不会被执行。为了避免错误,建议不要将 return
后面的数据换行写。
1.4.4 默认返回值
如果一个函数没有 return
语句,或者 return
后面没有跟任何值,那么这个函数的默认返回值是 undefined
。
函数补充:
①函数覆盖
在JavaScript中,如果你声明了两个相同的函数,后面的函数会覆盖前面的函数。这意味着,如果两个函数具有相同的名称,那么调用该函数时将执行最后声明的那个。
②参数数量不一致
在JavaScript中,实参的个数和形参的个数可以不一致。这种情况下,函数仍然可以正常工作,但需要注意以下几点:
形参过多
如果形参过多,即函数声明时的参数数量大于调用时传递的实参数量,那么缺失的参数值将是 undefined
。例如:
function example(a, b, c) {
console.log(a, b, c);
}
example(1, 2); // 输出:1 2 undefined
实参过多
如果实参过多,即调用函数时传递的参数数量大于函数声明时的参数数量,那么多余的实参将被忽略。不过,需要注意的是,函数内部有一个名为 arguments
的对象,它包含了所有传递给函数的实参。例如:
function example(a, b) {
console.log(a, b, arguments[2]);
}
example(1, 2, 3); // 输出:1 2 3
③使用 return
结束函数
一旦函数执行到 return
语句,它将立即停止执行并返回指定的值。return
语句不仅用于返回值,还用于结束函数的执行。如果在函数中使用了 return
,那么 return
后面的代码将不会被执行。例如:
function example() {
console.log('Hello');
return;
console.log('World'); // 这行代码不会被执行
}
example(); // 只输出 'Hello'
1.5 作用域
在编程中,作用域是一个非常重要的概念,它定义了变量、函数和对象的可见性和生命周期。理解作用域可以帮助你写出更加健壮、可维护和高效的代码。
1.5.1 全局作用域
全局作用域是最外层的作用域,它在整个代码执行环境中都是有效的。在Web浏览器中,全局作用域通常是整个<script>
标签内部或一个独立的JavaScript文件。在全局作用域中声明的变量和函数可以在任何地方被访问和修改。
1.5.2 局部作用域
局部作用域,也称为函数作用域,是在函数内部定义的作用域。在局部作用域中声明的变量和函数只能在该函数内部被访问和修改。当函数执行完毕后,局部作用域中的变量会被销毁,释放内存。
1.5.3 变量分类
根据作用域的不同,JavaScript中的变量可以分为全局变量和局部变量:
- 全局变量:在函数外部使用
let
声明的变量。全局变量可以在任何区域被访问和修改。但是过度使用全局变量可能导致命名冲突和代码难以维护,因此通常建议尽量减少全局变量的使用。 - 局部变量:在函数内部使用
let
声明的变量。局部变量只能在当前函数内部被访问和修改。当函数执行完毕,局部变量的生命周期也随之结束,这样可以减少命名冲突并节省内存。
注意
- 未声明的变量:如果函数内部直接给一个变量赋值而没有使用
let
声明,那么这个变量将被当作全局变量处理。但是这种做法是不推荐的,因为它容易导致命名冲突和代码难以维护。 - 形参:函数的形参也可以看作是局部变量,它们只在函数内部有效。
理解作用域的概念对于写出高质量的JavaScript代码至关重要。通过合理地使用全局作用域和局部作用域,你可以提高代码的可读性、可维护性和性能。
1.5.4 变量访问原则 (重点)
在编程中,当我们在代码中访问一个变量时,JavaScript引擎会遵循一套特定的规则来确定这个变量的值。这套规则通常被称为“变量访问原则”或“作用域链”。
作用域链
- 只要是代码,就至少有一个作用域。
- 写在函数内部的代码拥有局部作用域。
- 如果函数中还有函数,那么在这个作用域中就又可以诞生一个子作用域。
访问原则
在能够访问到的情况下,先局部,局部没有再找全局。具体来说:
- 局部作用域优先:当在当前作用域中查找一个变量时,如果找到了,就直接使用。不会再往上一层作用域去查找。
- 逐级向上查找:如果在当前作用域中没有找到所需的变量,引擎就会逐级向上查找,直到全局作用域。
- 全局作用域中的变量总是可用的:在任何作用域中都可以访问全局作用域中的变量。但是,如果在局部作用域中有一个与全局变量同名的变量,那么局部变量将会“遮蔽”全局变量。
案例解析
给出的案例中:
function f1() {
let num = 123;
function f2() {
console.log(num); // ①
}
f2();
}
let num = 456;
f1();
console.log(num); // ②
- 在①处,
f2
函数试图访问num
变量。由于f2
有自己的作用域,并且在这个作用域中没有找到num
,所以它会去父级作用域(也就是f1
的作用域)中查找。在f1
的作用域中找到了num
,值为123,所以输出123。 - 在②处,我们在全局作用域中访问
num
变量。全局作用域中确实有一个num
变量,值为456,所以输出456。
因此,最终输出为:123 456。这验证了“优先局部作用域”的原则。
1.6 匿名函数
在JavaScript中,函数可以有两种形式:具名函数和匿名函数。
1.6.1 具名函数
具名函数有一个明确的名称,可以通过这个名称来调用该函数。例如:
// 声明
function greeting() {
console.log('Hello, world!');
}
// 调用
greeting();
1.6.2 匿名函数
匿名函数没有名称。由于其没有名称,所以不能直接调用。例如:
function () {
console.log('This is an anonymous function.');
}
1.6.3 匿名函数使用方式
①函数表达式
匿名函数通常用作函数表达式的一部分,可以赋值给一个变量,或者作为参数传递给其他函数。这样,虽然函数本身没有名字,但是可以通过变量名来调用它。例如:
var greet = function() {
console.log('Hello, world!');
};
greet(); // 通过变量名调用匿名函数
②立即执行函数
立即执行函数是一种特殊的函数表达式,它在定义后立即被调用,然后返回一个值。这种模式在JavaScript中经常被用来创建一个隔离的作用域,防止变量污染全局作用域。例如:
(function() {
console.log('This function will run immediately!');
})(); // 立即执行匿名函数
在这个例子中,匿名函数被包裹在括号中,然后后面跟着一对括号来立即调用它。这种模式确保了函数只执行一次,并且创建了一个局部作用域,使得在其中定义的变量不会泄漏到全局作用域中。
案例:转换时间案例
需求: 用户输入秒数,可以自动转换为时分秒
let second = +prompt("请输入秒数:");
function getTime(t) {
let h = parseInt((t / 60 / 60) % 24);
let m = parseInt((t / 60) % 60);
let s = parseInt(t % 60);
h = h < 10 ? "0" + h : h;
m = m < 10 ? "0" + m : m;
s = s < 10 ? "0" + s : s;
// console.log(h, m, s)
return `转换完毕之后是${h}小时${m}分${s}秒`;
}
let str = getTime(second);
document.write(str);
1.7 逻辑终端
在JavaScript中,逻辑终端(Short-circuiting)是逻辑运算符&&
(逻辑与)和||
(逻辑或)的一种行为,当运算符左侧的表达式能够决定整个逻辑表达式的结果时,右侧的表达式将不会被执行。
1.7.1 短路原理
- 在使用
&&
运算符时,如果左侧的表达式为false
,则整个表达式的结果必定为false
,因此右侧的表达式将不会被执行。 - 在使用
||
运算符时,如果左侧的表达式为true
,则整个表达式的结果必定为true
,因此右侧的表达式也将不会被执行。
1.7.2 运算结果
无论是 &&
还是 ||
,运算结果都是最后被执行的表达式值。这一特性在变量赋值等场景中非常有用。
1.8 转为boolean类型
在JavaScript中,可以使用显式转换和隐式转换将其他类型的数据转换为布尔类型(true
或 false
)。
1.8.1 显示转换
可以使用 Boolean()
函数进行显式转换。以下值在转换为布尔类型时将被转换为 false
:
0
undefined
null
false
NaN
其余所有值都将被转换为 true
。
1.8.2 隐式转换
在某些情况下,JavaScript会自动将数据转换为布尔类型,例如:
- 当使用
+
运算符进行字符串拼接时,数字会被转换为字符串。例如:"" + 1
的结果是字符串"1"
。 - 当使用
-
运算符进行减法运算时,空字符串""
会被转换为数字0
。 null
在经过数字转换之后会变成0
。undefined
在经过数字转换之后会变成NaN
(非数字)。