闭包、递归

143 阅读4分钟

函数this的三种指向

环境对象 this:谁调用我,this就指向谁

说人话:this就相当于中文中的我,谁说出来这个字,这个字就代表谁

this指向取决于函数的调用,函数有三种调用函数

(1)普通函数:函数名() this->window

(2)构造函数:new 函数名()this->new创建的实例对象

(3)对象方法:对象名.方法名() this->对象

一句话总结:this指向三选一,先找new,后找点

function fn(){
            console.log(this);
        }
        //(1)普通函数
        fn//this指向window
        //(2)构造函数
        new fn()//this->new创建的实例


        let obj = {
            name : '张三',
            eat :fn
        }
        // (3)对象方法
        obj.eat()//this->对象
<script>
测试题
        /* 
        环境对象 this : 
            普通函数;  
            对象方法:   
            构造函数; 
               
        */

      //作用域链
      let obj = {
        name: "张三",
        eat: function() {
          console.log(this)
          function fn() {
            console.log(this) 
          }
          fn()
        }
      }

      let eat = obj.eat
      obj.eat()
    </script>

2.函数的上下文调用

 1.环境对象 this : 谁调用我,我就指向谁
        普通函数;  函数名()this->window
        对象方法:  对象名.方法名()this->对象
        构造函数;  new 函数名() this->new创建的实例对象
    ***默认情况下,函数内的this是固定的,无法被修改
    ***如果你想要动态的修改函数内部的this指向,则需要使用上下文调用方法
        *上下文:函数作用域  
        *上下文指向:修改函数作用域内部this指向

    2.上下文调用 : 修改函数内部this
        2.1 函数名.call()
        2.2 函数名.apply()
        2.3 函数名.bind()
        

2.1函数名.call(修改的this,参数1,参数2.。。。)

   function fn(a,b){
        console.log(this);
        console.log(a+b);
    }
   fn()
   fn(10,20)
   //函数名.call(修改的This,参数1,参数2)
   fn.call({name:'张三'},10,20) //值传给this,

image.png

使用场景 万能检测数据类型

1.typeof数据:检测数据类型,但是有两种数据类型无法检测 *type无法检测 数组与null ,得到的都是‘object’

2.万能数据类型检测:Object.prototype.toString.call(数据) 最后得到的是 【object 数据类型】

** 原理**

// 万能的数据类型检测原理

    // (1)Object.prototype.toString()内部会返回this的数据类型,得到固定格式字符串‘[object  数据类型 ]’
    
    // (2)使用object原型中的toString()要想得到数据类型,只需要把this修改成你想要检测的对象
            

2.2函数名.apply(修改的this,数组/伪数组)

  //只能传参为数组,或者伪数组,否则会报错
  //apply会自动遍历数组和伪数组,然后逐一传参
   fn.apply({name:"李四"},[50,60])  
            

使用场景

2.2.1伪数组转真数组

   <script>
    /* 
    apply()场景 :伪数组转真数组

    1.伪数组:有 数组三要素(下标、元素、长度)但是不能使用数组的方法
        伪数组本质是 对象
    */


    let obj = {
        0 :10,
        1:20,
        2:30,
        length:3
    }
    // 需求:有时候伪数组  想要使用 真数组的方法 ,就需要把伪数组转成真数组
    // (1)把伪数组的元素取出来,push到真数组中
    let arr = []
    // arr.push(obj[0],obj[1],obj[2])
    // console.log(arr);

    // (2)收到写循环遍历数组
    for(let i = 0;i<obj.length;i++){
        arr.push(obj[i])
    }

    // (3)arr.push.apply(arr,伪数组)
    // 这里使用apply不是为了修改this,而是借助传参特点:自动遍历伪数组/数组传参,所以第一个参数应该写arr(保持this不变)
    // arr.push.apply(arr,obj)
    // console.log(arr)
    arr.push.apply(arr,obj)
    console.log(arr);
    // ES6:伪数组转真数组,固定静态方法  Array.from(伪数组)
    **let newArr = Array.from(obj)
    console.log(newArr)**
    </script>

2.2.1伪数组转真数组 求数组的最大值

