[TOC]

函数的基本概念、声明及调用;函数作用域、作用域链、闭包;this指向及修改和绑定this指向等。

1.函数概念,声明及调用

JS中的函数:把一段需要重复使用的代码,用function语法包起来,方便重复调用,分块和简化代码。复杂一点的,也会加入封装、抽象、分类等思想。
声明方式:严格意义上两种方式,但还有匿名函数

  • 方式一: ```
  1. function 方法名(){
  2. //要执行的代码
  3. } ```
  • 方式二:ES6中声明方式箭头函数,()=>{}
  • 方式三:匿名函数,将函数存到变量里 var func = function(){};

函数调用:两种方式调用

  • 调用方式一:名字(); 函数可以多次调用 ```
  1. //函数声明
  2. function fn(){
  3. console.log(1);
  4. }
  5. //函数的调用
  6. fn();
    调用方式二:在事件中调用,直接写函数名,不使用括号
    
  7. //函数声明
  8. function fn(){
  9. console.log(1);
  10. }
  11. //函数在事件中的调用
  12. document.onclick = fn; ```

    2.函数表达式(匿名函数)

    函数表达式:就是把函数存到变量里。
    匿名函数:没有名字的函数;
    匿名函数在使用时只有两种情况:
  • 匿名函数自执行:声明后不需要调用就直接执行 ```
  1. (function(){
  2. console.log(“匿名函数自执行”);
  3. })(); ```
  • 函数表达式:把函数存到变量,或将函数存到数组的对应位置里等,调用时通过变量或数组对应位置进行调用。调用时需要写括号。 ```
  1. //2,函数表达式:把函数存到变量或数组等里,调用时通过变量进行调用
  2. var fn = function(){
  3. console.log(“函数表达式:将函数存到变量里”);
  4. };
  5. fn();//调用时需要写括号
  6. //2,函数表达式:把函数存到数组第0位,调用时通过数组第0位进行调用
  7. var arr = [];
  8. arr[0] = function(){
  9. console.log(“函数表达式:将函数存到数组的对应位置”);
  10. };
  11. arr0;//调用时需要写括号要写括号
    结果:<br />![](https://cdn.nlark.com/yuque/0/2020/png/2615872/1607244837220-19552b57-5f7c-419d-b203-9e9440ea6424.png#align=left&display=inline&height=69&margin=%5Bobject%20Object%5D&originHeight=69&originWidth=277&size=0&status=done&style=none&width=277)<br />事件函数扩展:给元素添加事件的说法是不正确的。事件时元素本身就具有的特征,只是触发事件后,默认没有相关的一些处理。这种操作其实就是给元素的某个事件添加一个事件处理函数。当事件被触发后,判断到属于该事件类型,就触发该事件函数的处理函数。<br />可以通过console.dir()把对象的所有属性和方法打印出来,查看对象或元素本身具有的事件。
    
  12. ``` 结果:
    函数 - 图1

    3.函数传参

    获取元素,最好从父级元素获取,全部从document中获取,可能会出现混乱。
  • 形参:形式上的参数——给函数声明一个参数;
  • 实参:实际的参数——在函数调用时给形参赋的值 ```
  1. function func(形参1,形参2){
  2. //函数执行代码
  3. }
  4. func(实参1,实参2);//调用时传参
    什么时候使用到传参?当有两段代码本身的功能极其相似,只有个别地方不一样时,就可以把两段代码合并成一个函数,然后把两段代码中不一致的内容通过传参传进去。
    <a name="xgS4Y"></a>
    # 4.函数的不定参(可变参)—关键字arguments
    案例:购物车商品累计。事先不知道用户买多少商品<br />不定参(可变参)使用关键字:arguments,代表所有实参的集合。通过下标获取参数的每一位;通过length获取实参的个数;<br />集合是类数组,可以使用下标,但是没有数组中的各种方法。
    
  5. <!DOCTYPE html>
  6. ``` 结果:
    函数 - 图2

    5.函数返回值

    函数返回值即函数执行之后的返回结果。

  7. 所有函数都会有函数返回值即函数执行后一定会返回一个结果,如果没有定义默认返回undefined;

  8. 在函数中,return后定义返回值;
  9. 在函数中,return之后的代码就不会再执行了
  10. return只能用于函数中,用在其他地方会报错 ```
  11. ``` 结果:发现return后的代码没有继续执行
    函数 - 图3

    6.作用域

    函数 - 图4
    通常来说一段程序代码中使用的变量和函数并不总是可用的,限定其可用性的范围即作用域,作用域的使用提高了程序逻辑的局部性,增强程序的可靠性,减少名字冲突。
    通俗的说,作用域:数据起作用的范围(某条数据可以在什么范围内使用)
    前端权威官方MDN:https://developer.mozilla.org/en-US/docs/Glossary/Scope
    作用域的分类:

    • 全局作用域:通过var或function声明在全局(声明在任意函数之外和代码块之外)中的数据,在全局的任意地方都可以调用或修改(即全局变量)和在window下的属性
    • 局部作用域:
  • 函数作用域:声明在函数内部的某个数据(var,function,参数),就只能在函数内部使用(函数的局部作用域)
  • 块级作用域(ES6新增)

