JS中的函数

176 阅读10分钟

---------------------函数 封装数据块-----------

在JS中

  函数肯定会给我们输出东西,我们可以不给它输入东西。
  它内部有很多内置函数,可以直接使用,也可以自定义函数。

从三个方面认识函数:

   函数是一个代码段。
   函数是一类数据,是函数这种数据类型。
   函数是一个对象。

函数的作用:

   代码重用
   用于组织代码
   作为构造器,用于生成对象

1.函数定义(两种)

    第一种   函数声明    使用function关键字,基本格式如下:
      function 函数名称(参数1,参数2,参数3,...){
        函数体内容;
      }
      function用来定义声明函数   ()是函数的标志
       一些细节:
               定义函数,需要关键字function,关键字之后和函数名需要空格
               函数名称,和定义变量时规则相同,大小写区分,首字母使用小写,多个单词遵循小驼峰命名法则
               函数名不要以数字开头,可以使用一些特殊符号,如下划线。
               函数名称后面是小括号,必不可少,用来装载形式参数。
               {}就是函数体,不可少。
               定义和声明的区别:定义需要赋值,声明不需赋值。
    第二种    函数表达式(变量)   用表达式方式定义一个函数,例如,
      let hello = function(){
        alert("hello world!");
      }
       一些细节:
               将function整体,放在赋值运算符的右边,作为表达式来使用。
               通过一个变量来引用当前的function,便于后续的调用。
               函数名称可以加上,但是只对函数内部起作用。对外是不可见,不可调用。

两种方式的区别:

  使用函数声明方式来定义函数, 可以函数声明提前。会被提升。
  使用函数表达式方式来定义函数,其实函数表达式就是一个变量,只不过我们在赋值的时候,将函数这种类型的数据赋值给它了。
   不会被提升。
 既然是变量,就要遵循变量的规则,一定要先声明,然后在使用。

2.函数调用

如果定义一个函数,需要调用它,它才会执行函数体中的代码段。如果没有调用,相当于函数没有写。
函数调用,只需要使用小括号就可以了。如:  函数();

js中函数调用有四种方式。
  作为函数
  作为方法
  作为构造函数
  使用call(apply)间接调用

作为函数调用,是做简单的一种情况。
  自定义函数的调用: f();
  js提供的内置函数: alert(), parseInt(), parseFloat(),setTimeout等

作为方法调用,将函数定义到某个具体的对象上面,然后通过语法来调用  如:  window.alert("哈哈");

3.函数的返回值

   js开发中,我们调用一个函数,往往是需要通过他的处理,得到一个具体的结果。
   自己定义的函数需要显式的返回一个值,使用return关键字即可。
   通过return返回,返回到函数的调用处。
   在函数体中,return后面的代码就不会执行了。return也叫跳转语句。
   return只能出现在函数中,不要写在函数外面。
   如果写了return,但是没有具体的内容,返回的是undefined.
   函数比小学要有一个函数值,没有默认返回undefined。
   只能返回一个值,如果想要返回多个值,可以返回一个(array,array)容器。
   return只是将结果返回到函数调用处,如果需要打印显示出来,还是需要console.log();       

4.函数的参数

函数的本质:
   函数的作用,代码重用,编写一个函数,就是为了解决这一类问题
   函数每次调用,都有一个返回值,那么返回值和什么相关呢?和我们给定的某一值相关。
   这就涉及到参数问题。


形参:
   函数定义时,()中写的参数,叫形参,相当于函数内部的局部变量,作用是用来接收实参。
实参:
   函数调用时,()中写的参数,叫实参,作用是给函数传递真实的参数,传递过去形参接收

在js中,定义和调用函数的时候,对形参和实参要求没有那么严格。
形参和实参的个数,可以不相等,程序不会出错,结果可能会出问题。
在js中,对于形参没有数据类型(数值,字符串,布尔值)的要求。

js是一门弱类型的语言。具体的体现在:
   变量在声明时,不需要指定类型。
   函数中的参数,在声明的时候,也不需要指定类型。

命名法;
     小驼峰命名:tosTring()
     大驼峰命名:ToString()
     下划线命名:to_string()

5.函数的作用域

作用域是针对变量而言的。作用域是指变量的生效范围,在程序中,在什么范围内可以访问,什么范围不能访问。

在JS中,以函数为界,可以分为以下两种:
      全局变量,在函数外部定义的变量
      局部变量,在函数内部定义的变量
      
局部作用域内(在函数中),可以访问全局的变量。
全局作用域中(在函数外部),是不能访问局部作用域的变量。

在JS中,作用域分为三类:全局作用域,局部作用域,函数内部会形成一个局部作用域(函数作用域),块级作用域(let{})

6.参数的传递

需要用参数的函数:
   alert(),console.info(),console.log()
不需要用参数的函数:
   getDate(),Random()
没有给参数,不需要用参数要分清楚。


参数传递分为基本数据类型和引用数据类型。
   基本数据类型的传递(值传递):
      就是把栈区的数据copy一份给形参,形参和实参是一个独立的内存空间。
      改变形参,实参是没有影响。
   引用数据类型的传递(引用传递):
      就是把栈区的数据地址copy一份给形参,形参和实参同时指向堆区的同一个内存空间。
      通过形参改变堆区内存空间的数据,实参这个地址对应的数据也会改变。

我的思考:根据函数体的数据类型来判断参数传递是基本数据类型还是引用数据类型。

