函数介绍:

  • 把一段相对独立的具有特定功能的代码块封装起来,形成一个独立实体,就是函数,起个名字(函数名),在后续开发中可以反复调用
  • 函数的作用就是封装一段代码,将来可以重复使用

函数特点:

  • 函数声明的时候,函数体并不会执行,只要当函数被调用的时候才会执行。函数一般都用来干一件事情,需用使用动词+名词,表示做一件事情 tellStory sayHello

    一、函数的创建

    1-1 函数的定义方式

    1-1-1 函数声明

    1. function go(a){
    2. console.log(a)
    3. }

    1-1-2 函数表达式

    ```javascript var foo = function () {

}

  1. <a name="Tew8Z"></a>
  2. #### 1-1-3 函数声明和函数表达式的区别
  3. ```javascript
  4. 1.函数声明必须有名字
  5. 2.函数声明会函数提升,在预解析阶段就已创建,声明前后都可以调用
  6. 3.函数表达式类似于变量赋值
  7. 4.函数表达式可以没有名字,例如匿名函数
  8. 5.函数表达式没有变量提升,在执行阶段创建,必须在表达式执行之后才可以调用

下面是一个根据条件定义函数的例子:

if (false) {
    function f () {
        alert(1);
    }
}
f();
//函数声明如果写在if else中,不同浏览器会有不同的结果
//谷歌报错 :f is not a function
//ie8  alert(1)
//说明不同浏览器的预解析规则不太一样

不过我们可以使用函数表达式解决上面的问题:

var f

if (false) {
  f = function () {
    console.log(1)
  }
} 

//函数表达式不会像函数声明一样被强行提升

1-1-4 new Function()

//所有的函数实际上都是Function的构造函数创建出来的实例对象
var f1 = new Function("num1","num2","return num1+num2");

1-2 函数的返回值

  • 返回值就是函数的执行结构,js中函数可以没有返回值,使用return语句之后,返回值后面的语句不会执行
              function show(){
             return "hello world"
             console.log(3)
         }    
         console.log(show())     //输出:hello world
    

1-3 函数也是对象

function f1(){}

1.所有函数都是 Function 的实例,所以函数也是对象(对象拥有 __proto__ 属性),函数拥有prototype属性
2.console.dir(f1)    打印函数
3.所有的函数实际上都是Function的构造函数创建出来的实例对象
  f1.__proto__ == Function.prototype    //结果为true,函数的__proto__指向了Function的prototype
4.console.dir(Function)   Function既是一个函数也是一个对象,其实var Function = new Function(),这里就不讨论了。
  Function.prototype.__proto__ == Object.prototype   //Function的prototype中的__proto__最终都指向了Object的原型
5.console.log(Object.prototype.__proto__);  //null

hanshuyuanxing.png

二、函数的传参

2-1 函数参数不定参(函数可以不传参或多传参)

  • 函数内部有个arguments 对象,接收函数传递过来的参数,是一个类数组对象

         function go(a,b,c){
             console.log(a)
             console.log(a+b)
             console.log(a+b+c)
         }
         go()   //不传参   undefined NaN
         go(10)    //一个参数  10 NaN NaN
         go(10,20,30,50)   //多余参数  10,30,60
    

    2-2 默认赋值

  • 预先给函数参数赋值

    function go(type="get"){
             console.log(type)
         }  
         go()
         go("post")
    

三、重载

3-1 arguments的使用

JavaScript中,arguments对象是比较特别的一个对象,实际上是当前函数的一个内置属性。也就是说所有函数都内置了一个arguments对象,arguments对象中存储了传递的所有的实参。arguments是一个伪数组,因此及可以进行遍历

3-2 模拟重载

  • 根据传入参数不一样,动态决定调用哪种方法。
  • JavaScript不支持重载,因为重复声明,覆盖掉了,可以用arguments对象模拟重载 ```javascript 1.声明覆盖 function go(a,b){
         console.log(a+b)
     }
     function go(a){
         console.log(a)
     }
     go(10,20)    //10
    

2.模拟重载 function show(){ if(arguments.length==2){ console.log(arguments[0]+arguments[1]) }else if(arguments.length==1){ console.log(arguments[0]) } } show(10) show(10,20)


<a name="VpQ8B"></a>
## 四、解构赋值

- ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构

old:
```javascript
let a = 1;
let b = 2;
let c = 3;

