函数
函数就是功能,封装一些语句放在函数内部,函数就具有了某一特定的功能
声明:
function 函数名() {}
调用:
函数名();
函数基础
1. 函数的声明和调用
函数声明:定义函数
关键字:function
语法:
function 函数名(参数) { 结构体 }
调用:
函数名();
注
- 函数名必须遵循标示符定义规则
- 函数只能先声明,才能够调用
- 函数声明时,内部的语句不会执行
- 函数执行的位置与声明的位置无关,取决于调用的位置
- 函数调用语法:函数名(实参);
- 函数可以多次调用
function fun() {
console.log(10);
}
console.log(1);
fun();
// 1
// 10
2. 函数的参数
函数可以帮我们封装一些代码,代码可以穿重复调用,函数留了一个接口,就是我们的参数,可以通过参数的变化让我们的函数发生不同的作用。
参数就是变量:命名规则与变量一样
形参
:函数定义时,小括号里的参数叫做形式参数
实参
:函数调用时,小括号的参数叫实际参数
传参
:函数执行时,把实际参数传递到形式参数的过程
JavaScript 是一个动态类型数据语言,变量的类型会根据里面存放的内容而变化。实参的数据类型会影响形参的数据类型,导致函数输出结构发生变化。
function sum(a, b) {
console.log(a + b);
}
sum(1, 2);
sum(1, '2');
sum(5); // 5 + undefined
// 12 number
// 12 string
// NaN
3. 函数返回值 return
函数可以使用 return
语句接受参数进行操作,return
只是返回值,不会输出。
在函数中执行到
return
关键字,会立即停止执行,直接返回
function sum(a, b) {
return a + b;
console.log(5); //不执行
}
console.log(sum(1, 2));
// 3
4. 模块化编程案例
案例1:判断1000以内得质数
思路:判断一个数是不是质数 ===》 找这个数得约数个数
function YSGS(a) {
var sum = 0;
for (var i = 0; i <= a; i++) {
if (a % i === 0) {
sum ++;
}
}
return sum;
}
function isZS(b) {
return YSGS(b) === 2 ? true : false;
}
for (let i = 2; i < 1000; i++) {
let msg = isZS(i) === true ? '是质数' : '不是质数'
console.log(i + msg);
}
案例2:1-1000中得完美数
思路:判断一个数是不是完美数数 ===》 约束和等于本身
function YSH(a) {
let sum = 0;
for (let i = 1; i < a; i++) {
if (a % i === 0) {
sum += i;
}
}
return sum;
}
function isWMS(a) {
return YSH(a) === a ? true : false;
}
function WMS(total) {
for (let i = 2; i < total; i++) {
if (isWMS(i)) {
console.log(i + '是完美数');
}
}
}
WMS(10000);
// 6是完美数
// 28是完美数
// 496是完美数
/// 8128是完美数
5. 函数表达式
函数定义可以使用关键字 function
来声明,也可以使用变量定义函数,称之为匿名函数
var sum = function(a, b) {
return a + b;
};
console.log(sum(1, 2));
// 3
6. 函数的数据类型
-
简单数据类型
var a = 3; var b = a; a = 4; console.log(a); console.log(b); // 4 // 3
-
引用数据类型
引用数据类型传递的是地址,之间相互影响,详情参考
var fun1 = function () { console.log(1); } var fun2 = fun1 fun2.haha = 10; console.log(fun1.haha); // 10
7. 函数声明的提升
-
变量声明得提升:先引用,后定义,只提升定义,不提升赋值
-
函数声明得提升:先调用,后声明,只提升函数命,不提升定义,函数名指向函数得地址,所以调用函数时,函数内部得语句也会执行
根据这个特性,我们一般会先调用函数,在最后声明函数。
fun();
function fun() {
console.log(10);
}
// 10
当函数和变量重名时
如果变量有值,那么输出的就是变量的值。
如果变量没有值,那么输出的就是这个函数。
var sum = 5;
function sum() {
console.log(3);
}
console.log(sum);
var num;
function num() {
console.log(3);
}
console.log(num);
console.log(a);
var a = 5;
function a() {
console.log(3);
}
// 5
// [Function: num]
// [Function: a]
当关键字声明函数和函数表达式重名时,关键字声明会覆盖函数表达式
foo();
function foo() {
console.log(5);
}
var foo = function () {
console.log(3)
}
// 5
函数进阶
1. 递归函数
定义
在一个函数内部通过名字调用自身函数
一个函数可以调用自身,这中现场叫做
递归
应用
递归函数常用来处理一些数学问题
例如:斐波那契数列:1 1 2 3 5 8 13 21 34
// 用一个函数求斐波那契数列的任意项
function feibo(n) {
for (let i = 0; i < n; i++) {
if (n === 1 || n === 2) {
return 1;
} else {
return feibo(n-1) + feibo(n-2)
}
}
}
console.log(feibo(9));
// 34
2. 变量的作用域
函数内部中使用关键字 var
定义的变量(函数作用域),只能在函数内部使用,不能在函数外部取得。
function fun() {
var num = 1;
console.log(num);
// 1
}
console.log(num);
// num is not defined
3. 局部变量和全局变量
局部变量:在作用域(定义域)内定义的变量就是这个作用域的局部变量,只能在作用域内被访问到。
全局变量:全局变量定义在全局,可以在任何地方访问到。
变量声明原理:全局变量,在全局定义后会永久存在,任何时候任何位置都能访问;局部变量定义在函数内部,函数定义的过程并没有去定义这个局部变量,只有在函数执行的时,才会立即定义这个局部变量,执行完之后,变量回立即销毁,在其他地方访问时,找不到这个变量,所以报错该变量未定义。
4. 作用域链
作用域链指的是变量在查找的规律:在不同的作用域内使用相同的标识符去命名变量。若当前作用域内有这个变量,则直接使用;若没有,则会一层一层的从本层向外层依次查找,使用查找到的第一个(就近原则)
var num = 1;
function fun1() {
var num = 2;
function fun2() {
var num = 3;
console.log(num);
// 本层中有定义直接输出 3
function fun3() {
console.log(num);
// 本层没有定义,从本层出发依次向外查找,得到上层的 3
}
fun3();
}
fun2();
}
fun1();
console.log(num);
// 输出全局的 1
5. 形参是局部变量
function fun(a) {
console.log(a);
// 1
}
fun(1);
console.log(a);
// a is not defined
6. 全局变量的作用
- 传递:全局变量可以在不同函数间通信(信号量)
var num = 1;
function jia() {
console.log(num++);
}
function jian() {
console.log(num--);
}
jia(); // 2
jia(); // 3
jian(); // 2
jian(); // 1
jia(); // 2
- 同意函数不同调用
// 全局变量,不会让变量重置或者清空
var num = 1;
function plus() {
num += 4;
console.log(num++);
}
plus(); // 5
plus(); // 9
plus(); // 13
7. 函数作用域
函数作用域与变量类似,只能在函数声明的地方使用,外部的任何地方都访问不到
function outer() {
function inner() {
console.log(1);
}
inner();
}
outer();
// 1
inner();
// inner is not defined
8. 闭包
体会闭包
function outer() {
var num = 1;
function inner() {
console.log(num);
}
// inner 没有括号表示只输出inner函数的定义,不会立即执行
return inner;
}
console.log(outer());
/*
ƒ inner() {
console.log(num);
}
*/
var fun = outer();
// fun相当于inner函数的定义(地址)
fun();
// 1
// 本层没有num的定义,但是inner存在作用域链,所以输出1
闭包
:函数把自己的内部语句
和自己声明所出的作用域
封装成一个密闭的环境
函数本身就是一个闭包。函数在定义的时候,能够记住他的外部环境和内部语句,每次执行都会参考定义时的密闭环境。
案例1
function outer(x) {
function inner(y) {
console.log(x + y);
}
return inner;
}
var fun = outer(3);
/*
此时fun = function inner(y) {
console.log(3 + y);
}
*/
fun(5); // 8
fun(8); // 11
案例2
function outer(x, y) {
function inner(x) {
console.log(x + y);
}
return inner;
}
var fun = outer(2, 3);
/*
inner的外部环境为
x = 2;
y = 3;
fun = function inner(y) {
此时x为传入的参数可以直接使用,
x在该作用域不存在找到上层 y = 3;
console.log(x + 3);
}
*/
fun(5); // 8
fun(7); // 10
案例3
函数的闭包,记住了定义时所在的作用域,这个作用域中的变量不是一成不变的
function outer() {
var num = 1;
function inner() {
return num++;
}
return inner;
}
var fun = outer();
/*
inner的外部环境为
num = 1;
fun = function inner() {
num在该作用域不存在找到上层 num = 1;
return num++;
}
*/
console.log(fun());
// 1
console.log(fun());
// 2
console.log(fun());
// 3
案例4
每次调用一个函数,都会产生一个新得闭包,新的闭包,语句全新,外部环境也是新的
function outer() {
var num = 1;
function inner() {
return num++;
}
return inner;
}
var fun1 = outer();
var fun2 = outer();
/*
inner的外部环境为 num = 1;
fun1 = function inner() {
num在该作用域不存在找到上层 num = 1;
return num++;
}
fun1 = function inner() {
num在该作用域不存在找到上层 num = 1;
return num++;
}
*/
console.log(fun1());
// 1
console.log(fun1());
// 2
console.log(fun2());
// 1
console.log(fun2());
// 2