JS高级-this指向/递归/闭包

133 阅读6分钟

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

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

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

函数三种执行模式 : 全局函数 、 对象方法 、 构造函数

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

  1. 全局函数 : this指向window

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

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

小技巧: (1)没有点.没new就是widow

(2)有点就是左边的对象

(3)有new就是实例对象

//声明函数
        function fn() {
            console.log(this)
        }



        //1.普通函数
        fn()//window


        //2.对象的方法
        let obj = {
            name: '张三',
            age: 20,
            eat: fn
        }



        obj.eat()//对象

        //3.构造函数
        new fn()//new 创建的实例对象

02-函数调用的上下文模式==

2.函数三种调用方式有一个共同点: this指向无法修改

3.函数上下文调用 : 用于修改函数中的this指向

3.1 函数名.call(修改this,形参1,形参2...)

3.2 函数名.apply(修改this,数组或伪数组)

3.3 函数名.bind(修改this)

不会立即执行函数,而是得到一个修改this后的新函数

bind一般用于修改,定时器函数\事件处理函数

  1. call和apply和bind区别:传参方式不同
function fn(a, b) {
            console.log(this)
            console.log(a + b)
        }

        //普通函数 调用函数
        fn(10, 20)
        //上下文调用 : 函数名.call(修改this,形参1,形参2...)
        fn.call({ name: '张三' }, 30, 40)

call应用场景

1. typeof 数据 : 检测数据类型
            * 有两种数据类型无法检测 : null 和 array
        2. 万能数据类型检测 : Object.prototype.toString.call( 数据 )
        */

        //值类型(基本数据类型)
        let str = 'abc'
        let num = 123
        let bol = true
        let und = undefined
        let nul = null
        //引用类型(复杂数据类型)
        let arr = [10, 20, 30]
        let fn = function () { }
        let obj = { name: '张三' }



        console.log(Object.prototype.toString.call(str))//'[object String]'
        console.log(Object.prototype.toString.call(num))//'[object Number]'
        console.log(Object.prototype.toString.call(bol))//'[object Boolean]'
        console.log(Object.prototype.toString.call(und))//'[object Undefined]'
        console.log(Object.prototype.toString.call(nul))//'[object Null]'
        console.log(Object.prototype.toString.call(arr))//'[object Array]'
        console.log(Object.prototype.toString.call(fn))//'[object Function]'
        console.log(Object.prototype.toString.call(obj))//'[object Object]'

apply调用

 function fn(a,b,c){
           console.log( this )
           console.log(a+b)
       }

       //普通函数 调用函数
       fn(10,20,30)
       //(1) 函数名.call(修改this,形参1,形参2...)
       fn.call({name:'张三'},30,40,50)
       //(2) 函数名.apply(修改this,数组/伪数组)
       // apply自动遍历数组/伪数组, 然后按照元素顺序逐一传参
       fn.apply({name:'李四'},[30,40,50] )

apply应用场景(求最大值)

//求数组最大值 
       let arr = [80,100,99,20,50]

       //1.js基础:擂台思想    (忘掉舍弃)
       let max = arr[0]
       for(let i = 1;i < arr.length;i++){
           if( arr[i] > max ){
               max = arr[i]
           }
       }
       console.log(max)

       //2.js高级写法

       let max1 = Math.max.apply(Math,arr)
       console.log( max1 )

       //ES6   (常用)
       //  ...是ES6新增的一种运算符,类似于apply也会自动遍历arr,然后逐一传参
       let max2 = Math.max(...arr)
       console.log(max2)

bind应用场景(定时器 事件处理函数)

4. call和apply和bind区别
             4.1 传参方式不同 : call是逐一传参, apply是数组/伪数组 传参方式不同
             4.2 执行机制不同 : call和apply立即执行函数, bind不会立即执行函数
                * call和apply用一次,改一次
                * bind修改一次,终生有效
        */



        function fn(a, b, c) {
            console.log(this)
            console.log(a + b)
        }

        //普通函数 调用函数
        fn(10, 20, 30)
        //(1) 函数名.call(修改this,形参1,形参2...)
        fn.call({ name: '张三' }, 30, 40, 50)
        //(2) 函数名.apply(修改this,数组/伪数组)
        // apply自动遍历数组/伪数组, 然后按照元素顺序逐一传参
        fn.apply({ name: '李四' }, [30, 40, 50])
        //(3) 函数名.bind(修改this)
        // bind不会立即调用函数,而是得到一个修改this之后的新函数
        // bind一般用于修改不会立即执行的函数中的this : 定时器、事件处理函数
        let fn1 = fn.bind({ name: '王五' })
        fn1(6, 7)

        //定时器中的this默认指向window
        setTimeout(function () {
            console.log(this)
        }.bind({ name: '王五' }), 3000)