new:

let [a, b, c] = [1, 2, 3];

上面代码表示,可以从数组中提取值,按照对应位置,对变量赋值。
本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。

     <script>
        /* 解构 */
       /* var obj={name:"xxx",age:18,sex:"男"}
        var {name,age,sex}=obj;
        console.log(name) */

       var {name,age,sex}={name:"xxx",age:18,sex:"男"}
        console.log(name)
        console.log(age)
        console.log(sex)
     //不完全解构
        let [a, [b], d] = [1, [2, 3], 4];
        console.log(a)
        console.log(b)
        console.log(d)
    </script>

五、箭头函数和回调函数

5-1 箭头函数

5-1-1 箭头函数的使用

        var go=x=>x;
        console.log(go(10))  //10

        var test=y=>console.log(y);
        test(20)    //20
       //两个参数
        var getInfo=(x,y)=>{
            console.log(x)
            console.log(x+y)
        }

5-1-2 箭头函数的作用

解决函数内部this关键字指向问题,当函数直接调用时,this指向window

       test.onclick=function(){
           console.log(this)
           go()
        }
             function go(){
                console.log(this)    //window调用
            }
            test.onclick=go;       //test调用

用箭头函数解决this指向问题

      var test=document.getElementById("test");
        test.onclick=function(){
            setTimeout(()=>{
                console.log(this)
            },300)
        }

5-1-3 箭头函数的特点

  • 普通function的提升在变量提升之前,箭头函数没有函数提升
  • 箭头函数没有this,super,函数体内部的this对象就是定义的时候所在的对象而不是使用时所在的对象
  • 箭头函数没有arguments对象,如果要用,可以使用rest参数
  • 箭头函数不能作为构造函数,不能被new
  • 箭头函数没有原型(prototype)
  • 不能通过call、bind、apply方法修改箭头函数中的this
  • 不可以使用yield命令,因此箭头函数不能做Generator函数

    5-2 回调函数

  • 就是将函数作为参数传递给另外一个函数
  • 作用:可以获取函数内部的值

ajax中,常使用回调函数进行处理,代码就可以进行其他任务,而无需等待

<script>
        function http(callback){
            var data={"name":"xxx",age:18};
            callback(data);
        }
        function handleData(res){
            console.log(res)
        }
        http(handleData);
    </script>

六、this指向

  • 当函数直接调用时,this指向window

    6-1 this的指向

    普通函数中的this
    function test(){
              console.log(this);  //window
          }
          test();
    

对象函数中的this

 var obj={
       name:"html",
       sayName:function(){
           console.log(this.name)
       }
   }
   var skill={
       name:"javascript",
       saySkill(){
           console.log(this.name)
       }
   }
   obj.sayName()      //html
   skill.saySkill()     //javascript

var obj = {
        name:"zhangsan",
        say:function(){
            console.log(this,this.name)  //obj對象
        }
    }
    obj.say();

构造函数中的this

 function Person(name,age){
            //this就是new出來的p對象
            this.name = name;
            this.age = age;
        }
        var p = new Person("xx",19);

事件中的this

var btn = document.querySelector("#btn");
    btn.onclick = function(event){
        console.log(this); //當前綁定事件的元素
    }

定时器中的this

 var obj2 = {
        name:"zhangsan",
        say2:function(){
           //this是 obj2對象
           setTimeout(() => {
               console.log(this)  //obj2
           }, 1000);
        },
        say3:function(){
            //this 是obj2對象
            //定時器,如果是普通函數,this是window
            setTimeout(function(){
                console.log(this)  //window
            },1000)
        }
    }
    obj2.say2();
    obj2.say3()

箭头函数中的this

声明函数时所在作用域的this

闭包中的this:指向window

var obj3 = {
        eat:function(){
            return function(){
                console.log(this,"0000");
            }
        }
    }
    obj3.eat()();

6-2 改变函数内部的关键字指向

