JavaScript函数的进阶

140 阅读6分钟

一、 arguments 与rest与 instanceof关键字

  • arguments:他是一个伪数组,他是用来获取所有的实参,他用于实参不确定的函数

  • rest剩余参数:他是一个真数组,他是用来获取函数剩余的所有实参

  • instanceof: 运算符 作用:检测构造函数的原型在不在实例对象的原型链中

二、 this的指向

1:this是什么

  • this又叫环境对象,this是不确定,因为this是谁调用我,this就指向谁: 2:函数三种的调用方式
  • 2.1 普通函数的调用:this的指向的是window
  • 2.2 对象的调用:this的指向的是对象
  • 2.3 构造函数的调用:this指向的是new创建的实例对象。 3:函数三种调用方式的共同点
  • 3.1 函数的三种调用方式有一个共同点:就是this无法修改

三、函数的上下文调用 call,apply,bind

1:什么是函数的上下文调用

  • 概念:可以理解为就是this的作用域 2:函数的上下文调用有什么用
  • 作用:函数上下文调就是用于修改函数中的this的指向,他有三个方法call,apply,bind用来

方法1 call()

一 语法格式: 函数名.call(修改this,参数1,参数2)

  <script>
        function fn(a,b){
            console.log(this);
            console.log(a+b);
        }
        fn(10,20)
        // 上下文调用:函数.call(修改this,形参1,形参2)
        fn.call({name:'小明'},20,30)
    </script>

image.png

二 call的经典应用场景。

1:call最重要的应用在数据检测

  • 以前我们检测数据类型,常用的是typeof,但是他有一个缺陷,typeof检测null和Array是检测不出来的,一律显示Object 2:万能检测数据法

  • 1:每一个对象都有一个toString方法,如果这个toString未被覆盖,他会返回一个固定的格式,[object type],其中type就是对象的类型,所以用toString方法来检测数据类型,因为万物都是对象,但是根据原型链的就近查找原则,他首先不会去找原型Object,而是先查找自身是否有toString方法,但是数组也有toString方法,所以他找不到Objec的toString那里去,但只有Object的toString方法才能检测数据。

  • 2: 所以要指定是哪个原型身上的toString,Object.prototype.toString才可以,但

  • 3: Object.prototype.toString去检测,但this的指向始终执行这个对象的方法所以需要通过call来修改他的this,把this改成指向我要检测数据类型的值

let num=10
let str='10'
Object.prototype.toString.call(str)
Object.prototype.toString.call(num)

方法2 apply()

语法格式:函数名.apply(修改this,数组/伪数组)

  • apply()跟call()方法,他的不同就是传参的方式,他是传数组或者伪数组。他适用于很多个参数传递的方式
  • apply会自遍历数组/伪数组,然后按照元素顺序逐一传参。
   function fn(a,b){
            console.log(this);
            console.log(a+b);
        }
        fn(10,20)
        // 上下文调用:函数.call(修改this,形参1,形参2)
        fn.call({name:'小明'},20,30)
         // 上下文调用:函数.apply(修改this,数组/伪数组)
        fn.apply({name:'小红'},[100,120,130])

image.png

二 apply的应用场景一伪数组转真数组

伪数组

什么是伪数组,自己创造一个伪数组。

概念:有数组的三要素(元素,长度,下标),但是没有数组的方法 为什么伪数组用不了数组的方法,是因为伪数组的原型对象并不是Array,所以伪数组的本质不是数组而是对象

image.png

伪数组转真数组

在ES5中用apply来进行伪数组转真数组

这里重点不是利用apply来修改this指向,而是利用他的数组传参和自动便利的特点

   // 伪数组
      let fakeArr={
          0:20,
          1:30,
          2:40,
          3:50,
          4:60,
          length:4
      }
      console.log(fakeArr);
// ES5
let arr1=[]
//这里不是利用apply修改this指向,而是利用他的数组传参和自动便利的特点
arr1.push.apply(arr1,fakeArr)
console.log(arr1,"利用apply来伪数组转真数组");

image.png

在ES6中用Array.from(伪数组),一键转换。

        // 伪数组
      let fakeArr={
          0:20,
          1:30,
          2:40,
          3:50,
          4:60,
         
          length:4
      }
      console.log(fakeArr);

// ES6 一键转换
let arr2=Array.from(fakeArr)
console.log(arr2,"222","利用Array.from伪数组转真数组");

二 apply的应用场景二求最大值和最小值

这里并不是利用apply来修改this指向,而利用apply的数组传参,自动遍历数组的特点

  let arr=[1,4,5,6,6,3,9]
        //    求最大值
        let maxArr=Math.max.apply(Math,arr)
       //    求最小值
        let minArr=Math.min.apply(Math,arr)
        console.log(maxArr);
        console.log(minArr);
       //apply的语法学起来很困难,现在es6通过展开运算符可以解决了
       let arr1= Math.max(...arr)
         console.log(arr1); 

方法3 bind()

一 语法格式:函数.bind(修改this,参数1,参数2)