全局作用域:

1. //声明在全局中的变量
2. var a = 0;
3. console.log(a);//可在全局任意地方调用
4. function fn(){
5. console.log(a);//可在函数中调用
6.         a = 10;//可在任意地方修改全局中的变量
7.     }
8.     fn();
9. console.log(a);

结果:
函数 - 图5

7.全局污染(命名冲突问题)

全局变量污染:大家都在全局中写代码,很容易造成命名冲突,导致代码冲突。ES6中代码冲突会直接报错。所以要养成好的习惯不要在全局去声明变量。

1. <!DOCTYPE html>
2. <html lang="en">
3. <head>
4. <meta charset="UTF-8">
5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
7. <title>全局污染</title>
8. </head>
9. <body>
10. <div id="list"></div>
11. <div class="memulist"></div>
12. <script>
13. var list = document.getElementById("list");
14. var list = document.querySelector(".memulist");
15. console.log(list);
16. </script>
17. </body>
18. </html>

结果:发现最后获取的只有一个元素,所以很容易造成代码冲突
函数 - 图6
解决:不要声明全局变量

1. (function(){
2. var list = document.getElementById("list");
3. console.log(list);
4. })();
5. 
6. (function(){
7. var list = document.querySelector(".memulist");
8. console.log(list);
9. })();

结果:
函数 - 图7
JS中提供了id使用的简便方法,直接调用id名即可:但尽量不要这么写,不规范:

console.log(list);

函数 - 图8
匿名函数:匿名函数自执行本身就是为了避免在全局写代码,避免冲突的。匿名函数自执行也叫开启一个新的命名空间。即开启新的作用域,此作用域和其他的不会冲突。

8.作用域链(scope chain)

作用域链决定了哪些数据能被函数访问。当一个函数创建后,它的作用域链会被创建此函数的作用域中可访问的数据对象填充。
作用域链:JS中数据的查找规则。
作用域链查找过程:在JS中我们调用一条数据时,会先在当前作用域进行查找,如果找不到,就从向上找父作用域的数据,还找不到就接着向上,一直找到全局作用域(window对象),window都找不到就报错。

1. //调用fn()时,在其子函数fn2()被调用时,首先会在fn2()自己的作用域内找变量a
2. //找不到就在其父级作用域即fn()作用域中找,即a=10,然后打印a=10
3. function fn(){
4. var a = 10;
5. function fn2(){
6. console.log(a);
7.         }
8.         fn2();
9.     }
10.     fn();

结果:函数 - 图9
作用域链查找关系图:
函数 - 图10
作用域链示例:

1. <script>
2. function fn(){
3. var b = 0;
4. return function(){
5.             b++;
6. console.log(b);
7.         };
8.     }
9. var f = fn();
10. console.log(f);//ƒ (){ b++; console.log(b); }
11.     f();//1
12.     f();//2
13.     f();//3
14.     fn()();//1
15. </script>

结果:
函数 - 图11
解析(函数拆分)三个f():
注意:这里的var f = fn();是将函数fn()的返回值函数体f(){ b++; console.log(b); };赋给变量f,但是并没有执行该返回值函数体,当f()调用时,便执行了该函数体。f此时是fn的子函数,那它可以访问和更改父级fn的作用域中的b。
b变量会一直赋值,是因为JS的垃圾回收机制决定的,只要检测都有引用存在,就不会释放。