bind() , call() , apply() , 箭头函数

  • bind() 改变函数内部this执行的上下文环境

          var name="zhangsan";
           var obj={
               name:"lisi"
           }
           var test=function(){
               console.log(this.name)
           }.bind(obj);
           test()    //输出:lisi  不用bind 输出:zhangsan
    
  • call() 调用的时候改变this关键字指向问题

    call(thisObj,params)
    //第一个参数指向函数内部的this,之后的参数是需要被传入函数的参数
    
          var li="lisi";
          var obj={
              name:"zhangsan"
          }
          var test=function(){
              console.log(this.name)
          }
          test.call(obj)  //zhangsan
    
        function sayName(label){
             console.log(label+":"+this.name)
         }    
         var name="window";
         var cheng={
             name:"cheng"
         }
         var wang={
             name:"wang"
         }
         sayName()     //undefiend:window
         sayName("wiow")    //wiow:window
         sayName.call(cheng,"chengchao")     //chengchao:cheng
         sayName.call(wang,"wangwu")     //wangwu:wang
    
  • apply()

fun.apply(thisArg, [argsArray])传递的是数组

       function go(name,age){
            console.log(this.name+":"+this.age)
        }
        var zhang={
            name:"zhangsan",
            age:18
        }
        var li={
            name:"lisi",
            age:21
        }
        go.call(zhang,"zhang",111)     //输出:zhangsan:18
        go.apply(li,["li",24])        //输出:lisi:21

6-3 三者区别

bind改变函数的上下文执行环境,不会马上执行
而被call和apply绑定的函数会马上执行五、函数 - 图2

七、自调函数

7-1 1.0

(function(){
    console.log("自调函数1.0")
})()

7-2 2.0

(()=>{
   console.log("自调2.0")
}())

image.png

7-3 自调函数的好处

我们自己写的代码不会对外部的js代码有任何影响

//我们在开发的时候会引入很多第三方的js文件,有时候我们并不直到在第三方js中谢了什么函数
//假如第三方js已经写好了一个函数如下:
function foo(){ 
  console.log("aa");
} 

//而我们不知情,又写了下面代码
function foo(){ 
  console.log("bb");
} 

//这个时候我们自己写的代码就会把第三方的js代码给顶掉
//而如果使用自调用函数则没有这个情况
(function(){
    function foo(){
        console.log("这是我自己的foo");
    }
    foo();
}());
foo();
//推荐在以后的开发工多使用自调用函数,把所有的js代码都放在自调用函数中

7-4 自调用案例(沙箱)

<div>这是div</div>
<div>这是div</div>
<div>这是div</div>
<p>这是p</p>
<p>这是p</p>
<p>这是p</p>


<script>
  var getTag = 10;
  var dvObjs = 20;
  var pObjs = 30;

  //此处如果不加沙箱,在调用getTag方法的时候会报not function错误,原因是getTag()这个函数会被提升
到script代码块的最前面

    (function () {
    //根据标签名字获取元素
    //自调用函数中的函数是私有的,在外部不可以被访问
    function getTag(tagName) {
      return document.getElementsByTagName(tagName)
    }
    //获取所有的div
    var dvObjs = getTag("div");
    for (var i = 0; i < dvObjs.length; i++) {
      dvObjs[i].style.border = "2px solid pink";
    }
    //获取所有的p
    var pObjs = getTag("p");
    for (var i = 0; i < pObjs.length; i++) {
      pObjs[i].style.border = "2px solid pink";
    }
  }());

  console.log(getTag);
  console.log(dvObjs);
  console.log(pObjs);
</script>

7-5 自调函数注意点

// 问题1:如果存在多个自调用函数要用分号分割,否则语法错误
// 下面代码会报错
(function () {
}())

(function () {
}())

// 问题2:当自调用函数 前面有匿名函数时,会把自调用函数作为参数(其实会匿名函数和自调用函数中的空格去掉)
var a = function () {
  alert('11');
}

(function () {
  alert('22');
}())

// 代码规范中会建议在自调用函数之前加上分号
;(function () {
}())

;(function () {
}())

7-6 自调函数中的全局变量

<script>
    //沙箱---小环境
    (function () {
        var num=10;  
    })();
    console.log(num); //无法访问
</script>

//虽然在自调用函数中的变量是局部变量,但是我们仍然可以通过window将自调用函数中的局部变量变为全局变量
<script>
    //沙箱---小环境
    (function () {
         num=10;  //这个地方相当于window.num = 10;
    })();
    console.log(num);
</script>

//虽然在自调用函数中的变量是局部变量,但是我们仍然可以通过window将自调用函数中的局部变量变为全局变量
//将局部变量变成全局变量更常见的做法如下:
(function (window) {
    var num=10;//局部变量
    window.num=num;//全局变量
})(window);
console.log(num);

