JS中的箭头函数的详细指南

113 阅读7分钟

读者你好,一直是JavaScript中最令人困惑的话题之一。如果你以前有一些像Java这样的编程语言的知识,那么理解这个又会是一个更困难的任务,因为你的头脑将不得不在其他语言中已有的这方面的知识和学习JavaScript中的新概念之间进行斗争,尽管它们完全没有重叠。

与其他各种功能和增强功能一起,ES6带来了箭头函数,随着箭头函数的引入,这个概念开始有了一些调整。要理解它的概念和与箭头函数的行为,肯定需要以前对有一些了解。所以,请在这里暂停一下,对这个问题有一些了解,然后再跳回到这个主题上。

目录

  1. 什么是箭头函数?
  2. 用箭头函数理解这种行为
    • 在正常的声明中
    • 在一个全局对象中定义的
    • 在一个对象内部的嵌套函数调用中
    • 全局函数绑定到一个对象
  3. 结论

什么是箭头函数?

除了其他各种功能外,ES6也带来了箭头函数。它们是更新、更容易、更干净的函数编写方式。它们减少了声明一个函数所需的代码行数,并且与普通的函数声明有一些小的区别。
让我们先比较一下普通函数和箭头函数的声明语法:

//normal function
function foo1(){
    return null
}
//arrow functions
const foo2 = () => {
    return null
}
//this can further be written in much simpler way
const foo = () => return null

正如我们所看到的,箭头函数的语法要简单得多,但主要的区别是箭头函数不创建自己的绑定,而是占用其父对象的绑定,即它们从父范围继承它(记住,父应该是一个非函数)。为了简单起见,箭头函数遵循与它们的父对象相同的行为
对于普通函数来说,这完全取决于它们被调用的方式和位置,但对于箭头函数来说,这取决于它们被定义的位置。在上面的例子中,如果我们在两个函数中都使用this,那么
两个函数中都会有一个全局对象,但原因是完全不同的。让我们在下一节中探讨这个问题。

了解箭头函数的这种行为

箭头函数的这种行为在不同的情况下会有不同的表现,所以让我们把它分成几个小部分来看看。

  • 在正常声明中
//normal function
function foo1(){
    console.log(this)
}
//with arrow functions
const foo2 = () => {
    console.log(this)
}

foo1()
foo2()

我们看到在上述情况下,两个控制台都会打印一个全局对象(如果是浏览器则是窗口对象)。函数foo1()是在全局上下文中声明的,并且在全局上下文中被调用,这是一个正常的函数调用。这意味着调用foo1()的对象是一个全局/窗口对象(foo1()也可以作为window.foo1()调用),因此,里面有一个全局对象的值,因此,对于一个正常的函数调用,总是指向调用该函数的对象。而foo2是在全局上下文中声明的,总是指向一个全局对象,因此,在箭头函数中,占用了它的父绑定的值,这里是全局上下文,指向全局/窗口对象。

  • 在一个全局对象内定义的
//normal function
const obj1 = {
foo1 : function(){
        console.log(this)
     }
}
//with arrow functions
const obj2 = {
foo2 : () => {
        console.log(this)
     }
}

obj1.foo1()
obj2.foo2()

要在obj1内部调用普通函数(foo1),我们需要首先访问obj1,然后通过它调用foo1。因此,foo1不是一个普通的函数调用,它必须通过obj1来调用。obj1是一个对象,并且已经创建了自己的绑定*(this*),通过obj1调用foo1使得obj1内部的这个对象只拥有obj1的所有属性,而不是全局/窗口对象。而如果我们看obj2和foo2,那么即使foo2被声明为obj2内部的一个键,但它仍然会有一个从其词法范围即obj2继承的值,因为正如前面所定义的,箭头函数从其父对象继承这个属性,在这里obj2,也就是foo2的父对象已经被声明为全局/窗口上下文,它对全局/窗口上下文有这个绑定,因此,传递给foo2同样的这个(全局/窗口)。

  • 在一个对象内部的嵌套函数调用
//normal function call inside a method
const obj1 = {
        foo1 : function(){
                function foo2(){
                        console.log(this)
                }
                foo2()
        }
}
obj1.foo1()
//arrow function call inside a method
const obj2 = {
        foo3 : function(){
                const foo4 = () =>{
                        console.log(this)
                }
                foo4()
        }
}
obj2.foo3()

如果你仔细看一下这段代码,那么你可以猜到两个控制台的输出,因为它已经在上面两点中解释过了。如果你的猜测与我们将要讨论的内容相同,那么恭喜你了。

所以,在foo2中,控制台将打印全局/窗口对象,原因是在上面的定义中,任何正常的函数调用总是将其指向全局/窗口对象。在这里,foo1是obj1的一个方法,但foo2是在这个方法中声明的一个普通函数,因此指向了全局/窗口对象。在第二种情况下,foo4是一个箭头函数,它从它的词法范围中获取this,所以在这里,foo4的词法范围是foo3,而foo3的this指向obj2对象,因此foo4也将指向obj2。

这里有一个有趣的问题。如果foo3被分配了一个箭头函数而不是一个普通的函数呢?

const obj2 = {
        foo3 : () => {
                const foo4 = () =>{
                        console.log(this)
                }
                foo4()
        }
}
obj2.foo3()

在这种情况下,可以肯定的是,foo4将总是从父级非函数中获取它的这个值,在这种情况下就是obj2,而且由于obj2是一个全局对象,它的绑定对象是一个全局对象,因此foo4的这个值也将指向全局/窗口对象。因此,我们可以看到函数的行为发生了巨大的变化,这取决于它们是普通函数还是箭头函数。

  • 全局函数绑定到一个对象
//normal function
function foo1(){
        console.log(this)
}
const obj1 = {
        name: 'Akansh'
}
const foo2 = foo1.bind(obj1)
foo2()

//arrow function
const foo3 = () => {
        console.log(this)
}
const obj2 = {
        name: 'Akansh'
}
const foo4 = foo3.bind(obj2)
foo4()

给出的例子与我们之前在上面的例子中看到的没有什么不同。唯一的区别是创建了一个全局函数,它现在被绑定到另一个对象上,因此,这些函数现在将被视为在对象内声明的。

foo2将foo1绑定到obj1,因此,调用foo2()就像调用obj1.foo1()一样(如果foo1是在obj1内部声明的),因此,foo2的this将只指向obj1。类似地,foo4携带foo3绑定到obj2,但在这里foo4的this指向全局/窗口对象,因为我们现在知道,对象内部的箭头函数将有this指向对象的绑定,这里是全局/窗口对象。

结论

对于一个正常的函数调用,将总是指向全局/窗口对象。一个定义在对象内部的普通函数将总是从相邻的左边写着点符号的对象中获取这个。例子。

const obj = {
    foo : function(){
         console.log(this)
    }
}
obj.foo()
//here, obj is the object calling foo and is on the adjacent left while writing in dot notation

对于箭头函数,最简单的解释是,将总是从它的非功能父对象中继承绑定。如果定义在全局上下文中,那么箭头函数中的这个对象只指向全局/窗口对象。

我相信这个解释对你有一些帮助,提供了一些价值。如果有什么东西我可能遗漏了,或者你希望我解决,或者有一些建议,请随时评论和联系。