函数this三种指向函数调用上下文模式递归闭包

Posted cs1330

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了函数this三种指向函数调用上下文模式递归闭包相关的知识,希望对你有一定的参考价值。

函数的三种调用方式(this指向)

函数三种调用方式:普通函数    对象方法    构造函数

        重点:理解this关键字的作用:谁调用这个函数,this就指向谁

无法修改this指向的三种:

        1.普通函数调用:this指向的就是window

        2.对象方法调用:this指向的就是这个对象

        3.构造函数调用:this指向的就是new关键字创造的对象

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
       /* 
        函数三种执行模式 : 全局函数 、 对象方法 、 构造函数
            this : 谁 `调用` 我,我就指向谁
            1. 全局函数 : this指向window
            2. 对象方法 : this指向对象
            3. 构造函数 : this指向new创建的空对象
         */
         // this环境对象:谁调用我,我就指向谁

        // 普通函数调用   this指向的就是window
        function fn() 
            console.log(this);
        
        fn()

        // 对象方法调用:对象名.方法名()   this指向就是这个对象
        let obj = 
            name: '陈爽',
            age: 19,
            saiHia: function () 
                console.log(this);
            
        
        obj.saiHia()

        // 构造函数调用:new  函数名()   this指向new 创建的实例对象
        function Fn() 
            console.log(this);
        
        let s = new Fn()
    </script>
</body>

</html>

函数调用的上下文模式(可以修改函数中this指向)

函数上下文的三个方法:call()   、 apply()   、 bind() ,他们定义在Function构造函数中

函数执行上下文模

作用可以修改this指向

异同点:都可以修改函数中的this指向

不同点:传参方式不同

call:函数名.call(this修改后的指向,参数1,参数2....)

        适用于只有一个参数的函数

        应用场景:伪数组排序、检测数据类型

   // call可以修改this指向
        // 语法:函数名.call(修改的this,参数1,参数2)
        function fn(a, b) 
            console.log(this);
            console.log(a + b);
        
        fn.call( name: '张子沁' , 30, 20)
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>


        // 检测数据类型固定格式语法:object.prototype.toString.call(数据)
        console.log(Object.prototype.toString.call('123'));
        console.log(Object.prototype.toString.call(12312));
        console.log(Object.prototype.toString.call(true));
        console.log(Object.prototype.toString.call(undefined));
        console.log(Object.prototype.toString.call(null));
        console.log(Object.prototype.toString.call([10, 20, 30]));
        console.log(Object.prototype.toString.call(function ()  ));
        console.log(Object.prototype.toString.call( name: '张三' ));
    </script>
</body>

</html>

apply() :函数名:apply(this修改后的指向,伪数组/数组)

适用于多个传参的函数

应用场景:伪数转真数组、求数组最大值

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        // apply上下文调用:修改this指向   传参不同
        // 语法:函数名.apply(修改的this指向,数组/伪数组)
        function fn(a, b, c) 
            console.log(this);
            console.log(a * b + c);
        
        fn.apply( name: '陈爽' , [30, 50, 50])
    </script>
</body>

</html>

伪数组转真数组

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>

        let obj = 
            0: 20,
            1: 30,
            2: 40,
            3: 90,
            4: 100,
            length: 5
        
        // console.log(obj);

        // 将伪数组转为真数组
        // ES6语法
        // let arr = Array.from(obj)
        // console.log(arr);


        // ES5语法
        // 创建一个空数组
        let Array = []
        // 函数名.apply(修改的this,数组/伪数组)
        // 第一个参数:不用修改this,因为this本来就指向Array
        // 第二个参数:obj,借助apply特点:自动遍历了伪数组,逐一传参
        Array.push.apply(Array, obj)
        console.log(Array);
    </script>
</body>

</html>

求数组最大值

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        let arr = [20, 40, 90, 10, 32, 90]

        // 排序法 // 最大值
        // arr.sort(function (a, b) 
        //     return b - a
        // )
        // console.log(arr[0]);

        // ES5语法
        // 第一个参数:Math,因为他this指向本身就Math,这里不用修改this指向,相当于不变
        // 第二个参数:arr,借助apply的特点,自动遍历数组里的元素,然后逐一传参,求出最大值赋值给max
        let max = Math.max.apply(Math, arr)
        console.log(max);

        
        // ES6语法
        let max3 = Math.max(...arr)
        console.log(max3);
    </script>
</body>

</html>

 bind()语法:函数名.bind(this修改后的指向,参数1,残数2)

