1、函数的基础知识
为什么会有函数?
在写代码的时候,有一些常用的代码需要书写很多次,如果直接复制粘贴的话,会造成大量的代码冗余;
函数可以封装一段重复的
javascript代码,它只需要声明一次,就可以多次调用;
冗余代码:
-
冗余:
多余的重复或啰嗦内容 -
缺点:
- 代码重复,可阅读性差
- 不易维护,如果代码逻辑变了,所有地方的代码都要跟着改,效率太低了
2、函数的声明与调用
函数声明的语法:
let是用来声明变量的, 函数是用function来声明的,一个函数一般是用来做一件事情的。
function 函数名 (){
//函数体
}
函数声明的时候,函数体并不会执行,只要当函数被调用的时候才会执行。
调用函数的语法:
函数名();
函数体只有在调用的时候才会执行,调用需要()进行调用。可以调用多次
示例代码:
// 声明函数
function sayHi (){
// 函数体
console.log("Hi!!");
}
// 调用这个函数
sayHi(); // console.log("Hi!!");
// 注意
console.log(sayHi); // 打印的是整个函数
// sayHi:指的就是这个函数
// ():指的是调用
// sayHi():这个函数的调用结果
3、声明函数的两种方式
3.1 函数声明(命名函数):
// 声明一个函数并且命名了
function 函数名(){
函数体;
}
函数名(); // 调用函数
/********示例代码***********/
function fn(){
console.log("哈哈哈");
}
fn();
3.2 函数表达式(匿名函数):
// 必须先声明才能调用
let 函数名 = function(){
函数体;
}
函数名(); // 调用函数
/********示例代码***********/
var fn = function(){
console.log("哈哈哈");
}
fn();
这两种函数的区别:
- 命名函数可以先调用,再声明,因为预解析
- 函数表达式必须先声明,再调用(在
DOM中注册事件的时候用的非常的多)
匿名函数:
没有名字的函数,叫做匿名函数。匿名函数没有办法直接用,需要赋值给变量或者自调用
自调用函数也叫自执行函数,声明和调用一起
- 可以防止变量全局污染
- 匿名函数自调用示例代码:
(function(n1,n2){
console.log(n1); // 1
console.log(n2); // 2
let name = "张三"
let age = 18;
function sayHello() {
console.log(age); // 18
console.log(name); // "张三"
}
sayHello();
})(1,2)
4、函数的参数
形式参数: 在声明一个函数的时候,为了函数的功能更加灵活,有些值是固定不了的,对于这些固定不了的值。我们可以给函数设置参数。这个参数没有具体的值,仅仅起到一个
占位置的作用,我们通常称之为形式参数,也叫形参。实际参数: 如果函数在声明时,设置了行参,那么在函数调用的时候就需要传入对应的参数,我们把传入的参数叫做实际参数,也叫
实参。
语法:
//带参数的函数声明
function 函数名(形参1, 形参2, 形参...){
//函数体
}
//带参数的函数调用
函数名(实参1, 实参2, 实参3);
特点:
- 在函数调用的时候,需要传递对应的参数,把实参的值赋值给形参。
实参如果多于形参的个数:多传的参数就丢弃了实参如果少于形参的个数:没有传的参数,值就是undefined。(容易出问题)
示例代码:
// 设置两个形参
function getSum(num1,num2){
console.log(num1+num2);
}
// 调用的时候传两个值进去
getSum(10,20); // 打印出来就是 30
计算n1-n2之间所有数的乘积:
function getProduct(n1, n2) {
let product = 1;
for (var i = n1; i <= n2; i++) {
product *= i;
}
console.log(product);
}
getProduct(1, 5); // 120
5、函数的返回值
当函数执行完的时候,我们期望函数给我一些反馈(比如计算的结果),这个时候可以让函数返回一些东西。也就是返回值。函数通过
return返回一个返回值
返回值语法:
//声明一个带返回值的函数
function 函数名(形参1, 形参2, 形参...){
//函数体
return 返回值;
}
//可以通过变量来接收这个返回值
let 变量 = 函数名(实参1, 实参2, 实参3);
函数的调用结果就是返回值,因此我们可以直接对函数调用结果进行操作。
示例代码:
// 计算 n1- n2之间所有数的乘积
function getProduct(n1, n2) {
let product = 1;
for (let i = n1; i <= n2; i++) {
product *= i;
}
return product; // 返回计算的值
}
let pro = getProduct(1, 5); // 用变量pro接收一下返回的值
console.log(pro); // 120
注意:
- 函数一碰到
return,就代表函数结束了。return后面的代码不会执行了。 - 函数可以没有返回值, 会在最后面返回一个
undefined。
6、函数三要素
函数三要素包括:
- 函数名
- 参数
- 返回值
7、文档注释
关于文档注释,javascript中还有一种注释叫做文档注释,经常用在函数
声明处,用来解释这个函数的作用。
文档注释: /** 这是文档注释 */
以后写的函数的声明,都应该加上文档注释,方便阅读
示例代码:
/**
* 求圆的面积
* @param r {number} 圆的半径
* @returns {number} 圆的面积
*/
function getArea (r) {
return Math.PI * r * r;
}
8、函数综合练习
8.1 对任意数组从小到大排序
// 封装一个从小到大冒泡排序的函数
function bubbleSort(arr){
for(let i = 0; i < arr.length - 1; i++){
let flag = true;
for(let j = 0; j < arr.length -1 -i; j++){
if(arr[j] >arr[j+1]){
flag =false;
let temp = arr[j];
arr[j] = arr[j+1];
arr[j + 1] = temp;
}
}
if(flag){
break;
}
}
return arr;
}
console.log(bubbleSort([12, 56, 14, 68, 45, 25, 17, 33]));
console.log(bubbleSort([25, 65, 48, 11, 15, 54, 24, 63]));
8.2 求任意数的阶乘(从1到n的积)
function getProduct (n){
let product = 1;
for(let i = 1; i <= n; i++){
product *= i;
}
return product;
}
console.log(getProduct(5)); // 120
console.log(getProduct(3)); // 6
8.3 求任意数组中的最大值与最小值
function getMaxAndMin(arr) {
let max = arr[0];
let min = arr[0];
for (let i = 0; i < arr.length; i++) {
if (max < arr[i]) {
max = arr[i];
}
if (min > arr[i]) {
min = arr[i];
}
}
return [max, min]; // 返回一个数组
}
console.log(getMaxAndMin([11, 45, 59, 12, 8, 36, 14, 25])); // [59 8]
9、函数的作用域
在函数中,只有
全局作用域和函数作用域,因为在if、while、for等语句中定义的变量都是全局变量。
全局变量: 在最外层声明的变量就是全局变量,全局变量在任何地方都能访问的到。
局部变量: 在函数中声明的变量,就是局部变量,局部变量只有在当前函数体内能够访问。
隐式全局变量: 没有使用var定义的变量也是全局变量。
作用域: 变量可以发挥作用的区域
全局作用域: 在script标签内,函数外定义的作用域就是全局作用域。在全局作用域中定义的变量都是全局变量。
函数作用域: 在函数中的区域叫做函数作用域,在函数作用域中定义的变量就是局部变量,只能在当前函数内访问。
10、预解析
js解析器执行js代码的时候,分为两个过程:
预解析过程和代码执行过程
预解析过程:
- 把变量的声明提升到当前作用域的最前面,只会提升声明,不会提升赋值。
- 把函数的声明提升到当前作用域的最前面,只会提升声明,不会提升调用。
- 先提升
var,再提升function
预解析例题:
第一题:
console.log(a); // 打印a这个函数整体
var a = 1;
function a(){
console.log("呵呵");
}
console.log(a); // 1
// 预解析后为
/**
var a;
function a(){
console.log("呵呵");
}
console.log(a); // 打印a这个函数整体
a = 1;
console.log(a); // 1
*/
第二题:
var num = 10;
fn1();
function fn1() {
//在函数调用的时候,这个函数也会做预解析操作。
console.log(num); // undefined
var num = 20;
console.log(num); // 20
}
console.log(num); // 10
// 预解析后为
/**
var num ;
function fn1() {
var num;
console.log(num); // undefined
num = 20;
console.log(num); // 20
}
num = 10;
fn1();
console.log(num); // 10
*/
第三题:
var a = 18;
var b = 30;
fn();
function fn() {
var b = 9;
console.log(a); // undefined
console.log(b); // 9
var a = 20;
}
// 预解析后为
/**
var a;
var b;
function fn() {
var b;
b = 9;
var a;
console.log(a); // 自己作用域里有的就不要出去找
console.log(b); // 9
a = 20;
}
a = 18;
b = 30;
fn();
*/
第四题:
fn();
var b = 10;
console.log(c); // 9
console.log(b); // 10
console.log(a); // 报错
function fn() {
var a = 9;
b = 9;
c = 9;
console.log(a); // 9
console.log(b); // 9
console.log(c); // 9
}
// 预解析之后
/**
var b;
function fn() {
var a;
a = 9;
b = 9;
c = 9;
console.log(a); // 9
console.log(b); // 9
console.log(c); // 9
}
fn();
b = 10;
console.log(c); // 9
console.log(b); // 10
console.log(a); // 报错
*/
第五题:
function fn() {
console.log(num1); // undefined
console.log(num2); // undefined
console.log(num3); // 30
var num1 = 10;
var num2 = 20;
num3 = 40;
console.log(num1); // 10
console.log(num2); // 20
console.log(num3); // 40
}
var num1 = 20;
var num3 = 30;
fn();
console.log(num1); // 20
console.log(num3); // 40
console.log(num2); // 报错
// 预解析之后
/**
var num1;
var num3;
function fn() {
var num1;
var num2;
console.log(num1); // undefined
console.log(num2); // undefined
console.log(num3); // 30
num1 = 10;
num2 = 20;
num3 = 40;
console.log(num1); // 10
console.log(num2); // 20
console.log(num3); // 40
}
num1 = 20;
num3 = 30;
fn();
console.log(num1); // 20
console.log(num3); // 40
console.log(num2); // 报错
*/
11、递归函数
函数直接或者间接调用自己,必须要留
出口,不然就调死了
示例代码:
// 用递归求1-100的和
/*
之前封装过一个getSum的函数比如getSum(100),就是求的1-100的和
现在我们可以这样理解:
1-100的和我们可以看做是 100 + getSum(99)
getSum(99) 可以看成 99 + getSum(98)。。。
依次这样推下去,但是要注意,到getSum(1)的时候,要留出口,否则会一直死循环下去
*/
function getSum(n) {
if (n == 1) { // 一定要留出口
return 1;
}
return n + getSum(n - 1);
}
console.log(getSum(100));
12、回调函数
回调函数:把函数当成参数来使用,那么这个函数就叫回调函数。函数也是一种数据类型
示例代码:
/*
思考,之前封装了一个bubbleSort排序的函数,但是只能排元素是数字的数组
现在想要判断字符串的长度,或者对象的属性的时候就很麻烦,就需要重新写一个函数
比如字符串长度,就需要是arr[j].length - arr[i+1].length
*/
function bubbleSort(arr, fn) {
for (let i = 0; i < arr.length; i++) {
let flag = true;
for (let j = 0; j < arr.length - 1 - i; j++) {
// 传一个函数进来,并且将arr[j], arr[j + 1]作为两个参数传进去
if (fn(arr[j], arr[j + 1]) > 0) {
flag = false;
let temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
if (flag) {
break;
}
}
}
// 纯数字数组
let arr = [4, 3, 1, 6, 22, 21, 41, 4];
// 调用的时候,我们需要将fn函数的两个参数也传进去
// 这种把一个函数作为参数传进另一个函数的方式就叫回调函数
bubbleSort(arr, function(a, b) { // a b 就相当于 arr[j] 和 arr[j+1]
return a - b;
// 如果是 return b - a 就相当于,上面的 arr[j+1] - arr[j]>0 那么就是从大到小排序
});
console.log(arr);
// 封装后的排序函数也可以直接根据字符串的长度进行排序了
let arrStr = ["aaa", "bb", "cccc", "d"];
bubbleSort(arrStr, function(a, b) {
// 因为传进去的是一个函数,arr[j] 和 arr[j+1]是以两个参数的形式传进去的
// 当数组元素是字符串的时候,就可以进行.length操作了
return a.length - b.length;
});
console.log(arrStr);