《Javascript中关于this作用域的箭头函数,普通函数以及回调函数中的特殊作用》

发布时间 2023-08-18 17:13:25作者: sdklxzciosad

开言

这篇文章的内容很简单,一句话就是“涉及this指向谁”!!!

但是涉及JavaScript的普通函数,箭头函数,WIndow,回调,Object等各种知识点

其糅杂在一起,其知识点会很混乱,我们要理性头绪!!

简单来说,普通函数可以看做管理严格的孩子,箭头函数是一个自由自在的孩子

因此,在object中,使用箭头函数很危险,其this指向很随性!!

但在回调函数中,箭头函数可以帮助我们传入当前object的this值!!

而function同时使用bind,也可以达到管理内部的this指针效果!!

这并不是语法缺陷,其可以看到代码编写中有很强大的灵活性!!!

这并不是语法缺陷,其可以看到代码编写中有很强大的灵活性!!!

这并不是语法缺陷,其可以看到代码编写中有很强大的灵活性!!!

函数式编程中,箭头函数非常重要,甚至出现高阶函数,返回一个构建的函数

在上述这些过程中,如果你单纯想用function来实现,你this绑定会非常麻烦!!

 

目录

一、This指针的错误理解

二、JavaScript脚本语言(动态运行)中This指针的灵活性

三、嵌套对象中this指针

四、在对象定义中警惕在箭头函数中使用this指针,不会链式传导

五、function函数在回调函数中的局限性

六、箭头函数解决参数传递问题

七、常规函数中使用bind来绑定this指针,解决回调函数中的问题

 

-----

 

一、This指针的错误理解

我们之前一直不理解this作用域,其核心是一些概念模糊,this关键是看调用方,而不是声明方。

当你看一个存在函数的声明,并不能推断出this的指向,因为JavaScript中存在函数绑定,其可能在不同的对象上调用,导致this的指向不同!!!

这么说,看this的指向,之前我们一直采用静态的眼光看,而我们现在要采用动态的眼光看!!!!

 

采用 动态的眼光,看调用方!!

采用 动态的眼光,看调用方!!

采用 动态的眼光,看调用方!!

 

二、JavaScript脚本语言(动态运行)中This指针的灵活性

记住,JavaScript是脚本语言,脚本语言的意义在于“动态运行”,像python一样

而JavaScript的语法比python复杂,同时又兼有c++这种this功能

在c++这种静态语言中,像直接定义一个 this.Attribute,是无法接受的,如下

    printFunc = function() {
        console.log(this.FirstName)
    }

因为C++其要“先编译,后运行”,在编译中直接报错!!!

而JavaScript是动态语言,其属于“函数声明”,无所谓,先存着;

当在函数调用时,直接动态搜索当前调用方的this对象;

如果没有找到FirstName这个属性,其直接输出undefined

看下面这个例子,最后Lucy原本的FirstName属性被改为_FirstName,输出Undefined值,并没有报错!!!

  let Tom = {
        FirstName : "Tom",
    }

    let Jack = {
        FirstName: "Jack",
    }

    let Lucy = {
        _FirstName: "Lucy",
    }

    printFunc = function() {
        console.log(this.FirstName) // 动态绑定 this.FirstName
    }

    Tom.print = printFunc
    Tom.print() // Tom


    Jack.print = printFunc
    Jack.print() // Jack

    Lucy.print = printFunc
    Lucy.print() // undefined

 

三、嵌套对象中this指针

这里牵扯链式指向,代码如下,可以看到嵌套中输出环境是不同的

    function print(){
        console.log(this)
    }
    Tier = {
        name: "Tier",
        Tier1:{
            name: "Tier1",
            print(){
                console.log(this.name)
            }
        },
        print(){
            console.log(this.name)
        }
    }

    print() // Window
    Tier.print()    // Tier
    Tier.Tier1.print() // Tier1z

直接构建出其链式结构,其链式结构如下,其结构和输出很明确

 

四、在对象定义中警惕在箭头函数中使用this指针,不会链式传导

在对象中使用箭头函数,其this指针,并不会如预期那样,指向调用对象。

在箭头函数中,并不存在“指向外侧”,而是直接指向指向Windows

    const myObject = {
        property1: 'Hello',
        property2: 'World',

        regularFunction: function() {
            console.log(this.property1 + ' ' + this.property2);
        },

        // Object中尽量不要使用箭头函数
        arrowFunction: ()=> {
            console.log(this.property1 + ' ' + this.property2);
        }
    };

    myObject.regularFunction(); // 输出:Hello World
    myObject.arrowFunction(); // 输出:undefined undefined

这个要警惕,其箭头函数定义的直接指向全局对象window。

同时,这里不会链式传导,不会链式传导,不会链式传导!!

哪怕内层的Object,当使用箭头函数定义时,其也会指向全局对象

 下面是代码结构,当你理解这个,你会真正理解其所处的函数

    const globalObject =
    {
         myObject : {
            property1: 'Hello',
            property2: 'World',

            regularFunction: function () {
                console.log(this.property1 + ' ' + this.property2);
            },

            // 箭头函数在内侧定义 Window --> globalObject --> myObject 
            arrowFunction: () => {
                console.log(this + ' ' + this);
            }
        }
    }

    globalObject.myObject.regularFunction(); // 输出:Hello World
    globalObject.myObject.arrowFunction(); // 输出:[Object Window] [Object Window]

  

