JavaScript高级复习03-this的三种指向,上下文调用,闭包,递归函数

this三种指向

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

重点: 与函数声明无关,取决于函数调用

(1)全局函数: 函数名()

this指向window

(2)对象方法: 对象名.方法名()

this指向对象

(3)构造函数:new 函数名()

this指向new创建的实例对象

   <script>    
        //1.全局函数
        function fn(){
            console.log('111111')
            console.log(this)           
        }
​
        fn();//window.fn()
​
        //2.对象的方法
        let obj = {
            name:'班长',
            sayHi:function(){
                console.log('我是胖胖又可爱的得小又又')
                console.log(this)          
            }
        }
        obj.sayHi()
        //将fn的地址赋值给sayHi
        obj.sayHi = fn
        //此时this指向obj,this指向跟声明没有关系。取决于函数是如何调用的
        obj.sayHi()
​
        //3.构造函数
        function Person(name,age){
            //(1)创建一个空对象  (2)this指向这个对象 (3)执行赋值代码 (4)返回这个对象
            //this :指向new创建的哪个对象
            console.log(this)        
            this.name = name
            this.age = age
        }
        let p1 = new Person() // 构造函数
        //没有加new,以全局函数方式执行。此时this就是window,函数里面其实是给window添加属性(全局变量)
        Person('张三',18) // 全局函数
        console.log(name)
        console.log(age)
        
    </script>

函数上下文调用

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

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

                call应用场景: 数据类型检测

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

                apply应用场景: 伪数组转成真数组, 求数组最大值

        (3)函数名.bind(修改this的指向)

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

                细节: 如果你在bind后面传了函数参数,那么参数也会绑定。之后传参无效

                bind应用场景: 主要是修改'不需要立即执行的函数',事件处理函数,定时器函数

2. call, apply, bind的异同点

        相同点: 都是用来修改this指向

        不同点:

                传参方式不同: call是一一传参; apply是数组/伪数组,自动遍历后逐一传参

                执行机制不同: call和apply会立即调用函数; bind不会立即调用,而是得到一个修改this之后的新函数

call上下文调用

  <script>
    function fn(a, b) {
      console.log(this)
      console.log(a + b)
    }
    fn(1, 2) // this指向window
​
    // 函数名.call(修改的this的指向,参数1,参数2...)
    fn.call({name: '张三'}, 1, 2)
  </script>

call应用场景

数据类型检测

  <script>
    /* 需求: 数据类型检测 */
    /* 
    typeof 数据: 检测数据类型
    typeof无法检测两种数据类型:null,array 都会得到object
     */
    console.log(typeof 123) // number
    console.log(typeof '123') // string
    console.log(typeof true) // boolean
    console.log(typeof undefined) // undefined
    console.log(typeof null) // object
    console.log(typeof [1, 2, 3]) // object
    console.log(typeof function () {}) // function
    console.log(typeof {name: '张三'}) // object
​
    /* 
    Object.prototype.toString(): 得到固定格式字符串
    [object Type], 其中Type是对象的类型
    检测数据类型固定格式语法: Object.prototype.toString.call(数据)
    */
​
    console.log(Object.prototype.toString.call(123)) // [object Number]
    console.log(Object.prototype.toString.call('123')) // [object String]
    console.log(Object.prototype.toString.call(true)) // [object Boolean]
    console.log(Object.prototype.toString.call(undefined)) // [object Undefined]
    console.log(Object.prototype.toString.call(null)) // [object Null]
    console.log(Object.prototype.toString.call([1, 2, 3])) // [object Array]
    console.log(Object.prototype.toString.call(function () {})) // [object Function]
    console.log(Object.prototype.toString.call({name: '张三'})) // [object Object]
  </script>

apply上下文调用

  <script>
    function fn(a, b) {
      console.log(this)
      console.log(a + b)
    }
    fn(1, 2) // this指向window
​
    // 函数名.call(修改的this,参数1,参数2...)
    fn.call({name: '张三'}, 1, 2)
