函数

167 阅读11分钟

笔记来源:拉勾教育 - 大前端就业集训营

文章内容:学习过程中的笔记、感悟、和经验

函数

概念

也叫功能、方法,可以将一段代码封装起来,这样这段封装的代码就有了某一功能,函数内部是一个完整的结构体,要么都执行要么都不执行

函数作用就是封装一段代码,将来可以通过调用这个函数反复使用这段代码

函数声明和调用

声明

又叫做函数定义,函数想要使用必须先定义才行,如果没有声明直接调用,会出现错误

书写方法:function 函数名(参数) { 结构体 }

函数定义的时候结构体并不会执行,只有函数被调用的时候才会执行结构体

函数名命名规则和变量相同

//定义函数
function fun(a) {
  console.log(a);
}

调用

书写方法:函数名(参数);

特点:

  • 函数调用也叫函数执行,调用的时候会将内部结构体全部立即执行
  • 函数内部语句执行的位置,与函数定义的位置无关,只与函数调用的位置有关
  • 函数定义后可以多次多次调用执行
//定义函数
function fun(a) {
  console.log(a);
}

//调用函数
fun('我调用了函数');
//多次调用
fun('我调用了函数');
fun('我调用了函数');
fun('我调用了函数');

函数的参数

  • 函数执行的结果可能不是相同的,可以根据参数的变化而输出不同的内容
  • 函数的参数可以是任意数量的,甚至没有参数也可以,多个参数使用逗号隔开
//定义函数
function fun(a, b) {
  console.log(a + b);
}

//多次调用
fun(1,2);   //3
fun(1,3);   //4
fun(4,5);   //9
fun(13,2);   //15
fun(12,2);   //14

形参和实参

  • 形参:在函数定过过程中,()内部的参数叫做形式参数,本质是变量,但没有赋值,可以接收实参穿递过来的数据
  • 实参:在函数调用过程中()内部的参数,实参把这个参数赋值给形参,进行数据的传递

传参过程:

在函数调用的时候,实参会将它的数据按照顺序挨个传给每一个形参,在函数执行过程中实际使用的数据也都是实参数据

  • 例如形参(a,b),调用是参数是(1,2),那么在调用的时候会发生赋值,既a=1,b=2,之后使用1和2去参与结构体的执行

优点

  • 不论是自己还是别人封装的函数,我们只需要传递的是什么参数,这个函数的功能是什么就可以了,不需要去关心函数内部结构体是怎么执行的
  • 一般自己或者别人封装的函数都需要有一个api接口说明,告诉别人需要传递哪种类型的参数,函数的功能是什么
//定义函数
//参数:传入两个数字类型的参数
//功能:求取两个数字的和并输出
function fun(a, b) {
  console.log(a + b);
}

函数返回值

利用函数内部的关键字return设置函数的返回值,返回值只是一个值,并不能直接输出,可以使用console.log()

作用:

函数内部如果出现return,那么return后面的代码都不会执行,会立即停止结构体

在return后面加空格后面可以定义任何字面量或者表达式,函数执行结束后会输出这个表达式或者字面量的值

//定义函数
//参数:传入两个数字类型的参数
//功能:求取两个数字的和
function fun(a, b) {
  return a + b;
}
fun(1, 2);    //不能输出
console.log(fun(1, 2));   //3

作用

  • 返回值可以当成普通数据参与程序
  • 返回值可以赋值给变量甚至可以当做参数参与其他函数

注意:如果函数没有return或者return后面没有任何值 那么函数的默认返回值就是undefined;

//定义函数
//参数:传入两个数字类型的参数
//功能:求取两个数字的和
function fun(a, b) {
  return a + b;
}

//把返回值赋值给变    var a = fun(1, 2);
console.log(a);    //3

//把返回值赋值给函数的实参
console.log(fun(1, fun(1, 2)));   //4

函数表达式

将函数赋值给一个变量,相当于把函数矮化成一个表达式,期中没有函数名的函数叫做匿名函数

书写方法:var 变量名 = function(){ 结构体 }

执行方式:函数名()