1. <!DOCTYPE html>
2. <html lang="en">
3. <head>
4. <meta charset="UTF-8">
5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
7. <title>作用域链示例-函数拆分</title>
8. </head>
9. <body>
10. <script>
11. // function fn(){
12. //     var b = 0;
13. //     return function(){
14. //         b++;
15. //         console.log(b);
16. //     };
17. // }
18. // var f = fn();
19. // f();//1
20. // f();//2
21. // f();//3
22. // fn()();//1
23. //这里的var f = fn();是将函数fn()的返回值函数体function(){ b++; console.log(b); };赋给变量f,但是并没有执行该返回值函数体,当f()调用时,便执行了该函数体
24. //即这里的三个f()相当于,在函数fn()中写了三个子函数,再进行调用
25. //由于b变量对于fn1(),fn2(),fn3()都是父级变量,所以每次b++都会将b的值+1,所以最后得到的值即1,2,3
26. function fn(){
27. var b = 0;
28. function fn1(){
29.             b++;
30. console.log(b);
31.         }
32.         fn1();
33. function fn2(){
34.             b++;
35. console.log(b);
36.         }
37.         fn2();
38. function fn3(){
39.             b++;
40. console.log(b);
41.         }
42.         fn3();
43.     }
44. 
45.     fn();
46. </script>
47. </body>
48. </html>

结果:所以执行三个f();和执行三个fn();得到的结果是不一样的。
函数 - 图12
以上进一步分解:

1. <script>
2. // function fn(){
3. //     var b = 0;
4. //     return function(){
5. //         b++;
6. //         console.log(b);
7. //     };
8. // }
9. // var f = fn();
10. // f();//1
11. // f();//2
12. // f();//3
13. // fn()();//1
14. //这里的var f = fn();是将函数fn()的返回值函数体function(){ b++; console.log(b); };赋给变量f,但是并没有执行该返回值函数体,当f()调用时,便执行了该函数体
15. //即这里的三个f()相当于,在函数fn()中写了三个子函数,再进行调用
16. //由于b变量对于fn1(),fn2(),fn3()都是父级变量,所以每次b++都会将b的值+1,所以最后得到的值即1,2,3
17. function fn(){
18. var b = 0;
19. // function fn1(){
20. //     b++;
21. //     console.log(b);
22. // }
23. // fn1();
24. // function fn2(){
25. //     b++;
26. //     console.log(b);
27. // }
28. // fn2();
29. // function fn3(){
30. //     b++;
31. //     console.log(b);
32. // }
33. // fn3();
34. function fnn(){
35.             b++;
36. console.log(b);
37.         }
38.         fnn();
39.         fnn();
40.         fnn();
41. 
42.     }
43.     fn();
44. </script>

结果:所以调用三次f()和再fn()函数里执行三次fnn()是一样的
函数 - 图13
函数拆分fn()():

1. <!DOCTYPE html>
2. <html lang="en">
3. <head>
4. <meta charset="UTF-8">
5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
7. <title>作用域链示例——函数拆分fn()()</title>
8. </head>
9. <body>
10. <script>
11. // function fn(){
12. //     var b = 0;
13. //     return function(){
14. //         b++;
15. //         console.log(b);
16. //     };
17. // }
18. // var f = fn();
19. // f();//1
20. // f();//2
21. // f();//3
22. // fn()();//1
23. 
24. //此处的fn()();第一个括号表示执行fn()函数,第二个括号表示执行fn()函数中返回值中的函数,所以fn()()相当于整个fn函数再执行一次
25. //函数每次调用,都相当于把这个代码复制出来执行了一遍。所以fn()();每次执行都是重新执行一遍代码,相当于以下:
26. function fn(){
27. var b = 0;
28. return function(){
29.             b++;
30. console.log(b);
31.         };
32.     }
33. function fnA(){
34. var b = 0;
35. return function(){
36.             b++;
37. console.log(b);
38.         };
39.     }
40.     fn()();//1
41.     fnA()();//1
42. </script>
43. </body>
44. </html>

结果:
函数 - 图14
函数每次调用,如fn()和fnA()之间没有任何关联,都相当于把这个代码复制出来执行了一遍。

(本文节选自CSDN黎小小咩~的js函数讲解,版权归黎小小咩~所有)