​
    // 函数名.apply(修改的this的指向,数组/伪数组)
    // apply传参的时候会自动的遍历这个数组,然后按照顺序逐一传参
    fn.apply({name: '李四'}, [3, 4])
  </script>

apply应用场景

伪数组转成真数组

  <script>
    let obj = {
      0: 20,
      1: 50,
      2: 88,
      3: 33,
      length: 4
    }
    console.log(obj)
​
    // 需求: 伪数组转成真数组
​
    // 1. ES5: apply上下文调用
    let newArr = []
    /* 
    函数名.apply(修改的this, 数组/伪数组)
    第一个参数: newArr 本来this就是newArr,这里面不需要修改this
    第二个参数:obj 借助apply的特点:自动遍历伪数组/数组,逐一传参(省去for循环)
    */
    // newArr.push(obj[0], obj[1], obj[2], obj[3])
    newArr.push.apply(newArr, obj)
    console.log(newArr)
​
    // 2. ES6(推荐): Array.from()
    let arr = Array.from(obj)
    console.log(arr)
  </script>
求数组最大值

  <script>
    /* 
    伪数组: 有数组三要素(下标,元素,长度),但是不能使用数组的方法
       伪数组: 本质是对象
       伪数组不能使用数组方法: 伪数组的原型指向对象的原型,而不是Array的原型
    */
​
    // 需求: 求数组最大值
    let arr = [10, 23, 44, 78, 32, 9]
​
    // 1. 排序法: 从大到小排序,取0下标
    arr.sort(function (a, b) {
      return b - a
    })
    console.log(arr[0])
​
    // 2. 擂台法
    let max1 = arr[0]
    for (let i = 0; i < arr.length; i++) {
      if (arr[i] > max1) {
        max1 = arr[i]
      }
    }
    console.log(max1)
​
    // 3.ES5: Math.max.apply(Math,数组/伪数组)
    // 第一个参数Math: this本来就是Math,这里也不需要修改this(传Math相当于this不变)
    // 第二个参数arr: 借助apply特点: 自动遍历数组/伪数组,逐一传参
    let max2 = Math.max.apply(Math, arr)
    console.log(max2)
​
    // 4.ES6(推荐):
    let max3 = Math.max(...arr)
    console.log(max3)
  </script>

bind上下文调用

  <script>
    function fn(a, b) {
      console.log(this)
      console.log(a + b)
    }
    fn(1, 2) // this指向window
​
    // 函数名.call(修改的this,参数1,参数2...)
    fn.call({name: '张三'}, 1, 2)
​
    // 函数名.apply(修改的this,数组/伪数组)
    // apply传参的时候会自动的遍历这个数组,然后按照顺序逐一传参
    fn.apply({name: '李四'}, [3, 4])
​
    // 函数名.bind(修改this)
    // bind不会立即调用函数,而是得到一个修改this之后的新函数
    let newFn = fn.bind({name: '王五'})
    newFn(5,6)
  </script>

bind应用场景

修改定时器中的this

  <script>
    //  定时器中的this一定是window
    let fn = function () {
      console.log(this)
    }
    // 使用bind修改this
    let newFn = fn.bind({
      name: '666'
    })
    // fn(): 调用函数,运行结果是函数返回值
    // fn: 变量取值,取出fn中存储的堆地址
    setTimeout(newFn, 3000)
​
    // 下面这个写法和上面写法是一样的: 函数是数据类型,也可以像其他数据一样直接使用用语法
    setTimeout(function () {
      console.log(this)
    }, bind({name: '干饭'}), 3000)
  </script>

函数调用上下文模式注意点

1. 函数上下文执行模式 : 动态修改this

        注意点 : 修改的this只能是引用类型

2. 如果写的是基本数据类型

        string,number,boolean : 自定帮我们转成对应本装包类型 new String() Boolean() Number()

undefined,null :

  <script>
    function fn() {
      console.log(this)
    }
​
    fn.call('str')
    fn.call(1)
    fn.call(true)
​
    //如果传的是undefined和null,或者不传。代码不会报错,也不会帮我们修改this,还是原来的window
    fn.call(undefined)
    fn.call(null)
    fn.call()
    fn.call(window)
  </script>