// 函数表达式,注意赋值语句后面的;
var foo = function foo() {
  console.log('函数表达式');
};
// 使用匿名函数定义函数表达式
var fun = function() {
  console.log('使用匿名函数赋值函数表达式')
};
//只能使用变量名调用
foo();
fun();

函数的数据类型

函数是一种单独的数据类型-function(使用typeof检查可以发现),所以函数可以参与其他程序,例如可以把函数当做另一个函数的参数或者返回值

//函数作为其他函数的参数
//这是一个定时器,每1秒执行一次结构体
setInterval(function () {
  console.log(1);
}, 1000);


//函数作为一个函数的返回值
//函数返回一个函数
function fn(a) {
  var b = 1;
  return function () {
    alert(a + b);
  }
}
//返回值赋值给一个变量
var x = fn(3);
//执行这个变量
x();

函数的arguments对象

arguments是函数内部内置的对象,所有函数都有,arguments存储了函数调用的时候的所有实参,是一个伪数组,因此可以进行遍历操作

函数的实参个数和形参个数可能不一致

  • 如果实参比形参少,那么少的个数会被定义为Undefined参与函数的运算
  • 如果实参比形参多,那么正常情况下,函数实际使用的实参个数和形参个数是相同的
//arguments对象
function fn() {
  console.log(arguments);
  console.log(arguments.length);
  for (let i = 0; i < arguments.length; i++) {
    console.log(arguments[i]);
  }
}
fn(1, 2, 3, 4, 56, 7);

案例

定义一个求和函数,如果传入1个参数,返回它自己,如果传入两个参数,返回他们的和,如果传入三个参数,先比较前两个的大小,大的与第三个参数求和返回,如果传入4个及以上,输出错误提示。

function sum(a, b, c) {
  switch (arguments.length) {
    case 1:
      return a;
      break;
    case 2:
      return a + b;
      break
    case 3:
      return a > b ? a + c : b + c;
      break;
    default:
      //控制台报错
      throw new Error('参数个数不能超过三个');
  }
}
console.log(sum(4)); //4
console.log(sum(3, 4)); //7
console.log(sum(3, 4, 2)); //6
console.log(sum(6, 3, 2)); //8
console.log(sum(3, '3', 2)); //32
sum(1, 2, 3, 4, 5);      //控制台报错

函数递归

函数内部调用自己本身么叫做函数递归

注意:如果递归次数太多会出现错误:超出计算机最大计算能力

更多的时候我们会使用递归解决一些数学问题,例如可以输出斐波那契数列的某一项值;

案例:

输出斐波那契数列的某一项值(后面一项的数值等于前面两项的和:1,1,2,3,5,8,13,21,34,55.....)

//案例:输出斐波那契数列某一项的值
// 参数:一个正整数
function feibo(a) {
  if (a === 1 || a === 2) {
    return 1;
  } else {
    // 调用自己
    return feibo(a - 1) + feibo(a - 2);
  }
}
console.log(feibo(1));  //1
console.log(feibo(2));  //1
console.log(feibo(3));  //2
console.log(feibo(4));  //3
console.log(feibo(5));  //5
console.log(feibo(6));  //8
console.log(feibo(60000000));  //报错

作用域

变量可以起作用的范围

如果一个变量在函数内部定义,那么就只能在这个函数内部才能访问到这个变量,函数外部不能访问到这个变量,那么这个变量的作用域就是这个函数

任何一对{}中的结构体都属于一个块,在这其中定义的所有变量在{}外部都是不可见的,我们称之为块级作用域

在es5之前没有块级作用域的概念,只有函数作用域,目前可以认为js没有块级作用域

//全局变量
var b = 13;
// 创建一个函数,并且在函数内部创建一个变量a
function fn() {
  //局部变量
  var a = 1;
  console.log(a);
}
fn();
//在函数外部直接调用a
// console.log(a);   //出现错误  a未定义
console.log(b);   //13

变量类型

  • 局部变量:定义在函数内部的变量,只能在函数内部访问到
  • 全局变量:定义在全局的变量,整个js代码在任何地方都可以访问到,本质上也属于一个局部变量

注意:变量在退出他的作用域之后会被销毁,全局变量在关闭网页后会被销毁