bind()语法并不会立即执行函数,而是返回一个修改指向后的新函数,常用于回调函数

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <script>
        /* 
        1.函数三种执行方式 : 
            全局函数 : this指向window
            对象方法:  this指向对象
            构造函数 : this指向new创建的对象
                共同的特点: this的指向无法动态修改

        2.函数上下文模式 : 
            2.1 作用: 可以动态修改函数中的this
            2.2 语法: 有3种写法,作用都是一样改this,应用场景不同
                a. 函数名.call(修改的this,arg1,arg2…………) 
                    * 适用于函数原本形参 <= 1
                b. 函数名.apply(修改的this,[数组或伪数组])
                    * 适用于函数原本形参 >= 2
                c. 函数名.bind(修改的this,arg1,arg2…………)
                    * 特点:不会立即执行这个函数,而是返回修改this后的新函数
                    * 适用于不会立即执行的函数 : 事件处理函数,定时器函数

         */

        // function fn()
        //     //三种执行模式this无法动态修改
        //     //this = age:18;

        //     console.log(this);
            
        // ;

        // fn();//this:window
        
        /* 上下文模式 */
        function fn(a,b)
            console.log(this);
            console.log(a+b);
        ;

        //a. 函数名.call(修改的this,arg1,arg2…………) 
        //应用场景: 适用于函数原本形参 <= 1
        fn(10,20);//this:window
        fn.call(age:18,100,200);
        
        //b. 函数名.apply(修改的this,[数组或伪数组])
        //应用场景: 适用于函数原本形参 >=2
        fn.apply(age:88,[20,30]);

        //c. 函数名.bind(修改的this,arg1,arg2…………)
        //特点:这个函数不会立即执行,而是返回一个修改this之后的新函数
        //应用场景 : 事件处理函数,定时器
        let newFn =  fn.bind(name:'坤坤');
        newFn(50,60);


        //4. 定时器中的this一定是指向window

        // 定时器:一段代码间隔事件执行 setTimeout(一段代码,间隔时间)

        //4.1 具名函数
        let test = function()
            console.log('我是具名函数');
            console.log(this);    
        ;

        let newTest = test.bind(name:'张三')

        setTimeout(newTest,3000);

        //4.2 匿名函数
        setTimeout(function()
            console.log('我是定时器中的函数');
            console.log(this);
        .bind(name:'李四'),2000);
    </script>
</body>
</html>


    //2.3 bind();
    //语法:  函数名.bind(this修改后的指向,arg1,arg2....);
    let obj = 
        name:"文聪兄"
    ;

    function getSum(a,b)
        console.log(this);
        console.log(a+b);
    

    getSum(10,20);//this指向window
    let fn = getSum.bind(obj); //bind()不会执行这个函数,而是会返回一个修改了this的函数.
    fn(100,200); //此时这个fn,就相当于是修改了this之后的getSum.


    //3 回调函数(例如定时器),一般使用bind来修改this指向
       let obj = 
         name:"班长"
       ;
        //3.1  定时器的回调函数是一个具名函数
       function test1()
         console.log(this);
       
       let test2 = test1.bind(obj);
       setInterval(test2,2000);
       //3.2 定时器的回调函数是一个匿名函数
       setInterval(function () 
           console.log ( this );
       .bind(obj),2000);

递归

递归函数介绍

1.递归函数:一个函数自己调用自己

2.递归函数特点:

        一定要有结束条件,否则会导致死循环

        能够用递归函数实现的需求,就一定可以用循环调用函数来解决,只是代码简洁与性能不同而已

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <script>
        /* 
        1. 递归函数 : 在函数中自己调用自己

        2. 递归特点
            a. 能用递归实现的功能一定可以用循环,只是语法不同
            b. 递归一定要有结束的条件,否则会导致死循环
         */

        //一个函数递归
        // function fn()
        //     console.log('哈哈');
        //     fn();
            
        // ;

        // fn();

        //两个函数递归
        // function fn1()
        //     console.log('哈哈');
        //     fn2();
            
        // ;

        // function fn2()
        //     console.log('呵呵');
        //     fn1();
            
        // ;
        // fn2();


        //需求:写一个函数,打印三次 我爱坤哥

        let i = 1;
        function fn()
            console.log('我爱坤哥');
            i++;
            if(i <= 3)
                fn();
            ;
            
            //循环实现
            // for(let i = 1;i<=3;i++)
            //     console.log('我爱坤哥');
                
            // ;
        ;

        fn();
    </script>
</body>
</html>

递归应有场景:浅拷贝与深拷贝

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <script>
      /*浅拷贝与深拷贝概念主要针对于对象这种数据类型 
        
        1.浅拷贝: 拷贝的是地址
            * 特点:修改拷贝后的数据,原数据也会随之修改
        2.深拷贝:拷贝的是数据
            * 特点:修改拷贝后的数据,对原数据没有影响
         */

      let obj = 
        name: 'ikun',
        age: 32,
        hobby: ['讲课', '敲代码', '黑马程序员'],
        class: 
          name: '武汉大前端',
          salary: [18888, 12000, 10000]
        
      

      //1.浅拷贝: 拷贝的是地址
      let obj1 = obj
      obj1.name = '黑马李宗盛'
      console.log(obj, obj1)

      //2.深拷贝:拷贝的是数据
      //核心原理:使用递归。 只要遇到属性值是引用类型,则遍历。

      function kaobei (newObj, obj) 
        // 遍历
        for (let key in obj) 
          if (obj[key] instanceof Array) 
            // obj[key] 是数组
            // obj[key]是数组
            newObj[key] = []

            kaobei(newObj[key], obj[key])
           else if (obj[key] instanceof Object) 
            // obj[key] 是对象
            // obj[key]再遍历拷贝
            newObj[key] = 

            kaobei(newObj[key], obj[key])
           else 
            newObj[key] = obj[key]
          
        
      

      let obj2 = 
      //深拷贝
      kaobei(obj2, obj)
      //修改拷贝后的数据
      obj2.name = '黑马颜值担当'
      obj2.hobby = '唱歌'
      console.log(obj,obj2)
    </script>
  </body>
