一、函数
1.1 函数的声明
函数就是功能。
我们可以自己封装一些语句在函数内部,函数就具有了某一种特定的功能。
声明函数:
使用关键字function 空格隔开函数名(参数) { },在大括号内部封装语句,表示一个整体
1. function 函数名(参数) {
2. 语句;
3. }
函数声明语句并不会执行,只是告诉我们函数可以实现某一种功能,内部有哪些语句。
1. // 声明函数=
2. function xiyiji() {
3. console.log("接水");
4. console.log("洗衣服");
5. console.log("漂洗");
6. console.log("甩干");
7. console.log("烘干");
8. }
函数名命名规则和变量相同。
函数优点1:将一些重复性的语句封装在函数内部,可以声明一次多次调用,简化书写。
1.2 调用
函数名();
函数是一个整体,在调用时,内部语句都会执行。
函数执行的位置和声明没有位置没有关系只和调用位置有关。
1. console.log(1);
2. // 声明函数
3. function xiyiji() {
4. console.log("接水");
5. console.log("洗衣服");
6. console.log("漂洗");
7. console.log("甩干");
8. console.log("烘干");
9. }
10. // 调用,函数内部语句都会执行
11. xiyiji();
12. console.log(2);
13. // 函数声明一次可以多次调用
14. xiyiji();
1.3 函数的参数
函数可以帮我们封装一些代码,代码可以重复调用,函数留了一个接口,就是我们的参数,可以通过参数的变化让我们的函数发生不同作用。
参数都是变量:命名规则与变量一样。
调用过程中给函数传递参数的过程就是一个给变量赋值的过程。
形式参数:在函数声明语句中小括号内部书写的是形式参数,简称形参
实际参数:在函数调用语句中小括号内部书写的是实际参数,简称实参
传参:在函数调用时,将实际参数赋值给形式参数的过程。
形参也有数据类型,是动态变化,根据实际参数赋值动态变化的。
参数个数问题:
arguments: 由实际参数组成的类数组对象。
实际参数个数多于形式参数,多于实际参数直接舍弃。
实际参数个数少于形式参数,优先将实参赋值前面的形参,没有赋值是undefined。
- sum(1);
函数的优点2:函数有参数,相当于给我们提供一个API接口,我们可以通过接口去调用函数,执行不同的操作,后面封装函数的时候,只需要了解API的用途就够了,就是传参之后有什么结果,不用去了解函数里面的构造。不论是自己的函数还是用的别人封装好的函数,都只需要知道怎么用就够了。
1.4 return
return返回值
return也可以接收参数(变量),作为函数的返回值。return并不会输出,想看输出console.log()
1. // 声明函数
2. function sum(a,b) {
3. // return 4;
4. // 只要调用函数可以的到a+b结果
5. return a + b;
6. }
7.
8. console.log(sum(1,4));
9. console.log(sum(10,sum(1,19)));
返回值相当于将我们的函数变成了一个表达式。利用这个特性,我们可以将函数作为一个实际参数,传递给另外一个函数。
return特点:函数内部遇见return,直接返回
1. // 函数内部遇见return直接返回值
2. function fun() {
3. console.log(1);
4. console.log(2);
5. console.log(3);
6. return;
7. console.log(4);
8. }
9.
10. // 函数调用
11. fun()
函数优点3:函数内部书写return,可以让函数作为参数,传递,有利于模块化编程
1.5模块化编程
人类从古至今,习惯将事情分工,将一些内容做成一些公共模块,模块可以重复反复使用。
模块化编程:将一些基础的公共的部分单独封装到一个函数内,可以多次被调用。
注意:模块化编程,可以让我们的程序更加优化,各个小模块要尽量功能单一,提高重复使用率。
案例:输出100以内的质数,模块化编程。
案例:输出100以内的完美数
1. /*
2. 找100以内的完美数:(一个数的约数除了它本身外其他约数和还等于这个数)。
3. 比如:6 = 1 + 2 + 3 6完美数
4. 8 != 1 + 2 + 4 8不是完美数
5.
6.
7. 输出100以内所有的完美数:
8. 判断任何数字是不是完美数
9. 一个数字除了本身之外其他约数和
10. */
11. /*
12. *函数1:一个数字约数和(不包含本身)
13. *参数: 一个数字
14. *返回值:约数和
15. */
16. function yueshuhe(n) {
17. // 求n约数和
18. // 累加器
19. var sum = 0;
20. for (var i = 1; i < n; i ++) {
21. // 约数和
22. if (n % i === 0) {
23. sum += i;
24. }
25. }
26. // 返回值
27. return sum;
28. }
29.
30.
31. /*
32. *判断任何数字是不是完美数
33. *参数: 一个数字
34. *返回值:布尔值
35. */
36. function isWms(num) {
37. // 判断num是不是完美数
38. if (num === yueshuhe(num)) {
39. return true;
40. }else {
41. return false;
42. }
43. }
44.
45. // 输出100以内所有的完美数
46. for (var i = 1; i <= 100; i ++) {
47. // i是完美数,输出
48. if (isWms(i)) {
49. console.log(i);
50. }
51. }
⦁ 1.6函数表达式
声明函数可以使用关键字function
还可以使用函数表达式
将一个匿名函数(拉姆达函数)赋值给一个变量。
⦁ var 函数名 = function (形式参数) {
⦁ 语句;
⦁ }; //结尾必须添加分号
调用 函数名(实际参数);
1 // 将匿名函数赋值给变量,结尾必须添加分号
2 var fun = function (a,b) {
3 return a + b;
4 };
5
6 // a,b数字 字符串
7 // 调用函数名()
8 alert(fun(1,10));
9 console.log(fun("1","你好"));
⦁1.6 函数数据类型
简单数据类型:number, string, undefined, boolean
引用数据类型:object, function ,arry
不管是关键字function还是函数表达式数据类型都是function。
简单数据类型:不同变量在赋值,变量将保存的值复制一份然后进行赋值。变量改变不互相影响
1 //简单数据类型
2 var a = 10;
3 // b = 10
4 var b = a;
5 // 改变a
6 a = "你好";
7 console.log(b); //b=10
引用数据类型:不同变量在赋值,将变量保存的地址进行赋值,变量改变会互相影响
1 // 引用数据类型
2 var fun1;
3 function fun2() {
4 console.log(1);
5 }
6 // fun2地址指向了fun1
7 fun1 = fun2;
8 // 改变
9 fun2.xixi = "嘻嘻";//设置属性
10 fun1.haha = "哈哈";//设置属性
11 console.log(fun1.xixi);//嘻嘻
12 console.log(fun2.haha);//哈哈
总结:简单数据类型保存的值,引用数据类型保存的地址。
1.7 执行上下文与执行上下文栈
1.71 执行上下文
1. 代码分类(位置)
* 全局代码
* 函数代码
2. 全局执行上下文
* 在执行全局代码前将window确定为全局执行上下文
* 对全局数据进行预处理
* var定义的全局变量==>undefined, 添加为window的属性
* function关键字声明的全局函数==>赋值(fun), 添加为window的方法
* this==>赋值(window)
* 开始执行全局代码
3. 函数执行上下文
* 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象
* 对局部数据进行预处理
* 形参变量==>赋值(实参)==>添加为执行上下文的属性
* arguments==>赋值(实参列表), 添加为执行上下文的属性
* var定义的局部变量==>undefined, 添加为执行上下文的属性
* function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
* this==>赋值(调用函数的对象)
* 开始执行函数体代码
1.72 执行上下文栈
1. 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
2. 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)
3. 在函数执行上下文创建后, 将其添加到栈中(压栈)
4. 在当前函数执行完后,将栈顶的对象移除(出栈)
5. 当所有的代码执行完后, 栈中只剩下window
⦁ 1.8函数声明提升
1. 变量声明提升
* 通过var定义(声明)的变量, 在定义语句之前就可以访问到
* 值: undefined
2. 函数声明提升
* 通过function声明的函数, 在之前就可以直接调用
* 值: 函数定义(对象)
变量提升只提升声明语句,赋值语句不提升。
function关键字声明函数提升。在函数预解析时将函数名提升到所有语句之前,由于函数保存的是地址,通过地址可以找到声明整体。相当于函数整体提升到所有语句之前。
函数先调用,在声明也不会报错。
1 // 先调用
2 fun();
3 // 后声明
4 function fun() {
5 console.log(1);
6 }
函数表达式,提升只提升声明语句,赋值语句不提升。先调用会报错。
1 // 先调用
2 fun();
3 // 后声明
4 var fun = function () {
5 console.log(1);
6 };
总结:在声明函数时最好使用function关键字声明,不会出错。
一般我们习惯先书写函数调用,将声明书写在所有语句前,便于代码读取。
function声明函数名和变量相同,优先提升function,给function使用。
1 // 先使用
2 console.log(fun);
3 // 使用function声明fun函数
4 function fun() {
5 return 1;
6 }
7 // 声明fun变量
8 var fun = 10;
9
10 /*
11 预解析:
12 优先提升函数
13 function fun(){};名字不会重复声明
14 console.log(fun);
15 fun = 10;
16 */
17 console.log(fun);
函数表达式和function关键字重名,优先给function关键字使用。
1 // 先使用
2 fun();
3 // 声明函数表达式
4 var fun = function () {
5 console.log(1);
6 };
7 // function关键字
8 function fun() {
9 console.log(2);
10 }
11 /*
12 预解析:优先提升function
13 function fun(){console.log(2)}; fun名字声明过不会重复声明
14 fun();
15 fun = function() {console.log(1)};
16 */
17 fun();
1.8递归函数
在函数内部调用自身。一般解决数学问题
菲波那切数列:
1,1,2,3,5,8,13,21……
1 /*
2 1,1,2,3,5,8,13,21……
3
4 函数:用户输入一个项数,告诉用户该项对应的值
5 从第3项开始,该值=前1项+前2项对应值
6 参数: 项数1-
7 返回值:该项对应的值
8 */
9 function feibo(n) {
10 // 求第n项对应的值
11 if (n === 1 || n === 2) {
12 return 1;
13 }else {
14 return feibo(n-1) + feibo(n-2);
15 }
16 }
17
18 console.log(feibo(1));
19 console.log(feibo(2));
20 console.log(feibo(3));
21 console.log(feibo(4));
22 console.log(feibo(8));
23 console.log(feibo(20));
1.9变量作用域
作用域是什么:
1. 理解
* 就是一块"地盘", 一个代码段所在的区域
* 它是静态的(相对于上下文对象), 在编写代码时就确定了
2. 分类
* 全局作用域
* 函数作用域
* 没有块作用域(ES6有了)
3. 作用
* 隔离变量,不同作用域下同名变量不会有冲突
在函数内部声明的变量只能在函数内部使用,在函数外部任何地方都不能使用。
1 // 在函数内部声明a变量
2 function fun() {
3 var a = 10;
4 console.log(a);
5 }
6
7 // 在函数外部使用a
8 console.log(a);//报错
js中只有函数可以关住变量的作用域。
1.9局部变量和全局变量
局部变量:在一个作用域(定义域)内定义的变量就是这个作用域内的局部变量。只能在作用域内被访问到。
全局变量:从广义上来看,全局变量也是一种局部变量。全局变量定义在全局,所以也叫全局变量。可以在任何地方都被访问到。
变量申明的原理:全局变量,在全局定义之后,会永久存在,任何时候,任何位置访问,都能够找到它。局部变量定义在函数内部的,函数定义的过程,并没有真正的去定义这个局部变量,只有在执行函数的时候,才会立即定义这个局部变量,执行完之后,变量就被立即销毁了,在其他的地方访问变量的时候,找不到这个变量,所以会有一个引用错误,变量未定义。
1 // 没有书写在函数内部的变量,可以在全局范围内访问
2 var b = "你好";
3 // 在函数内部声明a变量
4 function fun() {
5 // a只能在函数内部使用,a局部变量
6 var a = 10;
7 console.log(a);
8 // 全局b,可以在全局使用,当然可以在函数内部使用
9 console.log(b);
10 }
11
12 // 在函数外部使用a
13 // console.log(a);
14 fun();
15 console.log(b);
1.10 形参是局部变量
形参只能在该函数内部使用,在函数外部不能访问,会报错。
1 function fun(a,b) {
2 console.log(a);
3 console.log(b);
4 }
5
6 fun(1,10);
7 // 形参是fun()函数局部变量,在fun函数外部不能使用,会报错
8 console.log(a);
9 console.log(b);
1.11全局变量使用
函数间的通信作用:函数声明一次可以多次调用,全局变量可以进行累加。
1 // 声明全局变量a
2 var a = 1;
3 // 声明函数
4 function jia() {
5 a ++;
6 console.log(a);
7 }
8 // 函数间的通信,都可以改变a
9 jia(); //a=2
10 jia(); //a=3
11 console.log(a);
全局变量作用:传递作用
不同的函数都可以改变全局变量值,并且使用最新值参与计算。(信号量)
1 // 声明全局变量a,信号量
2 var a = 1;
3 // 声明函数1让a自加
4 function jia() {
5 a ++;
6 console.log(a);
7 }
8 // 声明函数2让a自减
9 function jian() {
10 a --;
11 console.log(a);
12 }
13
14 jia(); //a=2
15 jian(); //a=1
16 jia();
17 jia();
18 jia(); //a=4
19 console.log(a);
1.12 作用域链
指的是我们变量查找的一个规律:我们可以在不同的作用域内使用相同的标识符去命名变量。我们在使用一个变量的时候,需要找到匹配的标识符,我们有重复的,用哪一个?如果在当前作用域有这个变量,就直接使用,如果当前作用域没有这个变量定义,会一层一层的从本层往外依次查找,遇到第一个就直接使用,类似于就近原则。
1 // 声明全局a
2 var a = 1;
3 function fun1() {
4 // 声明fun1局部变量a
5 var a = 2;
6 // a=2
7 console.log(a);
8 function fun2() {
9 var a = 3;
10 function fun3() {
11 // 本层fun3没有a定义;从本层出发依次向外查找。
12 //a=3
13 console.log(a);
14 }
15 fun3();
16 }
17 fun2();
18 }
19 fun1();
20 console.log(a);
当遇见一个变量时,JS引擎会从其所在的作用域依次向外层查找,查找会在找到第一个匹配的标识符的时候停止。在多层嵌套的作用域中可以定义同名的标识符,发生“遮蔽效应”。
如果变量声明时,不写var关键字,计算机会自动在全局作用域内给它进行一个声明,局部变量就强制性的变成了全局变量。这种情况是不合理,会造成一个全局变量的污染。所以,定义变量必须写var关键字。
1 // 声明全局a
2 var a = 1;
3 // a=2; a=3
4 function fun1() {
5 a = 2;
6 console.log(a);
7 function fun2() {
8 a = 3;
9 function fun3() {
10 console.log(a);
11 }
12 fun3();
13 }
14 fun2();
15 }
16 fun1();
17 // 由于变量强制转为全局变量a=3
18 console.log(a);
1.13函数作用域
函数的作用域和变量作用域相似,也是函数只能在声明函数的内部使用,在声明函数外部不能使用。
1 function outer() {
2 var a = 1;
3 // inner函数在outer内部声明,只能在outer内部使用
4 function inner() {
5 console.log(a);
6 }
7 inner();
8 }
9 outer();
10 // 在outer外部不能使用inner
11 inner();
总结:函数能关住变量和函数的作用域。
1.14 闭包
闭包就是能够读取其他函数内部变量的函数。只有函数内部的子函数才能读取局部变量,在本质上,闭包是函数内部和函数外部连接起来的桥梁。
1. 如何产生闭包?
* 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包
2. 闭包到底是什么?
* 使用chrome调试查看
* 理解一: 闭包是嵌套的内部函数(绝大部分人)
* 理解二: 包含被引用变量(函数)的对象(极少数人)
* 注意: 闭包存在于嵌套的内部函数中
3. 产生闭包的条件?
* 函数嵌套
* 内部函数引用了外部函数的数据(变量/函数)
4. 常见的闭包
* 将函数作为另一个函数的返回值
* 将函数作为实参传递给另一个函数调用
闭包的作用
1. 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
2. 让函数外部可以操作(读写)到函数内部的数据(变量/函数)
问题:
1. 函数执行完后, 函数内部声明的局部变量是否还存在?
一般不存在,存在于闭包中的变量才可能存在
2. 在函数外部能直接访问函数内部的局部变量吗?
不能但是可以通过闭包操作它
1 function outer(){
2 var a = 1;
3 function inner(){
4 console.log(a);
5 }
6 return inner;
7 }
8 var inn = outer();
9 inn();//值为1
inner函数把它自己内部的语句(console.log(a)),和自己声明时所处的作用域(var a = 1;)一起封装成了一个密闭环境,我们称为“闭包”。
闭包天生存在,并不需要什么特殊的结构才存在,只不过我们必须要刻意地把函数放到其他的作用域中调用,才能明显的观察到闭包性质。
⦁ 闭包外部环境并不是一成不变的。
1 function fun1() {
2 var a = 1;
3 function fun2() {
4 a ++;
5 console.log(a);
6 }
7 return fun2;
8 }
9 /*
10 对于fun2 外部环境a=1 内部语句a++; console.log(a);
11
12 */
13 var inn = fun1();
14 inn(); //a=2
15 inn(); //a=3
16 inn(); //a=4
17 inn(); //a=5
18 inn(); //a=6
19 inn(); //a=7
⦁ 函数每一次调用都产生一个全新的闭包,外部环境和内部语句都是全新的
1 function outer(a) {
2 function inner(b) {
3 console.log(a + b);
4 }
5 return inner;
6 }
7
8 /*
9 第一个包
10 outer(10) 初始定义inner 外部环境a=10 和 内部语句console.log(10 + b)
11 */
12 var fun1 = outer(10);
13 /*
14 第二个包:
15 外部环境a = 100
16 内部语句console.log(100 + b)
17 */
18 var fun2 = outer(100);
19
20 fun1(1);
21 fun2(1);
闭包的生命周期:
<!--
1. 产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)
2. 死亡: 在嵌套的内部函数成为垃圾对象时
-->
<script type="text/javascript">
function fn1() {
//此时闭包就已经产生了(函数提升, 内部函数对象已经创建了)
var a = 2
function fn2 () {
a++
console.log(a)
}
return fn2
}
var f = fn1()
f() // 3
f() // 4
f = null //闭包死亡(包含闭包的函数对象成为垃圾对象)
</script>
闭包的缺点及解决:
1. 缺点
* 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长
* 容易造成内存泄露
2. 解决
* 能不用闭包就不用
* 及时释放