参数和函数作用域

函数的参数作用域只有在函数内部,属于函数的局部变量

函数的作用域类似,如果定义在全局,那就是全局函数,如果定义在其他函数内部,那就是一个局部函数

//创建函数
function fn(b) {
  var a = 1;
  // 在函数内部创建函数
  function inner() {
    console.log(a);
  }
  //只能在函数内部调用函数
  inner();
}

//下面两个外部调用都会报错,因为b和inner不在这个作用域内
// inner();
// console.log(b);
</script>

作用域链

js代码中,默认会有有个全局作用域,但是如果在全局里面新建函数,就会新建一个小作用域,如果函数内部再新建函数,就会在形成一个作用域,以此类推

所有的作用域列出来,从新到旧的作用域会形成一个链式结构,叫做作用域链

遮蔽效应

在程序调用一个变量的时候,优先寻找自己的作用域中有没有这个变量,如果没有会按照作用域从内到外依次向上查找,直到找到这个变量,如果一直没有找到,会输出错误,这种内层变量遮蔽外层变量的效果叫做遮蔽效应

//全局作用域
var a = 1;
//创建函数(作用域)
function fn(b) {
  var a = 2;  //a=2会遮蔽a=1,
  //创建新函数(作用域)
  function inner() {
    // var a = 3;   //a=3会遮蔽a=2,
    console.log(a);   //会从内到外依次寻找a
  }
  inner();
}
fn();   //2(如果a=3没有注释 那么输出结果会是3)

变量不写var的影响

在函数内部想要定义一个变量,如果不加var关键字,相当于定义了一个全局变量,如果全局也有这个变量,那么就会被这个函数影响到,也就是说局部变量可能会污染 全局变量

注意:函数内每次定义变量一定要写var,否则会定义到全局,可能会污染全局

//全局作用域
var a = 1;
//创建函数(作用域)
function fn(b) {
  a = 2;     //不写var会修改全局下的a,造成污染
}

//输出a
console.log(a);   //2

预解析

js代码是由浏览器中的js解析器来执行的,js解析器执行代码的时候,会有预解析、和代码执行两个过程

  • 预解析:
    • 把变量声明的过程提到当前作用域最前面。只会提升声明,不会提升赋值
    • 把函数声明的过程提到当前作用域最前面,只会提升声明,不会提升调用
    • 先提升var,在提升function
  • 代码执行:预解析后,根据新的代码顺序,按照一定的规律执行代码

注意:

  • 在变量提升中,如果前面调用后面的定义变量,不会报错,但会使用undefined的值
  • 函数提升是整体提升到顶部,可以在任意位置调用

提升顺序

  • 先提升var,后提升function,所以如果变量和funtcion是同名的,function会覆盖变量,如果后面又赋值了这个标识符,会出现意想不到的错误
  • 函数表达式提升相当于变量提升中,直接执行会出现undefined

建议:

  • 不要唉书写相同的变量名和函数名,避免出现覆盖错误
  • 建议定义函数式用function关键字定义,这样函数调用就永远生效

应用:函数定义写在最后面,可以用于调整代码顺序,不会影响执行结果

IIFE自调用函数

表示函数呗定义的时候就立即调用

书写方法:函数();

注意:关键字(function)定义的函数并不能直接加()立即调用,但是函数表达式可以,原因是只有在表达式后面加()才能立即执行**

启发:如果把function关键字定义的函数转换成表达式是不是就可以实现立即执行函数?

解决方案:可以在函数前面加一些运算符模拟表达式效果例如:

  • 数学运算符:+、-、()
  • 逻辑运算符:!
//自调用函数(关键字定义)
+ function fn1() {
  console.log('fn1');
}(); -
function fn1() {
  console.log('fn2');
}();
(function fn1() {
  console.log('fn3');
})();
! function fn1() {
  console.log('fn4');
}();

//最常用的书写方法
(function(a) {
  console.log(a);
})(4);               //4

注意事项

自调用函数定义的函数无法在其他位置调用,是一次性函数,所以可以不写函数名

后面常用的IIFE结构是(function(形参){ 结构体})(实参)