03-闭包(closure)

1.闭包 :

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

b.闭包 = 函数 + 组合(上下文引用),指的是局部作用域

2.判断闭包 :

代码角度两个条件: 函数 + 上下文引用(使用其他函数局部变量)

控制台角度 : 会出现 closure提示

3.闭包作用 : 解决全局变量污染,一般用于回调函数.

4.在浏览器中调试闭包

04-递归

1.1-递归函数介绍


本小节知识点

  • 1.递归函数:一个函数在内部自己调用自己
  • 循环能用的就用循环,不能用的再用递归,使用场景不多
  • 2.递归函数特点
    • a.一定要有结束条件,否则会导致死循环
    • b.能用递归函数实现的需求,就一定可以用循环调用函数来解决,只是代码简洁与性能不同而已
function fn(){
            console.log('今天学得很开心')
            fn()
        }

        // fn()

        //双函数递归 : 两个函数互相调用
        function fn1(){
            console.log('哈哈')
            fn2()
        }

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

        // fn1()

1.2-递归应用场景:浅拷贝与深拷贝:json实现


递归应用:

浅拷贝与深拷贝 :

方式一(推荐) : JSON方式实现

  • let newObj = JSON.parse( JSON.stringify( obj ) )

方式二(递归) : 了解

遍历dom树

*/


       
        let obj = {
            name:'张三',
            age:20,
            sex:'男',
            hobby:['吃饭','睡觉','学习']
        }

        //浅拷贝: 拷贝地址
        // let newObj = obj
        // 修改拷贝后的数据,原数据也会修改
        // newObj.name = '李四'
        // console.log( obj,newObj)

        //深拷贝 : 拷贝数据
        //(1)先把js对象 -> JSON字符串   (JSON会自动帮你深拷贝)
        // let jsonStr = JSON.stringify( obj )
        //(2)再把 JSON字符串 -> js对象
        // let newObj = JSON.parse( jsonStr )

        let newObj = JSON.parse( JSON.stringify( obj ) )

        newObj.name = '李四'
        console.log(obj,newObj)

1.3-递归应用场景: 浅拷贝与深拷贝:递归实现


let obj = {
            name:'张三',
            age:20,
            sex:'男',
            hobby:['吃饭','睡觉','学习'],
            student:{
                name:"班长",
                score:90
            }
        }

        //使用递归函数
        function kaobei(obj,newObj){
            for(let key in obj){
                if( obj[key] instanceof Array ){
                    //声明一个空数组,然后继续拷贝数组里面的数据
                    newObj[key] = []
                    //递归调用继续拷贝 数组
                    kaobei(obj[key],newObj[key])
                }else if(  obj[key] instanceof Object ){
                     //声明一个空对象
                     newObj[key] = {}
                    //递归调用继续拷贝 对象
                    kaobei(obj[key],newObj[key])
                }else{
                    newObj[key] = obj[key]
                }
            }
        }
        //创建一个空对象,然后深拷贝
        let newObj = {}
        kaobei(obj,newObj)

        newObj.name = '李四'
        newObj.hobby[0] = '摸鱼'
        newObj.student.name = 'ikun'
        console.log( obj,newObj)

1.4-递归应用场景:遍历dom树


<!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: ["雀巢咖啡"]
            }
          ]
        }
      ]

      //封装一个遍历dom树函数
      function addElement(arr, father) {
        //遍历数组
        for (let i = 0; i < arr.length; i++) {
          //(1)创建空标签
          let div = document.createElement("div")
          //(2)设置内容
          div.innerHTML = `<p>${arr[i].type || arr[i] }</p>`
          //(3)添加到父盒子
          father.appendChild(div)
          //如果元素还有data属性,则需要使用递归继续添加下级菜单
          if( arr[i].data ){
              addElement(arr[i].data , div)
          }
        }
      }

      addElement(arr, document.querySelector(".menu"))
    </script>
  </body>
</html>

05-递归小节


1.递归


  • 什么是递归:函数内部调用自己
  • 递归场景
    • 深拷贝
    • 遍历dom树

2.this三种指向


this : 谁 调用 我,我就指向谁 1.全局函数 : this指向window

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

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

3.call、apply、bind区别


  • 相同点:都是修改函数this指向
  • 不同点
    • 传参方式不同: call用于单个参数,apply用于多个参数
    • 执行机制不同: call与apply会立即执行, bind不会立即执行
      • call、apply用一次修改一次
      • bind;一次修改,终生有效

4.闭包


  • 什么是闭包:以下两种回答都可以
    • 闭包是一个访问其他函数内部变量的函数
    • 闭包是 函数 + 上下文代码组合
  • 闭包作用:解决变量污染