概念:bing不会立即调用函数,而是得到一个修改this之后的新函数

二 bind的应用场景一般用于修改不会立即执行的函数中this

常用于修改定时器的this,默认情况下定时器的this指向window

    <script>
     
     function fn(a,b){
            console.log(this);
            console.log(a+b);
        }
         // bing不会立即调用函数,而是得到一个修改this之后的新函数 
        //  bind一般用于修改不会立即执行的函数中this:如定时器,事件处理函数
         let fn1=fn.bind({name:'小黑'},2,4)
     
         fn1(10,6)
         setTimeout(function(){
             console.log(this);
         }.bind({name:"小黑"},10,30),3000)
        
    </script>

三 总结三种方法的区别

1、传参的方式不同:

  • call是逐一传参,apply是数组/伪数组传参,传参的方式不同 2、执行的机制不同

  • call和apply是立即执行函数,bind不会立即执行函数

  • call和apply用一次,改一次

  • bind修改一次终生有效 3、注意点

  • 修改this必须是引用类型,如果是string、number、boolean,则js编译器会自动转成对应的对象,如果是undefined和null,则修改无效。

四、闭包

一 、闭包的概念:

  • :一个函数对其周围状态的引用捆绑在一起或者说函数被引用包围,这样的组合就是闭包,也就是说闭包让你可以在一个内层函数中访问到其外层函数的作用域,在JavaScript中每创建一个函数,闭包就会在函数创建的同时被创建出来。

  • 总结起来就是:闭包是使用其他函数内部变量的函数(闭包=函数+其他函数内部变量) 二、闭包的作用

  • 闭包是用来解决全局变量污染的问题

三、写一个简易的闭包

 <script>
    // 闭包:闭包是一个使用了其他函数内部变量的函数(函数+其他函数内部变量)
        // 判断闭包
        // 闭包作用
        function fn(){
            let num=1
            function test(){
               console.log(num);
            }
            test()
        }
        fn()
    </script>

判断闭包1:从代码的角度来说满足两个条件:函数+使用其他函数局部变量

判断闭包2:从浏览器控制台,通过断点调试,出现Closure就是闭包

image.png

五、递归函数——深拷贝和浅拷贝

一 、什么是递归

概念:函数内部自己调用自己,他类似于我们的循环

他的应用场景:一般主要用递归函数来实现深拷贝和浅拷贝,第二个主要用递归函数实现dom树的遍历

二 深拷贝和浅拷贝

2.1 什么是浅拷贝:

浅拷贝:拷贝的是地址,修改拷贝后的数据对原数据有影响。

//浅拷贝
   <script>
  let obj={
        name:'张三',
        age:40,
        sex:'男',
        hobby:['学习','上课','跑步'],
        student:{
             name:'小张三',
             age:20,
             hobby:['听歌','吃饭','睡觉']
        }
    }
    // 浅拷贝
    let obj1=obj
    obj1.hobby[0]='打豆豆'
    obj1.name="张三三三三"
    console.log(obj,"原本的数据");

image.png

2.2 什么是深拷贝

深拷贝拷贝的是数据,修改拷贝后的数据对原数据没有影响。

实现深拷贝的方式

json方式(推荐)

    <script>
  let obj={
        name:'张三',
        age:40,
        sex:'男',
        hobby:['学习','上课','跑步'],
        student:{
             name:'小张三',
             age:20,
             hobby:['听歌','吃饭','睡觉']
        }
    }
    // 深拷贝
    // (1)先把对象浅拷贝
    // let jsonStr=JSON.stringify(obj)

    //(2)在把json字符串转成js对象:JSON.parse(json字符串)
    // let obj1=JSON.parse(jsonStr)
    // 简写
    let  obj1=JSON.parse(JSON.stringify(obj))
    obj1.hobby[0]='打架'
     console.log(obj,"原本的数据");
     console.log(obj1,"我是拷贝后的数据");
    </script>

image.png

递归方式实现深拷贝

  <script>
  let obj={
        name:'张三',
        age:40,
        sex:'男',
        hobby:['学习','上课','跑步'],
        student:{
             name:'小张三',
             age:20,
             hobby:['听歌','吃饭','睡觉']
        }
    }
    // newObj:拷贝后的新对象, obj:要拷贝的数据
    function kaobei(newObj,obj){
          for(let key in obj){
 //如果是值类型,则直接拷贝数据,如果是key是引用类型,继续遍历引用
            if(obj[key] instanceof Array){
                newObj[key]=[]
                kaobei(newObj[key] ,obj[key])
 //如果是值类型,则直接拷贝数据,如果是key是引用类型,继续遍历引用
            }else if(obj[key] instanceof Object){
                // 对象
                newObj[key]={}
                kaobei(newObj[key],obj[key])
            }else{
                // 值类型
                newObj[key]=obj[key]
            }
          }
    }
    let obj1={}
    // 调用数组
    // obj1是对象, obj是对象
   kaobei(obj1,obj)
   obj1.hobby[0]='打架'
console.log(obj,obj1);
    </script>