结论:函数是一个特殊对象

如何定义一个函数

具名函数

  1. //写法1
  2. function 函数名(形参){
  3. 语句
  4. return 返回值
  5. }
  6. //写法2,变量a存储fn()的返回值
  7. //这里要注意,在调用函数的时候,不能写成fn(1,2),会报错,因为fn这个函数的作用域只在等号右边
  8. let a = function fn(x,y){return x+y}

匿名函数

  1. //因为是匿名函数,所以写成如下形式,右边叫函数表达式,需要用一个变量存储返回值,
  2. let a = function(x,y){return x+y}

箭头函数

  1. let f1 = x => x*x
  2. let f2 = (x,y) => x + y
  3. let f3 = (x,y) => {return x+y}
  4. let f4 = (x,y) => ({name:x,age:y})//创建对象

用构造函数

  1. let f = new Function('x','y','return x+y')
  2. 基本上没人用,但是能让你知道函数是谁构造的
  3. 所有函数都是Function构造出来的
  4. 包括ObjectArrayFunction也是

函数自身(fn)&函数调用(fn())

  1. let fn = () => console.log('hi')
  2. let fn2 = fn
  3. fn2()

上面的代码运行的结果:

  • fn保存了匿名函数的地址
  • 这个地址被赋值给了fn2
  • fn2()调用了匿名函数
  • fn和fn2都是匿名函数的饮引用而已
  • 真正的函数既不是fn也不是fn2

函数的几大要素:

调用时机

时机不同,结果不同

例1

  1. let a = 1
  2. function fn(){
  3. console.log(a)
  4. }
  5. 上面的代码,最终的打印结果是?
  6. 没有结果,因为没有调用代码

例2

  1. let a = 1
  2. function fn(){
  3. console.log(a)
  4. }
  5. a = 2
  6. fn()
  7. //打印结果是:2

例3

  1. let a = 1
  2. function fn(){
  3. console.log(a)
  4. }
  5. fn()
  6. a = 2
  7. //打印结果是:1

例4

  1. let a = 1
  2. function fn(){
  3. setTimeout(()=>{
  4. console.log(a)
  5. },0)
  6. }
  7. fn()
  8. a = 2
  9. //打印的结果是:2
  10. //setTimeout的时间虽然是设置为0,但是它会在代码执行完以后再执行setTimeout,所以结果是2

例5

  1. let i = 0
  2. for (i = 0;i<6;i++){
  3. setTimeout(()=>{
  4. console.log(a)
  5. },0)
  6. }
  7. fn()
  8. //打印的结果是:6个6
  9. //setTimeout但会在for循环代码执行完以后再执行setTimeout,所以结果是6个6

例6

  1. for (let i = 0;i<6;i++){
  2. setTimeout(()=>{
  3. console.log(a)
  4. },0)
  5. }
  6. fn()
  7. //打印的结果是:0,1,2,3,4,5
  8. //js在for和let一起用的时候,每次循环都会多创建一个i,for内用let相当于在每个循环{}内重新定义并赋值i。

例7(来自网上查找的结果)

  1. 除了for let配合,还有下面的方法可以打印出012345
  2. //方法1:
  3. 创建一个立即执行函数(匿名函数),把i作为参数传给这个函数
  4. let i
  5. for(i = 0; i<6; i++){
  6. !function(value) {
  7. setTimeout(()=>{
  8. console.log(value)
  9. },0)
  10. }(i)
  11. }
  12. //方法2:
  13. 利用const关键字
  14. let i
  15. for(i = 0; i<6; i++){
  16. const x = i
  17. setTimeout(()=>{
  18. console.log(x)
  19. })
  20. }
  21. //方法3:
  22. 利用setTimeout的第三个参数,将i传进去当做value打印出来即可
  23. let i
  24. for(i = 0; i<6; i++){
  25. setTimeout((value)=>{
  26. console.log(value)
  27. },0,i)
  28. }

作用域

例1

  1. function fn(){
  2. let a = 1
  3. }
  4. console.log(a)//a不存在

例2

  1. function fn(){
  2. let a = 1
  3. }
  4. fn()
  5. console.log(a)//a还是不存在,因为a的作用域只在{}中