7.函数可以是另一个函数的参数

一个函数可以作为另一个函数的参数,这个函数可以是函数声明,也可以是函数表达式

第一种写法,作为函数声明
     function add(x,y);{
       return x + y;
     }
     function sub(x,y){
       return x - y;
     }
     function compute(f,a1,a2){
       return f(a1,a2);
     }
     var rs = compute(add,1,2);
     console.log(rs);
     var rs = compute(sub,1,2);
     console.log(rs);

第二种写法,作为函数表达式
     let add = function(x,y){
       return x + y;
     }
     let sub = function(x,y){
       return x - y;
     }
     function compute(f,a1,a2){
       return f(a1,a2);
     }
     var rs = compute(add,1,2);
     console.log(rs);
     var rs = compute(sub,1,2);
     console.log(rs);
  变量名字add,它可以保存任何类型数据(基本数据类型的值,引用数据类型的值),函数也是一种引用数据类型。

另一种写法
     let a = 1;
     setInterval(function(){
       console.log(a++);
     },1000);
    把函数作为一个参数传递给另一个函数
    setInterval是一个函数,他需要两个参数,第一个是函数,第二个是时间间隔。
    功能是:每隔指定时间间隔就去调用函数(第一份参数)

8.arguments对象

  arguments类数组对象。注意:arguments只是长的像数组,但它不是一个数组。
  arguments:当前调用函数时,传递给函数的实参。
  
  函数调用的过程就是实参向形参赋值的过程。实参会复制一份给形参。除了复制一份给形参外,它会给arguments一份。
  
  arguments是属于函的内部属性,它只能出现在函数内部,只能在函数内部使用,不能再函数外部访问。
  
  arguments的应用:求最值,做累加

  arguments和形参之间有一一对应的关系:
      当形参改变了,那么arguments里面的数据也会改变
      当改变了arguments里面的数据,形参也会发生改变
  arguments和形参之间没有一一对应关系:
      一个被修改,另一个不会变化。

  什么时候使用形参,什么时候使用arguments?
      1,当实参非常多时,写一堆的形参来接收,不方便,此时你就可以使用arguments
      2,当实参有特殊含义时,需要有一个特殊的标识,就可以使用形参来标识

  形参是函数的局部变量,arguments与形参是两个不同的变量(arguments[0]与x是两个变量)

9.函数返回多个值

   JS有两个容器:
     数组:[1,2,3,"hello",true]  获取某个元素,使用索引
     对象:{name:"wangcai",age:11}  name和age叫做key    "wangcai"和11叫做value

   默认情况下,函数只能返回一个值。
   如果你想返回多个值的话,那么你可以使用容器。
   在函数内部,把你要返回的多个值,都保存在数组元素(或者对象的属性)当中。最后,把这个数组(对象)返回。

10.执行上下文

每一个代码在调用时,都会产生一个执行上下文。
执行上下文的作用:给全局代码或局部代码提供数据。数据包含变量和函数。
                提供代码运行时需要的各个数据的值。

代码分成两类:函数代码,全局代码
    局部代码:在函数里面的代码叫局部代码。
    全局代码:一打开这个页面,就会执行的代码。默认进入的代码。

全局执行上下文,函数局部代码:
    当执行全局代码时,产生一个全局执行上下文。
       全局执行上下文只有一个。
    当执行函数代码时,产生一个函数执行上下文。
        每调用一次函数,都会产生一个函数的执行上下文件。函数执行上下文可以有N个。
  
执行上下文栈:
    全局执行上下文位于栈底,当调用一个函数,就产生一个局部地执行上下文,这个局部地执行上下文
    要压栈,当这个函数执行完,这个执行上下文就要出栈。

全局执行上下文组成:
    全局的变量和函数。
局部执行上下文组成:
    本函数内部定义的变量,arguments,内部定义的函数
    父级函数的执行上下文
父级函数:
  在定义f时,把直接f包起来的那个函数。如果f外层没有函数,则父级函数就理解全局代码。

执行上下文中保存数据,由两部分组成,1是自己定义的变量,2是父级函数的执行上下文。
对于全局代码来说,就只有第一部分:自己定义的变量,也就是全局变量。
先在自己定义的变量中找,找不到,就去父级函数的执行上下文去找,如果还找不到,会报错。

11.立即执行的函数表达式IIFE

不需要我们手动调用就可以执行的函数。
三种实现方式:
   方式一:(function(){})()
   方式二:(function(){}())
   方式三:在前面加上运算符+ - * % !
       +function(){
         console.log(1);
       }()
     
       -function(){
         console.log(1);
       }()
IIFE最好在后面加上;

12.闭包closure

定义:
   如果一个函数A内部定义了函数B,并且函数B引用了A中定义的变量。则称为函数B是一个闭包。
   注意:没有这一条“引用了外部了的变量”,不能称之为闭包。
   当一个函数调用完毕后,它里面变量所占的空间会被释放

好处:
   延长变量的生命周期

缺点:
   闭包会产生内存泄露

应用;
   实现节流函数
   让一个函数只能被调用指定次数
   点击li显示对应的数字

闭包结构;
   function的嵌套
   在子函数中,用到了index,而index是父函数的形参,就相当于是父函数的局部变量。
     也就是在子函数中使用了父函数定义的变量。
   Return 子函数。    

13. ES6中对函数的扩展