7-7 自调函数中封装Random对象

//通过自调用函数产生一个随机数对象,在自调用函数外面,调用该随机数对象方法产生随机数
(function (window) {
  //产生随机数的构造函数
  function Random() {
  }
  //在原型对象中添加方法
  Random.prototype.getRandom = function (min,max) {
     return Math.floor(Math.random()*(max-min)+min);
  };
  //自调用函数中的普通函数是私有函数,只能够在当前自调用函数内部使用
  //要想把这个方法暴露给外部,可以设置给window,或者设置给Random函数
  function test1(){
     console.log("test1");
  }
  //把Random对象暴露给顶级对象window--->外部可以直接使用这个对象
  window.Random=Random;
})(window);
//实例化随机数对象,这边能够使用Random函数,原因是Random函数已经被绑定到了window对象上
var rm=new Random();
//调用方法产生随机数
console.log(rm.getRandom(0,5));
test1();  //无法访问,test1()是私有函数

八、闭包

闭包:函数中嵌套函数,定义在一个函数内部的函数,静态保存所有了父级作用域的内部函数。

8-1 特点:

  • 内部函数可以使用外部函数中声明的变量
  • 延长外部函数中变量的作用域链
  • 闭包可能产生内存泄漏的问题

    8-2 闭包的分类

  • 函数模式闭包

    //1.函数模式的闭包
    function f1(){
      var num = 10;
      return function(){
          console.log(num);
      }
    }
    var ff = f1();
    ff();
    
  • 对象模式闭包

    //2.对象模式的闭包
    function f3(){
      var num = 10;
      return {
          age:num
      }
    }
    var obj = f3();
    console.log(obj.age);
    

    8-2 用途:

  1. 缓存,延长变量的作用域链
  2. 面向对象中的对象
  3. 实现封装,防止变量跑到外层作用域中,发生命名冲突 ```javascript //闭包的主要作用:缓存数据,延长变量的作用域链 //1.普通的函数 function f1() { var num = 10; num++; return num; } console.log(f1()); console.log(f1()); console.log(f1());

//2.函数模式的闭包 function f2() { var num = 10; return function () { num++; return num; } } var ff = f2(); console.log(ff());//11 console.log(ff());//12 console.log(ff());//13

//可以发现每次ff执行的时候,num的值发生了累加(其实是对应的内存未被释放)

<a name="1TPJ7"></a>
#### 8-3 危害:

1. 闭包有一个非常严重的问题,那就是内存浪费问题,这个内存浪费不仅仅因为它常驻内存,更重要的是,对闭包的使用不当的话会内存泄漏。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
1. 性能问题,使用闭包时,会涉及到跨作用域访问,由于闭包内部变量优先级高于外部变量,所以多查找作用域链中的一个层次,就会在一定程度上影响查找速度。
<a name="tsMQk"></a>
## 九、函数防抖和函数节流
<a name="8Lkog"></a>
### 9-1 函数防抖
**定义**:函数调用n秒后才会执行,如果函数在n秒内被调用的话则函数不执行,重新计算执行时间。函数去抖主要避免快速多次执行函数(操作DOM,加载资源等等)给内存带来大量的消耗从而一定程度上降低性能问题。

**应用场景**:

1. 监控键盘keypress事件,每当内容变化的时候就向服务器发送请求
1. 在页面滚动的时候监控页面的滚动事件,会频繁执行scroll事件
1. 监控页面的resize事件,拉动窗口改变大小的时候,resize事件被频繁的执行

**函数去抖的封装**:
```javascript
<input type="text" oninput="textInput()">
    <script>
        //模拟向服务器发送请求的方法
        function ajax() {
            console.log("发送请求")
        }

        //去抖函数的封装
        function debounce(method, delay) {
            var timer = null;
            return function () {
                var context = this
                var args = arguments;
                clearTimeout(timer);
                timer = setTimeout(function () {
                    method.apply(context, args);
                }, 1000);
            }
        }

        var fn = debounce(ajax,1000);

        //用户先输入一个 a,再输入一个b
        function textInput() {
            //fn函数肯定会调用两次
            //当第一次调用fn函数的时候,先清除之前的定时器,再开启一个定时器,定时器的作用就是1秒后触发指定的方法
            //当第二次调用fn函数的时候,先清除之前的定时器,再开启一个定时器,定时器的作用就是1秒后触发指定的方法
            fn();
        }
    </script>