例3

  1. function f1(){
  2. let a = 1
  3. function f2(){
  4. let a = 2
  5. console.log(a)//此时的a在f2()的作用域,所以a的值是2
  6. }
  7. console.log(a)//此时的a是在f1()的作用域中,所以a的值是1
  8. a = 3
  9. f2()
  10. }
  11. f1()

作用域规则

如果多个作用域有同名变量a

  • 那么查找a的声明时,就向上去最近的作用域——就近原则
  • 查找a的过程与函数执行无关(静态作用域,也叫词法作用域),但a但值与函数执行有关(动态作用域)

    例4

    1. function f1(){
    2. let a = 1
    3. function f2(){
    4. let a = 2
    5. function f3(){
    6. console.log(a)//就近原则,这里但a在上层的let a = 2中
    7. }
    8. a = 22
    9. f3()//a的值是22
    10. }
    11. console.log(a)
    12. a = 100
    13. f2()
    14. }
    15. f1()

    全局变量&局部变量

    在顶级作用域声明的变量是全局变量,window的属性是全局变量,其他都是局部变量

    ```javascript let b = 1 function f2(){ window.c = 2 let b } f2() function f1(){ console.log(c) } f1() //在window上面声明变量c以后,这个变量就变成了全局变量,f2()的let b只作用在f2()里面
  1. <a name="1odV6"></a>
  2. ### 闭包
  3. <a name="JOuuR"></a>
  4. #### 如果一个函数用到了外部的变量,那么这个函数加这个变量,就叫做闭包,可以看例4,f2()的a和f3()组成了闭包
  5. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/1444038/1591587565442-b5e21431-2dc9-4a79-9cf5-44075e3420d5.png#align=left&display=inline&height=334&margin=%5Bobject%20Object%5D&name=image.png&originHeight=334&originWidth=668&size=27458&status=done&style=none&width=668)
  6. <a name="6ZHWy"></a>
  7. ### 形参
  8. <a name="OvDF7"></a>
  9. #### 形参的意思就是非实际参数,可以理解成变量声明
  10. ```javascript
  11. function f1(x,y){
  12. return x+y
  13. }
  14. //x,y就是形参
  15. f1(1,2)
  16. //调用函数时填入的参数就是实参
  17. //上面的代码近似等价于下面的代码
  18. function f1(){
  19. var x = arguments[0]
  20. var y = arguments[1]
  21. return x+y
  22. }

形参可多可少

  1. function f1(x){
  2. return x + arguments[1]//形参有1个,当传进来的参数有多个时,可以用arguments[n]获取
  3. }
  4. f1(1,2)

返回值

每个函数都有返回值,函数执行完以后才会返回,只有函数才有返回值

  1. function hi(){
  2. console.log('hi')
  3. }
  4. hi()//因为没有写return,所以返回值是undefined
  5. function hello(){
  6. return console.log('hello')
  7. }
  8. hello()//返回值为console.log('hello')的值,即undefined

调用栈

什么是调用栈:

JS引擎在调用一个函数前,需要吧函数所在的环境push到一个数组里 这个数组叫做调用栈 等这个函数执行完了,就会把环境弹(pop)出来,然后return到之前的环境,继续执行后续代码

例:

  • console.log(1)
  • console.log(‘1+2的结果为’ + add(1,2))
  • console.log(2)

    函数提升

    什么是函数提升

    1. function fn(){}
    2. //不管把具名函数声明在哪里,它都会跑到第一行
    3. 例如:
    4. add(1,2)
    5. function add(x,y){
    6. return x+y
    7. }//这样的顺序,函数还是会跑到调用的前面

    什么不是函数提升

    1. let fn = function(){}
    2. //这是赋值,右边的匿名函数声明不会提升

    arguments(除了箭头函数)

    arguments是包含了所有参数的伪数组

    this(除了箭头函数)

    如果不给任何条件,那么this默认指向window

    如果传进去的this不是个对象,js会自动封装成对象

    箭头函数里面的this就是外面的this,就算加call都没用

    ```javascript console.log(this)//window let fn = () => console.log(this) fn()//window

```

结论:

this是隐藏参数,arguments是普通参数