函数概念
函数(function)又叫做 功能、方法。函数可以将一段代码封装起来,被封装起来的函数具备某一项特殊功能,函数内部封装的代码作为一个完整的结构体,要执行就都执行,不执行就都不执行。
函数的 作用 就是用来封装一段代码,便于将来可以 重复使用。
函数是被设计执行特定任务的代码块。只有被调用时才会执行。
函数声明
函数声明又叫做函数定义。
函数必须先定义才能被调用。如果直接调用未定义的函数,则返回 undefined。
函数声明语法:
function 函数名(参数:形参,也可以不写) {
封装的结构体;
}
特点: 函数之声明的时候,函数体并不会执行,只有当函数被调用时才会执行。
函数名 命名规范: 可以使用字母、数字、下划线_、$ 。
PS:与变量命名规范相同。不能以数字开头,区分大小写,不能使用关键字和保留字。
函数数据类型
函数数据类型是一种单独的数据类型 Function。
由于函数是一种数据类型,可以参与其他程序。可以把函数当做听一个函数的参数,在另一个函数中调用;或者可以把函数作为返回值从函数内部返回。
// 将函数当成其他函数的参数
setInterval(function () {
console.log("每隔1秒输出一次");
}, 1000);
// 将函数当成另一个函数的返回值
function fun(a) {
var b = 3;
return function () {
alert(a + b);
}
}
函数调用
调用方法: 函数名();
函数调用 也叫作函数执行,调用时会立即执行内部结构体。
函数内部结构执行的位置,与函数定义的位置无关,与函数调用的位置有关。
函数可以一次定义,多次调用执行。
函数中的代码将在其他代码调用该函数时执行:
- 当事件发生时(当用户点击按钮时)
- 当 JavaScript 代码调用时
- 自动的(自调用)
// 函数声明
function fun() {
// 结构体
var i = 1;
var j = 2;
console.log(i + j);
}
// 函数调用
fun(); // 3
函数接口
接口: 就是函数的参数,函数参数的本质就是变量。可以接受任意类型的数据,导致执行结果根据参数不同,结果也不同。
一个函数可以设置 0 到多个参数,参数之间用逗号 , 隔开。
传参: 所有函数的参数都是按值传递的,也就是说把函数外部的值复制给函数内部的参数。
函数的参数根据书写的位置不同,名称也不同:
-
形式参数: 函数定义的()内部的参数,叫做形式参数,本质是变量,可以接受实际参数传递过来的数据。 简称
形参。 -
实际参数: 调用的()内部的参数,叫做实际参数,本质就是传递各种数据类型的数据,传递给每个形参。简称
实参。
传参过程: 实际参数复制给形参(接受数据的变量)的过程,就是 传参过程。
// 定义函数
function sum(a, b) {
console.log(a + b);
}
// 调用并传参
sum(3,7); // 10
sum("haha",7); // haha7
函数返回值
函数能通过参数接收数据,也能将函数执行结果返回一个值。
通过函数内部 return 关键字设置函数的返回值。
作用:
-
函数结构体执行到
return关键字时,会立即停止后面代码的执行。 -
可以在
return后面添加空格,后面任意一个数据字面量或者表达式。
函数在执行完自身功能后,整体会被 return 矮化成一个表达式,表达式必须求出一个值可以继续参与程序,表达式的值就是 return 后面的数据。
实例:
// 返回值
function sum(a, b) {
return a + b;
console.log("执行了吗"); // return后面的代码不会被执行
}
// 调用函数并传参
console.log(sum(2, 3)); // 5
// 将返回值赋值给变量
var num = sum(5, 5);
console.log(num); // 10
// 将返回值赋值给实参
console.log(sum(6, sum(6, 6))); // 18
如果函数没有设置 return 语句,那么函数默认的返回值是 undefined;
如果函数使用了 return 语句,但是后面没有跟任意一个数据字面量或者表达式,那么函数默认的返回值也是 undefined。
函数表达式
函数表达式 是函数定义的一种方式。
定义方法: 将函数的定义、匿名函数赋值给一个变量。 相当于把函数矮化成一个表达式。
匿名函数: 没有函数名的函数。
调用函数表达式: 变量名()
实例:
// 定义一个函数表达式:将函数 fun 赋值给变量 foo
var foo = function fun() {
console.log("A");
};
// 将匿名函数赋值给变量 foo2
var foo2 = function () {
console.log("B");
};
// 函数表达式 不能使用函数名调用
fun(); // 报错:fun is not defined
// 函数表达式只能用变量名调用
foo(); // A
foo2(); // B
arguments 对象
arguments 是 js 中当前函数的一个内置对象,会接收所有的实参。
arguments 是一个伪数组,因此可以进行遍历。
函数的实参个数和形参个数可以不一致,所有的实参都会储存在函数内部的 arguments 伪数组对象中。
// arguments 应用案例
// 定义求和函数,传入一个参数返回这个参数;传入两个参数,返回两数之和;传入三个参数,比较前两个大小,返回大的值与第三个值的和
function sum(){
// 条件分支语句,根据实参个数走不同分支
switch (arguments.length) {
case 1:
return arguments[0];
break;
case 2:
return arguments[0] + arguments[1];
break;
case 3:
return arguments[0] > arguments[1] ? arguments[0] + arguments[2] : arguments[1] + arguments[2];
break;
default:
throw new Error("输入参数个数不能超过3个");
}
}
// 调用函数
console.log(sum(0,2,3));
函数递归
函数内部可以通过函数名调用自身的方法,就是 函数递归 现象。
如果递归次数太多超出计算机计算能力,容易报错。
更多的时候使用递归解决一些数学中的现象。
递归的优势: 代码结构简单、代码量少、阅读方便、维护简单。
递归的缺陷和不足: 递归的计算量非常大,而且存在重复计算的可能性。
实例:
// 实现递归
// 函数如果 传入的参数是1,返回1;如果传入的是1以上的数字,返回参数与 这个函数调用上一项的和
function sum(a) {
if (a === 1) {
return 1;
} else {
return a + sum(a - 1);
}
}
// 调用函数
console.log(sum(1)); // 1
console.log(sum(2)); // 3
console.log(sum(6)); // 21
小案例:递归实现 斐波那契
// 递归实现 斐波那契
function fib(n) {
if (n === 1 || n === 2) {
return 1;
} else {
return fib(n - 1) + fib(n - 2);
}
}
// 调用函数
console.log(fib(1)); // 1
console.log(fib(2)); // 1
console.log(fib(3)); // 2
console.log(fib(4)); // 3
console.log(fib(5)); // 5
console.log(fib(6)); // 8
console.log(fib(7)); // 13
console.log(fib(8)); // 21
作用域
作用域:函数可以起到作用的范围。
如果变量定义在函数内部,就只能在函数内部被访问到,在函数外部不能使用这个变量,函数就是变量定义的作用域。
任何一对花括号 {} 中的结构体都属于一个块,在这这之中定义的所有变量在代码块外都是不可见的,称之为会计作用域。
在es6之前没有块级作用域的概念,只有函数作用域。
根据变量定义的位置不同区分:全局变量和局部变量
-
全局变量: 定义在全局的变量,作用域范围是全局,在整个js程序中任意位置都能够被访问到。广义上来说也是一种局部变量。
-
局部变量: 定义在函数内部的变量,只能在函数作用域内部被访问到。
// 作用域:全局变量 和 局部变量
// 定义全局变量就可以在外部调用变量 a
var a = 5;
function fun() {
var a = 1;
console.log(a);
}
// 调用函数 输出局部变量 a
fun(); // 1
// 在外部调用变量 a
console.log(a); // 报错:a 未定义 === a is not defined
- 变量退出作用域之后会被销毁,全局变量关闭网页或关闭浏览器才会被销毁。
函数的参数 实际也是变量,其作用域属于函数内部的局部变量,在函数外部是无法被调用的。
// 函数的参数 也是局部变量
function fun(a) {
a = 6;
console.log(a);
}
fun(3); // 6
// 如果 调用函数内部的变量 a 报错,说明函数内部变量也是局部变量
console.log(a); // 报错:a 未定义 === a is not defined
函数的作用域 函数也有自己的作用域
// 函数作用域
function outer() {
console.log("外部函数");
function inner() {
console.log("内部函数");
}
// 父函数内部定义一个子函数,这个子函数只能在父函数内部调用
inner(); // 内部函数
}
outer(); // 外部函数
inner(); // a is not defined
作用域链和遮蔽效应
作用域链
只有函数可以制造作用域结构,只要是代码就至少有一个作用域,既全局作用域。
凡是代码中的函数,这个函数就构成了另一个作用域。如果函数中还有函数,那么在这个作用域中就又产生一个新的作用域。
将这样的所有的作用域列出来,可以有一个结构:由函数内指向函数外的链式结构。就称之为 作用域链。
遮蔽效应
程序在遇到一个变量时,会按照自己所在的作用域查找,如果当前作用域没有变量定义会按照顺序从本层一次向外层查找,直到找到第一个变量定义。不同层次的作用域可能出现命名相同的变量,程序依次查找的过程中会发生内层变量遮蔽外层变量的效果,叫做 遮蔽效应。
var a = "全局变量";
function outer() {
var a = "outer函数";
function inner() {
var a = "inner函数";
// 程序要执行输出一个 a ,那么它会从 a 自己所在的作用域查找,如果没有就向外一层查找,没有就再外一层查找,直到有结果输出,如果到全局还没有找到就报错:a is not defined
console.log(a);
}
inner();
}
outer();
var 关键字
在内部函数定义新的变量,如果不写var关键字,相当于定义了全局变量。如果全局也有相同的标识符,会被函数内部变量影响,局部变量污染全局变量。
JS 预解析
JavaScript 代码的执行是由浏览器中JavaScript解析器也就是js引擎来执行的。JavaScript引擎执行js代码时,分为两个过程:预解析 过程、代码执行 过程
预解析:
- 把变量的声明提升到当前作用域的最前面,只提升声明不提升赋值。变量声明提升后默认值 undefined。
- 把函数的声明提升到当前作用域的最前面,值提升声明不提升调用。
- 先提升 var ,再提升 function 。变量在作用域最前面,函数在变量之后。
- 不要书写相同的标识符给变量名或函数名,避免出现覆盖情况。
变量声明提升
// 变量 预解析
var a; // 相当于js引擎在预解析过程中把 a 变量提升到作用域最前面并赋值undefined未定义的值。
console.log(a); // undefined 程序走到这里以为在预解析时只找到未定义变量a,所以输出undefined
var a = 1;
函数声明提升
// 函数 预解析
// 在函数之前调用函数,也可以正常执行函数。预解析后函数声明放到了作用域前面第二的位置,在除变量声明外所有代码之上,所以才会执行。
fun(); // 111
function fun() {
console.log(111);
}
// 正常调用函数
fun(); // 111
函数表达式提升
在预解析过程中,函数表达式进行的是 变量声明提升,而不是函数声明提升。提升后变量内存的是一个undefined,在前面进行函数方法调用,数据类型会提示错误。所以定义函数时,应该使用function关键字,这样函数声明提升永远生效。
代码执行:
在预解析之后,根据新的代码顺序,从上到下按照既定规律执行。
IIFE 自调用函数
IIFE: 及时调用的函数表达式,也叫作自调用函数,表示函数在定义时就立即调用。
函数调用方式: 函数名 或 函数表达式的变量名 后面跟 () 运算符。
-
函数自调用其实就是将函数矮化成一个表达式就可以实现自调用。
-
函数矮化成表达式的方法,可以让函数参与一些运算,就是给函数前面加一些运算符。
- 数学运算符:+ - ()
- 逻辑运算符:!
-
IIFE 结构可以关住函数的作用域,在结构外面是不能调用函数的。
-
IIFE 最常用的是 () 运算符,而且函数可以不写函数名,使用匿名函数自调用。
使用 () 运算自调用
// 使用 () 运算符声明匿名函数自调用
(function (a) {
console.log(a);
})("运算符 ()"); // 运算符 ()
函数表达式自调用
// 使用函数表达式自调用
var fun = function foo() {
console.log("函数表达式自调用");
}(); // 函数表达式自调用
注意:不能用function关键字声明函数,并自调用执行函数
// 使用function关键字声明函数,并自调用执行函数。书写错误
function fun() {
console.log("function关键字声明");
}(); // 报错