五、function函数在回调函数中的局限性

我们之前提到过,在object中警惕使用箭头函数,但是真的一事无成吗箭头函数?

当然不是,在回调函数中,我们可以看看箭头函数的“威力”

现在来,什么是回调函数?相当于我们定义一个函数,交给回调器,其定义方不在我们

这里,存在一个问题,其回调函数原型中,参数已经被定义好了,这是非常关键的

我们如何传递自己的值到回调函数中?

我们如何传递自己的值到回调函数中?

我们如何传递自己的值到回调函数中?

此时就要借助 箭头函数 了!!!!

 

看下面这个代码,什么问题呢?

    var tahoe = {
        resorts: ["Kirkwood","Squaw","Alpine","Heavenly","Northstar"],
        print: function(delay=1000) {

            setTimeout(function() {
                console.log(this.resorts.join(","))
            }, delay)

        }
    }

    tahoe.print() // Uncaught TypeError: Cannot read properties of undefined (reading 'join')

其function中的this,其尝试获取 tahoe对象中的  ["Kirkwood","Squaw","Alpine","Heavenly","Northstar"] 这组数据

这样写很天真,因为JavaScript是动态语言,其回调函数调用这个函数,this指针被setTImeout调用时,this指向Window

自然获取不到这个数据(this并不指向tahoe),其是一个undefined,然后找不到join方法

最后自然抛出一个异常 Uncaught TypeError !!!

---

当然,这里可以使用bind解决,bind返回一个新函数,负责管理函数内部this指针的指向问题,在(七)中会提到的。

 

六、箭头函数解决参数传递问题

函数如下,内侧函数改用箭头函数,这里注意一下重点。

其箭头函数的作用域不限定,如下函数,其this指针直接是函数定义时的this指针 object tahoe

其箭头函数的作用域不限定,如下函数,其this指针直接是函数定义时的this指针 object tahoe

其箭头函数的作用域不限定,如下函数,其this指针直接是函数定义时的this指针 object tahoe

    var tahoe = {
        resorts: ["Kirkwood","Squaw","Alpine","Heavenly","Northstar"],
        print: function(delay=1000) {

            setTimeout( ()=> {
                console.log(this.resorts.join(","))
            }, delay)

        }
    }

    tahoe.print() // Kirkwood,Squaw,Alpine,Heavenly,Northstar

其链子如下,其传入回调函数之后,this值已经确定,不会改变!

其链子如下,其传入回调函数之后,this值已经确定,不会改变!

 其链子如下,其传入回调函数之后,this值已经确定,不会改变!

 

 

但是下列代码,当使用两个箭头函数,其this指针就无法无天了,声明时就指向Window对象!!

这个前面讲过,这个一点也不难,很好理解。

   var tahoe = {
        resorts: ["Kirkwood","Squaw","Alpine","Heavenly","Northstar"],
        print: (delay=1000) => {

            setTimeout( ()=> {
                console.log(this.resorts.join(","))
            }, delay)

        }
    }

    tahoe.print() // Uncaught TypeError: Cannot read properties of undefined (reading 'join')

关于Object的回调函数,记住下面三种情况:

① ()=>{} + ()=>{this}  // 回调函数传入时this指向Window

② function(){} + ()=>{this}  // 回调函数传入时this指向Window

③ function(){} + function(){this} // this直接指向调用方

 

七、常规函数中使用bind来绑定this指针,解决回调函数中的问题

function(){ xx }.bind(),其返回一个新函数,这个其实非常关键

我们使用function.bind(this)可以解决回调函数问题

bind()的核心是帮助管理函数内部的this指针!!

bind()的核心是帮助管理函数内部的this指针!!

bind()的核心是帮助管理函数内部的this指针!!

---

如下代码,其对象中的函数可以单独拆分出来,但是此时其this指针会丢失而指向Window

此时,函数最好的方法是调用bind(),把对象重新绑定函数上,此时this就会重新指向person

const person = {
  name: 'John',
  greet: function() {
    console.log(`Hello, my name is ${this.name}`);
  }
};

const greetFunction = person.greet;
greetFunction(); // 输出:Hello, my name is undefined

const boundGreetFunction = person.greet.bind(person);
boundGreetFunction(); // 输出:Hello, my name is Johne

而下面解决我们提到的setTimeout回调函数的困惑,直接function(){xxx}.bind(this),这个this指针,很明显指向Object

此时该函数挂载到回调函数时,不会再改变了,this就指向这个Object

但是如果使用原来的Function,其传入时就是被视作一个单纯的普通函数,因为其是单独定义的!

但是如果使用原来的Function,其传入时就是被视作一个单纯的普通函数,因为其是单独定义的!

但是如果使用原来的Function,其传入时就是被视作一个单纯的普通函数,因为其是单独定义的!

    var tahoe = {
        resorts: ["Kirkwood","Squaw","Alpine","Heavenly","Northstar"],
        print: function (delay=1000) {

            setTimeout( function () {
                console.log(this.resorts.join(","))
            }.bind(this), delay)

        }
    }

    tahoe.print() // Kirkwood,Squaw,Alpine,Heavenly,Northstar