<script>
        /* 
        apply场景:求数组的最大值
        */
       let arr = [10,20,3040,50]
        
        //(1)js基础
        let max = arr[0]
        for(let i = 0;i <arr.length;i++){
            if(arr[i]>max){
                max = arr[i]
            }
        }
        console.log(max);
        // (2)js高级:Math.max()
        // let max1 = Math.max(arr[0],arr[1],arr[2],arr[3])
        let max1 = Math.max.apply(Math,arr)
        console.log(max1);

        // (3)ES6:Math.max(...arr)
        let max2 = Math.max(...arr)
        console.log(max2);

2.3函数名.bind(修改的this)

blind()不会立即执行函数,而是得到修改this的新函数

    *blind()一般修改:定时器函数、事件处理函数             
   
   //(3)函数.blind(修改的this)
   // blind不会立即执行函数,而是得到一个修改this之后的新函数 
   let newFn = fn.blind({name:'王五'})//这里不能传参,因为会把参数定死
   newFn(20,30)
            

3. call 和 apply 和 bind三者区别

相同点:都可以修改this指向
        不同点:
            (1)传参方式不同:call是单个传参,apply是数组/伪数组传参
            (2)执行机制不同:call和apply会立即执行函数,bind不会立即执行而是得到修改this的新函数(一般修改定时器函数)
            
            
            

4.递归(在函数中调用自己)

递归类似于循环,也要有结束的条件

<script>
        /* 
        1.递归函数:在函数中调用自己
            *递归类似于循环,也要有结束的条件

        2.递归应用:
            2.1浅拷贝与深拷贝
            2.2遍历dom树
        */    
        function fn(){
            console.log('哈哈');
            fn()
        }
        fn()

        // 双函数递归
        function fn1(){
            console.log('呵呵');
            fn2()
        }
        function fn2(){
            console.log('嘿嘿');
            fn1()
        }
        fn1()
    </script>

应用场景

4.1.浅拷贝与深拷贝

    3.浅拷贝与深拷贝  有两种实现方式
        3.1浅拷贝:拷贝地址,修改拷贝后的数据,对原数据有影响
        3.2深拷贝:拷贝数据,修改拷贝后的数据,对原数据没有影响
            深拷贝两种方式:(1)json方式:let newObj = JSON.parse(JSON.stringify(js对象))
                           (2)递归函数

QQ图片20220502213033.png 浅拷贝:拷贝地址

    // let newObj = obj

深拷贝:json深拷贝

把所有的数据都拷贝一份,如果里面有嵌套的内容,也会被拷贝一份,比如hobby
    (1)JSON.stringify(js对象):把js对象->json字符串(json底层会自动深拷贝)
      let json = JSON.stringify(obj)
    (2)JSON.parse(json字符串):json字符串->js对象
    let newObj = JSON.parse(json)
    */
    
    /* let newObj = JSON.parse(JSON.stringify(obj))
    newObj.name = '常常'
    console.log(obj,newObj); */
// 2.深拷贝函数封装
        function  kaobei(obj,newObj){
            // 遍历obj,把obj里面的数据  拷贝给newObj
            for(let key in obj){
                // 判断是不是数组,如果是数组还需要继续遍历拷贝
                if(obj[key] instanceof Array){
                    // (1)声明空数组
                    newObj[key] = []
                    // (2)递归遍历数组
                    kaobei(obj[key],newObj[key])
                }else if(obj[key] instanceof Object){
                    // (1)声明空对象
                    newObj[key] = {}
                    // (2)递归遍历数组
                    kaobei(obj[key],newObj[key])
                }else {
                    newObj[key] = obj[key]
                }
            }
        }
        // (1)创建一个空对象
        let newObj = {}
        // (2)调用拷贝函数
        kaobei(obj,newObj)
        newObj.name = '李四'
        console.log(obj,newObj);

5.闭包

1.闭包closure是什么

两个条件 a:函数 b:函数内部还要访问 其他函数的变量

(1)闭包是一个 访问其他函数内部变量 的函数

(2)闭包 = 函数+上下文引用

2.闭包作用:解决变量污染

 *实际开发中,闭包一般用于回调函数
let age = 20
  function fn(){
    // 局部变量
    let num = 0
    // fn1+num的组合就是闭包
    function fn1(){
      console.log(num);
      console.log(age);//age不能组成闭包
    }
    fn1()
  }
  fn()

3.在浏览器中调试闭包

document.querySelector('.btn').addEventListener('click', function () {
            // (1)获取输入框文本
            let text = document.querySelector('input').value
            // (2)模拟网络请求
            setTimeout(function () {
                alert(`${text}的搜索结果为123456`)
            }, 1000)
        })