</html>

递归应用场景:遍历dom树

  • 在我们后期vue项目中,有一个这样的案例:服务器返回一个不确定的数据结构。 是一个多级菜单,这个数据是不确定的。我们需要根据服务器返回的数据,来生成对应的页面结构

     

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta http-equiv="X-UA-Compatible" content="ie=edge" />
        <title>Document</title>
        <style>
          * 
            padding: 0;
            margin: 0;
          
    
          .menu p 
            width: 100px;
            border: 3px solid;
            margin: 5px;
          
    
          .menu > div p 
            margin-left: 10px;
            border-color: red;
          
    
          .menu > div > div p 
            margin-left: 20px;
            border-color: green;
          
    
          .menu > div > div > div p 
            margin-left: 30px;
            border-color: yellow;
          
        </style>
      </head>
      <body>
        <div class="menu">
          <!-- <div>
            <p>第一级菜单</p>
            <div>
              <p>第二级菜单</p>
              <div>
                <p>第三级菜单</p>
              </div>
            </div>
          </div> -->
        </div>
        <script>
          //服务器返回一个不确定的数据结构,涉及到多重数组嵌套
          let arr = [
            
              type: '电子产品',
              data: [
                
                  type: '手机',
                  data: ['iPhone手机', '小米手机', '华为手机']
                ,
                
                  type: '平板',
                  data: ['iPad', '平板小米', '平板华为']
                ,
                
                  type: '智能手表',
                  data: []
                
              ]
            ,
            
              type: '生活家居',
              data: [
                
                  type: '沙发',
                  data: ['真皮沙发', '布沙发']
                ,
                
                  type: '椅子',
                  data: ['餐椅', '电脑椅', '办公椅', '休闲椅']
                ,
                
                  type: '桌子',
                  data: ['办公桌']
                
              ]
            ,
            
              type: '零食',
              data: [
                
                  type: '水果',
                  data: []
                ,
                
                  type: '咖啡',
                  data: ['雀巢咖啡']
                
              ]
            
          ]
    
          /* 使用递归遍历数组 */
          function addElement (arr, father) 
            for (let i = 0; i < arr.length; i++) 
              let div = document.createElement('div')
              div.innerHTML = `<p>$arr[i].type || arr[i] </p>`
              father.appendChild(div)
              if( arr[i].data )
                addElement(arr[i].data,div)
              
            
          
    
          //调用递归函数
          addElement(arr,document.querySelector('.menu'))
        </script>
      </body>
    </html>
    

    闭包

1.闭包:是一个可以访问其他函数变量的函数

2.闭包作用:解决变量污染问题,让变量被函数保护起来

function fn()
    let a = 1
    function fn1() 
        console.log(a)
    
    fn1()

执行函数 fn1 用到了另一个函数fn中的 a 这个变量,所以 fn1 + a 构成了闭包。

闭包的案例

  1. 案例需求:在输入框输入搜索文字,点击百度一下按钮,用定时器模拟网络请求,1 秒之后显示搜索结果;

结构如下:

<div class="box">
  <input type="search" name="" id="">
  <button>百度一下</button>
</div>

代码如下:

// 1. 获取元素
let search = document.querySelector('.box input')
let btn = document.querySelector('.box button')

// 2. 添加点击事件
btn.onclick = function () 
  // 获取搜索的文字
  let text = search.value

  // 模拟发送网络请求
  setTimeout(function () 
    alert(`您搜索的内容是 $text 共搜索到 12345 条结果`)
  , 1000)

学习重点梳理(高频面试题)

this三种指向

this:谁调用我,我就指向谁

1.全局函数:this指向window

2.对象方法:this指向对象

3.构造函数:this指向new创建的空对象

call、apply、bind三者区别

相同点:都是修改函数this指向

不同点:

        传参方式不同:call用于单个参数,apply用于多个参数(数组/伪数组)

        执行机制不同:call与apply会立即执行,bind不会立即执行

        call、apply用一次修改一次

        bind:依次修改,终生有效

闭包

什么是闭包:以下两种回答都可以

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

        闭包是函数+上下文代码组合

闭包作用:解决变量污染

递归

什么是递归:函数内部调用自己

递归场景

        深拷贝

        遍历dom树

以上是关于函数this三种指向函数调用上下文模式递归闭包的主要内容,如果未能解决你的问题,请参考以下文章

JS面试题(进阶)——原型链、this指向、闭包

少女学习日记——函数进阶闭包以及递归

前端学习(33)~js学习:函数

半小时搞明白JavaScript中函数的上下文调用模式

javascript设计模式与开发实践阅读笔记—— this,闭包与高阶函数

ES6基础3--函数和闭包