闭包

1. 闭包(closure)是什么?

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

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

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

        闭包出现较多的一般都会在回调函数里面

<script>
    function fn() {
      let a = 1
      // 在fn1函数中,访问了其他函数fn内部的变量 fn1+a 形成闭包
      function fn1() {
        console.log(a)
      }
      fn1()
    }
    fn()
  </script>

递归函数

1. 什么是递归函数? 函数内部调用自己

        注意点: 需要满足条件才会递归,否则会导致死循环

        递归函数和循环功能类似

2. 递归函数的应用场景:

        (1)浅拷贝与深拷贝

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

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

                深拷贝实现的两种方式: json转换; 递归函数

        (2)遍历dom树

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

<script>
    // 声明一个对象
    // 拷贝: 把对象中储存的数据拷贝一份赋值给其他对象
    let obj = {
      name: '张三',
      age: 30,
      hobby: ['吃饭', '睡觉', '追剧'],
      friend: {
        name: '李四',
        sex: '男'
      }
    }
​
    //浅拷贝: 拷贝的是地址
    let obj1 = obj
    //由于浅拷贝,拷贝的是地址。 所以修改拷贝后的数据,原来的数据也会变化
    obj1.hobby = '美食'
    console.log(obj1, obj)
  </script>

浅拷贝转成深拷贝方法一(json转换)

  <script>
    let obj = {
      name: '张三',
      age: 30,
      hobby: ['吃饭', '睡觉', '追剧'],
      friend: {
        name: '李四',
        sex: '男'
      }
    }
​
    /* 
    (1)先把js对象转成json格式字符串: JSON.stringify(js对象)
    json在把js转成json格式字符串的时候,底层会自动帮你深拷贝
     */
    let json = JSON.stringify(obj)
    console.log(json)
​
    /* 
    (2)再把json字符串转成js对象: JSON.parse(json格式)
     */
    /* let js = JSON.parse(json)
    console.log(js)
​
    js.hobby = '学习'
    console.log(js, obj) */
​
    // 简写
    let newObj = JSON.parse(JSON.stringify(obj))
    newObj.hobby = '学习'
    console.log(newObj, onj)
  </script>

浅拷贝转成深拷贝方法二(递归函数)

  <script>
    let obj = {
      name: '张三',
      age: 30,
      hobby: ['吃饭', '睡觉', '追剧'],
      friend: {
        name: '李四',
        sex: '男'
      }
    }
​
    /* 
    (1)遍历obj,把所有的属性添加给newObj
    (2)如果obj[key]是引用类型(数组,对象),则不能直接拷贝地址
        (2.1)数组:给newObj声明一个空数组,然后遍历obj[key],把里面元素添加给newObj[key]
        (2.2)对象:给newObj声明一个空对象,然后遍历obj[key],把里面元素添加给newObj[key]
    (3)如果obj[key]不是引用类型,则直接赋值,结束递归
    */
    let newObj = {}
​
    function copy(newObj, obj) {
      for (let key in obj) {
        // 判断 obj[key] 是不是数组类型
        if (obj[key] instanceof Array) {
          // 声明一个空数组,然后继续拷贝数组里面的数据
          newObj[key] = []
          copy(newObj[key], obj[key])
        } // 判断 obj[key] 是不是数组类型
        else if (obj[key] instanceof Object) {
          // 声明一个空数组,然后继续拷贝数组里面的数据
          newObj[key] = {}
          copy(newObj[key], obj[key])
        } else {
          newObj[key] = obj[key]
        }
      }
    }
​
    // 调用深拷贝函数
    copy(newObj, obj)
    // 深拷贝: 修改拷贝的数据,对原数据没有影响
    newObj.hobby[0] = '111'
    newObj.friend.name = '222'
    console.log(newObj, obj)
  </script>

递归经典场景: 遍历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: ['雀巢咖啡']
          }
        ]
      }
    ]
​
    /* 使用递归遍历数组 */
    // arr: 数据  father: 父盒子
    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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

端端1023

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值