JS函数

156 阅读7分钟

函数的概念

函数:函数就是封装了一段可被重复调用执行的代码块,通过此代码块可以实现大量代码的重复使用

函数的封装:把一个或者多个功能通过函数的方式封装起来,对外只提供一个简单的函数接口

具名函数:

function 函数名(形式参数1,形式参数2){
        语句
        return 返回值
    }
    
    
funciton fun(x,y){
    return x+y
}

匿名函数:上面的函数名去掉成为匿名函数

let a = function(x,y){
    return x+y
}

箭头函数

箭头函数定义

ES6中新增的定义函数方式,用来简化函数定义语法的

let fn = () => {
    console.log(123);
}
fn();

如果形参只有一个,可以省略小括号

let fn = x => x*x

如果函数体中只有一句代码,且代码的执行结果就是返回值,可以省略大括号

let fn = (x,y) => x+y // 圆括号不能省略
let fn = (x,y) => {return x+y} // 花括号不能省略
let fn = (x,y) => ({name: x, age: y}) // 直接返回对象会报错,需要加个小括号

箭头函数不绑定this关键字

箭头函数不绑定this关键字,箭头函数中的this,指向的是函数定义位置的上下文this

function fn() {
        console.log(this);
        return () => {
          console.log(this);
        };
      }
      const obj = { name: "张三" };
      const resFn = fn.call(obj);
      resFn();

函数调用

let fn() => console.log('hi')
fn; // fn没执行,不会输出结果
let fn() => console.log('hi')
fn(); // 打印出hi,有圆括号才是调用
let fn() => console.log('hi')
let fn2 = fn
fn2();

结果:

  • fn 保存了匿名函数的地址
  • 这个地址被复制给了fn2
  • fn2调用了匿名函数
  • fn和fn2都是匿名函数的引用而已
  • 真正的函数既不是fn也不是fn2

函数的使用

1. 声明函数

  • function声明函数关键字,全部小写
  • 函数是做某件事情,函数名一般是动词,sayHi
  • 函数不调用自己不执行
function sayHi() {
        return hi~;
      }

2. 调用函数

函数名();

sayHi(); 

3. 函数的调用时机

当一个函数被写好以后,并不会马上执行,而是等待被调用,所以什么时候被调用就决定了函数的输出结果是什么 eg1:

let a = 1;
function fn() {
  console.log(a);
}
fn(); // 这时候会在控制台打印出数字1

eg2:

let a = 1;
function fn() {
  console.log(a);
}
a = 2;
fn(); // 这时候会在控制台打印出数字2,因为fn()的调用时间在a=2之后,这时候 a 已经被重新赋值为 2 了,所以输出结果是 2,如果这里fn()调用在a=2之前,则会输出 1

eg3:

let a = 1;
function fn() {
  setTimeout(() => {
    console.log(a);
  }, 0);
}

fn();
a = 2; // 这个代码会输出2,因为 setTimeout()这个函数的意思是等一会执行,所以当你调用 fn()的时候并不会马上输出 a,而是继续执行a=2,然后才执行console.log(a)

eg4:

let i = 0;
for (i = 0; i < 6; i++) {
  setTimeout(() => {
    console.log(i);
  }, 0);
}

// 以上代码的输出结果是 6 个 6,是不是跟你预期的 0-5 有出入 原因是setTimeout()函数回调属于异步任务,会出现在宏任务队列中,被压到了任务队列的最后,在这段代码应该是for循环这个同步任务执行完成后才会轮到它 简单来说当循环执行到setTimeout()的时候并不会马上打印出 i,而是会记下一个事件,”我等下要把i打一次“,然后会继续执行循环, 当 for 循环结束的时候,这时候 i 的值是 6,然后setTimeout()开始执行,因为刚才循环了 6 次,所以会将这个语句也执行 6 次,这时候会把i的值打印出来,但是这时候i=6,所以他就连续打了 6 个 6,是不是 666666。 但是这种性质其实对新人非常不友好,因为他是反直觉的,所以在最新版的 JS 语法中,做了一个微调

eg5:

for (let i = 0; i < 6; i++) {
  setTimeout(() => {
    console.log(i);
  }, 0);
} 

// 将let i=0放到 for 的条件里之后,情况发生了改变,这时候的输出结果就是一般人认为的,0-5 的输出 6 个数字

其实这个问题还有另一种解决方法,那就是立即执行函数和闭包

