函数进阶:this指向 闭包 递归

400 阅读4分钟

this 知识

函数的三种调用方式: 普通函数 对象方法 构造函数
普通函数this的指向问题:调用我, 我就指向谁.

  1. 普通函数; 函数名() this指向window
  2. 对象方法: 对象名.方法名() this指向对象
  3. 构造函数; new 函数名() this指向new创建实例对象

箭头函数this的指向问题: 箭头函数没有this.

在箭头函数里使用this, 本质是通过作用域链找上一级作用域的this.

箭头函数没有this对箭头函数有一些影响:
(1)箭头函数不能作为构造函数
(2)箭头函数不能修改this
(3)事件处理函数一般不用箭头函数

上下文(context): 表示函数内部this的值。

默认情况下, 函数内部的this不能主动修改, 如果需要修改, 则需要修改上下文.
修改上下文的几种方式:call() ,.apply() ,.bind()
1.  函数名.call(this修改后的指向,参数1,参数2......)

应用场景: 万能数据类型检测语法: Object.prototype.toString.call( 数据 )

  • typeof数据 : 有两种数据类型无法检测, null和数组无法检测,结果都是 'object'
  • 解决方案: 万能数据类型检测 Object.prototype.toString.call( 数据 ) 万能数据类型检测原理:
  • Object.prototype.toString() 内部会返回this的数据类型, 得到固定格式字符串 '[object   数据类型]'
  • 使用Object原型中的toString()要想得到数据类型,只需要把this修改成你想要检测的对象

举例:

       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]'

2.  函数名.apply(this修改之后的指向, 伪数组或者数组)

apply会自动遍历数组,然后按照顺序逐一传参

应用场景(1): 伪数组转真数组

      // 类似于伪数组  本质是 :  对象 
        let obj = {
            0: 20,
            1: 66,
            2: 87,
            3: 90,
            length: 4
        }
        console.log(obj);
        //伪数组转真数组
        let arr = []
        console.log(arr);
        // arr.push( obj[0],obj[1],obj[2],obj[3])
        //借助 apply自动遍历数组/伪数组 逐一传参特点
        //这里不需要修改this,只是借助apply传参的特点. this指向原来是谁,还是设置谁
        arr.push.apply(arr, obj)
        console.log(arr) // [20, 66, 87, 90]

以前学的伪数组转真数组语法:Array.from( 伪数组 )
应用场景(2): 求数组最大值

        //求数组最大值
        let arr = [20, 50, 66, 100, 30]
        //2. Math.max()
        // let max1 = Math.max(arr[0],arr[1],arr[2],arr[3],arr[4])
        //这里使用apply只是借助传参特点,this指向不用修改。还是原来的this
        let max1 = Math.max.apply(Math, arr)
        console.log(max1)
        
        //ES6求最大值   展开运算符...作用和apply类似,也会自动遍历数组,然后逐一传参
        let max2 = Math.max(...arr)
        console.log(max2)

3.  函数名.bind(this修改后的指向, arg1,arg2......)

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

应用场景: 定时器中的this : 默认指向window, 修改定时器中的this,

      setTimeout(function(){
        console.log(this);
      }.bind({name:'111'}),2000)

这里不能使用call()apply(), 因为她俩会立即执行函数, 定时器失效啦

闭包

一个函数内使用了外部的变量,那么这个函数和被使用的外部变量一起被称为闭包结构, 通常会再使用一个函数包裹住闭包结构, 以起到解决变量污染问题,让变量被函数保护起来.

示例代码:

const count = 0
setInterval(function () {
  console.log(count++)
}, 1000)

上述代码中的count 是一个使用频率很高的变量名,为了避免和其他位置的代码冲突, 一般外面再套一个函数, 可起到保护作用.

function fn() {
  const count = 0 // 私有化数据
  setInterval(function () {
    console.log(count++)
  }, 1000)
}

setInterval 第一个参数的匿名函数count 构成了闭包。 使用闭包的优缺点:
优点: 私有化数据,在私有化数据的基础上保持数据.
缺点: 可能会导致内存泄漏,内部的变量不会被自动回收掉.

递归

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

递归函数特点:

  • 要有结束条件, 否则会导致死循环
  • 能用递归函数实现的需求, 就一定可以用循环调用函数来解决, 只是代码简洁与性能不同而已.

递归的应用场景: 使用递归进行深拷贝

        let obj = {
            name: '陈平安',
            age: 20,
            gender: '男',
            hobby: ['吃饭', '睡觉', '学习'],
            student: {
                name: "裴钱",
                age: 6
            }
        }
        //console.log(obj['hobby']);
        // //使用递归函数
        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.age = 17
        newObj.gender = '女'
        newObj.hobby[0] = '喜欢平安'
        newObj.student.name = '小米粒'
        newObj.student.age = 7
        console.log(obj, newObj)

现在基本上采用json方式 : 实现深拷贝.
语法:let newObj = JSON.parse( JSON.stringify( obj ) )