9-2 函数节流

定义:函数预先设定一个执行周期(或者节流阀),当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新周期。

应用场景

  1. 上拉下拉刷新,每拉动一次彻底完毕之后才可以下一次拉动
  2. 图片轮播动画,每一张图片动画完成之后才开始下一个图片的动画

代码封装

function throttle(method,duration){
        var  begin = new Date();
        return function(){
            var context = this
            var args=arguments
            var current=new Date();
            if(current-begin>=duration){
                 method.apply(context,args);
                 begin = current;
            }
        }
}
function resizehandler(){
    console.log(++n);
}
window.onresize=throttle(resizehandler,500);

十、递归函数

10-1 递归执行模型

function fn1 () {
  console.log(111)
  fn2()
  console.log('fn1')
}

function fn2 () {
  console.log(222)
  fn3()
  console.log('fn2')
}

function fn3 () {
  console.log(333)
  fn4()
  console.log('fn3')
}

function fn4 () {
  console.log(444)
  console.log('fn4')
}

fn1()

10-2 举例:求n个数字的和

//递归实现:求n个数字的和   n=5--->  5+4+3+2+1

//函数的声明
function getSum(x) {
    if(x==1){
        return 1;
    }
    return x+getSum(x-1);
}
//函数的调用
console.log(getSum(5));

10-3 递归应用场景

  • 遍历DOM树
    //从根节点开始遍历
    function fromRoot(ele){
      //找到根节点的所有孩子
      var children = ele.children;
      //遍历根节点的每个孩子
      for(var i=0;i<children.length;i++){
          //获取每一个孩子
          var c = children[i];
          //打印节点名称
          console.log(c.nodeName);
          //如果每一个孩子下面还有子节点,则继续将每一个孩子看成根节点继续遍历孩子下面的子孩子
          c.children && fromRoot(c);
      }
    }
    fromRoot(my$("box"));
    

    十一、高阶函数

    11-1 函数作为参数

    ```javascript function f1(fn) { console.log(“f1的函数”); //fn是参数,最后作为函数使用了,函数是可以作为参数使用 fn();//此时fn当成是一个函数来使用的 }

//1.传入匿名函数 f1(function () { console.log(“我是匿名函数”); });

//2.命名函数 function f2() { console.log(“f2的函数”); } //函数作为参数的时候,如果是命名函数,那么只传入命名函数的名字,没有括号 f1(f2);


```javascript
function eat (callback) {
  setTimeout(function () {
    console.log('吃完了')
    callback();//回调
  }, 1000)
}

eat(function () {
  console.log('去唱歌')
})
function f1(fn) {
   setInterval(function () {
       console.log("定时器开始");
       fn(); //回调
       console.log("定时器结束");
   },1000);
}

f1(function () {
   console.log("好困啊,好累啊,就是想睡觉");
});

11-2 函数作为返回值

function f1() {
    console.log("f1函数开始");
    return function () {
        console.log("我是函数,但是此时是作为返回值使用的");
    }
}

var ff=f1();
ff();

11-3 判断和获取变量类型的方式

//1.获取变量类型
var num=10;
console.log(typeof num);//获取num这个变量的数据类型
var obj={};//对象

//2.判断这个对象是不是某个类型的
console.log(obj instanceof Object);
console.log(obj.constructor == Object);//这种方式在函数的原型被改掉后会不适用

//3.获取变量类型的第三种方案
//此时输出的是Object的数据类型   [object Object]
console.log(Object.prototype.toString());

//获取某个对象的数据类型的样子
//Object.prototype.toString.call(对象);//此时得到的就是这个对象的类型,注意点是这个方法无法获取自定义函数的类型

//输出的数组的数据类型      [object Array]
console.log(Object.prototype.toString.call([]));

var arr=[10,20,30];
console.log(Object.prototype.toString.call(arr));

案例:判断某个对象是否是指定类型的对象

//判断某个对象是否是指定类型的对象
function genFun (type) {
  return function (obj) {
    //Object.prototype.toString.call(obj) 输出obj对象的数据类型
    return Object.prototype.toString.call(obj) === type
  }
}

var isArray = genFun('[object Array]')
var isObject = genFun('[object Object]')

console.log(isArray([])) // => true
console.log(isObject({})) // => true