for (let i = 0; i < 6; i++) {
  (function fn(i){setTimeout(() => {
    console.log(i);
  }, 0);})(i)
} // 在闭包函数内部形成局部作用域,不受外界变量变化的影响

4. 函数的作用域

每个函数都会默认创建一个作用域 eg1:

  function fn() {
        let a = 1;
      }
      fn();
      console.log(a); // a不存在,访问不到作用域里面的a
  1. 闭包:如果一个函数用到了外部的变量,那么这个函数加这个变量就叫做闭包
 function f1() {
        let a = 1;
        function f2() {
          let a = 2;
          function f3() {
            console.log(a);
          }
            a = 22;
            f3();
          }
          console.log(a);
          a = 100;
          f2();
        }
        f1();

a和f3组成了闭包

函数的参数

  • 形参:形式上的参数,函数定义的时候,传递参数,当前并不知道是什么
  function getSum(num1, num2) {  // num1和num2为形参
        return num1 + num2;
      }
      console.log(getSum(1, 2));
  • 实参:实际的参数,函数调用的时候传递的参数,实参是传给形参的
  function getSum(num1, num2) {  
        return num1 + num2;
      }
      console.log(getSum(1, 2)); // 1,2 为实参

函数的参数可以有,也可以没有

  • 如果实参的个数和形参的个数一致,则正常输出结果
getSum(1,2); // 3
  • 如果实参的个数多余形参的个数,会取到形参的个数
getSum(1,2,3); // 3
  • 如果实参的个数小于形参,多余的形参定义为 undefined,最终的结果为NaN
getSum(1); // NaN

建议形参个数和实参个数匹配

函数的返回值

通过return将结果返回给函数的调用者

函数返回值格式

function 函数名(){
    return 需要返回的结果
}
函数名();

求一组数组中的最大值

function getArrayMax(arr) {
        let max = arr[0];
        for (let i = 1; i <= arr.length; i++) {
          if (arr[i] > max) {
            max = arr[i];
          }
        }
        return max;
      }

      let re = getArrayMax([1, 2, 3]);
      console.log(re);

函数返回值注意事项

  • return 终止函数,后面的代码不会执行
  • return 只能返回一个值,如果用逗号隔开多个值,以最后一个为准。如果要输出多个值,可以借助数组来实现
  • 函数都有返回值,且只有函数有返回值,函数如果没有return,返回undefined

arguments的使用

当我们不确定有多少个参数传递的时候,可以用arguments来获取,arguments是当前函数的内置对象,arguments存储了传递的所有实参

 function fun() {
        console.log(arguments);
      }
      fun(1, 2, 3);
      
//

Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
0: 1
1: 2
2: 3
callee: ƒ fun()
length: 3
Symbol(Symbol.iterator): ƒ values()
__proto__: Object

arguments的展示形式是一个伪数组,因此可以进行遍历。伪数组具有以下特点:

  • 具有数组的length属性
  • 按索引方式存储数据
  • 不具有数组的push,pop等方法

函数案例

利用函数求任意个数的最大值

    function getMax() {
        let max = arguments[0];
        for (let i = 1; i < arguments.length; i++) {
          if (arguments[i] > max) {
            max = arguments[i];
          }
        }
        return max;
      }
      console.log(getMax(1, 2, 3,4,5));

利用函数翻转任意数组

 function reverse(arr) {
        let newArr = [];
        for (let i = arr.length - 1; i >= 0; i--) {
          newArr[newArr.length] = arr[i];
        }
        return newArr;
      }
      let arr1 = reverse([1, 2, 3, 4, 5]);
      console.log(arr1);

利用函数封装对数组进行排序-冒泡排序

     function sort(arr) {
        for (let i = 0; i < arr.length - 1; i++) {
          for (let j = 0; j < arr.length - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
              let temp = arr[j];
              arr[j] = arr[j + 1];
              arr[j + 1] = temp;
            }
          }
        }
        return arr;
      }
      let arr1 = sort([1, 3, 6, 4, 8, 5]);
      console.log(arr1);

函数之间可以相互调用

输入年份,输出当前年份的2月份天数

  function backDay() {
        let year = prompt("请输入年份:");
        if (isRunYear(year)) {
          alert("当前年份是闰年2月份有29天");
        } else {
          alert("当前年份是平年2月份有28天");
        }
      }
      backDay();
      function isRunYear(year) {
        //如果是闰年返回true,不是则返回falselet flag = false;
        if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
          flag = true;
        }
        return flag;
      }