JavaScript的函数、箭头函数与this

JavaScript在ES6语法中新增了箭头函数,相较于传统函数,箭头函数不仅更加简洁,而且在this方面进行了改进。this作为JavaScript中比较诡异的存在,许多文章对于this的解释也不尽相同,本篇文章试图厘清JavaScript中函数与this的关系。

1. JavaScript的函数写法

1.1 常规函数写法

在ES6语法之前,JS中的函数由function关键字、params参数和被花括号包裹的函数体组成。为了与后面说到的箭头函数相区别,我们先把这样的函数叫做常规函数,常规函数既可以用声明式写法也可以用赋值式写法。例子:

function test(name) {  //声明式写法
    console.log(name)
}
test('Jerry')

let test2 = function(name) {  //赋值式写法
    console.log(name)
}
test2('Tom')

1.2 箭头函数写法

ES6箭头函数的引入,使函数的写法变的更加简洁,但在书写上要遵循一定的规则。
规则一:箭头函数只能用赋值式写法,不能用声明式写法

const test = (name) => {
    console.log(name)
}
test('Jerry')

规则二:如果参数只有一个,可以不加括号,如果没有参数或者参数多于一个就需要加括号

const test = name => {
    console.log(name)
}
test('Jerry')

const test2 = (name1, name2) => {
    console.log(name1 + ' and ' + name2)
}
test2('Tom', 'Jerry')

规则三:如果函数体只有一句话,可以不加花括号

const test = name => console.log(name)

规则四:如果函数体没有括号,可以不写return,箭头函数会帮你return。也就是说函数体的花括号与return关键字同在。函数体的花括号与return关键字同在

const add = (p1, p2) => p1 + p2
add(10, 25)

从上面的例子可以看出,箭头函数对常规函数的圆括号花括号都进行了简化。除了这些简化,箭头函数对于常规函数最大的优化之处在于this

2 常规函数中的this

在探讨箭头函数对于 this 的优化之前,我们先得明白 this 究竟是什么,以及它是如何使用的。 this 是使用 call 方法调用函数时传递的第一个参数,它可以在函数调用时修改,在函数没有调用的时候, this 的值是无法确定。

如果没有使用过 call 方法来调用函数的话,上面的对于 this 的定义可能不太明白。那么我们需要先理解函数调用的两种方法。

2.1 纯粹的函数调用

纯粹的函数调用方法最常见,例子如下

function test(name) {
    console.log(name)
    console.log(this)
}
test('Jerry')  //调用函数

这种方法我们使用的最多,但是这种函数调用方法只是一种简写,它完整的写法是下面这样的:

function test(name) {
    console.log(name)
    console.log(this)
}
test.call(undefined, 'Tom')

注意到上面调用函数的 call 方法了吗? call 方法接收的第一个参数就是 this ,这里我们传了一个 undefined 。那么,依据定义,函数执行了之后打出来的 this 会是 undefined 吗?也不是。如果你传的 context 是 null 或者 undefined ,那么 window 对象就是默认的 context(严格模式下默认 context 是 undefined )。所以这里我们打出来的 this 是 window对象。

2.2 对象中函数的调用

直接看例子:

const obj = {
    name: 'Jerry',
    greet: function() {
        console.log(this.name)
    }
}
obj.greet()  //第一种调用方法
obj.greet.call(obj) //第二种调用方法

例子里第一种调用方法只是第二种调用方法的语法糖,第二种才是完整的调用方法,而且第二种方法厉害的地方在于它可以手动指定 this 。

手动指定 this 的例子:

const obj = {
    name: 'Jerry',
    greet: function() {
        console.log(this.name)
    }
}
obj.greet.call({name: 'Spike'})  //打出来的是 Spike

2.3 构造函数中的this

构造函数里的this稍微有点特殊,每个构造函数在 new 之后都会返回一个对象,这个对象就是 this ,也就是 context 上下文。

function Test() {
    this.name = 'Tom'
}
let p = new Test()
console.log(typeof p)  //object
console.log(p.name)    // Tom

2.4 setTimeout 和 setInterval 中的函数调用

window.setTimeout()window.setInterval()的函数中的 this 有些特殊,里面的 this 默认是 window 对象。

简单总结一下:函数完整的调用方法是使用 call 方法,包括test.call(context, name)obj.greet.call(context,name),这里的 context 就是函数调用时的上下文,也就是 this ,只不过这个 this 是可以通过 call 方法来修改的;构造函数稍微特殊一点,它的 this 直接指向 new 之后返回的对象;window.setTimeout()window.setInterval()默认的是 this 是 window 对象。

3 箭头函数中的this

上面关于 this 讲了很多,this 是函数用 call 方法调用时传递的第一个参数,而且它还可以手动更改,这样要确定 this 的值就太麻烦了。不过,箭头函数的出现给我们确定 this 帮了一些忙。

3.1 箭头函数特性一:默认绑定外层this

上面提到:this 的值是可以用 call 方法修改的,而且只有在调用的时候我们才能确定 this 的值。而当我们使用箭头函数的时候,箭头函数会默认帮我们绑定外层 this 的值,所以在箭头函数中 this 的值和外层的 this 是一样的。

不使用箭头函数例子:

const obj = {
    a: function() { console.log(this) }
}
obj.a()  //打出的是obj对象

使用箭头函数的例子:

const obj = {
    a: () => {
        console.log(this)
    }
}
obj.a()  //打出来的是window

在使用箭头函数的例子里,因为箭头函数默认不会使用自己的 this ,而是会和外层的 this 保持一致,最外层的 this 就是 window 对象。

3.2 箭头函数特性二:不能用call方法修改里面的this

这个也很好理解,我们之前一直在说,函数的 this 可以用 call 方法来手动指定,而为了减少 this 的复杂性,箭头函数无法用 call 方法来指定 this

const obj = {
    a: () => {
        console.log(this)
    }
}
obj.a.call('123')  //打出来的结果依然是window对象

上文我们提到window.setTimeout()中函数里的 this 默认是 window ,我们也可以通过箭头函数使它的 this 和外层的 this 保持一致:

const obj = {
    a: function() {
        console.log(this)
        window.setTimeout(() => {
            console.log(this)
        }, 1000)
    }
}
obj.a.call(obj)  //第一个this是obj对象,第二个this还是obj对象

通过上面的例子,想必大家明白了,函数obj.a没有使用箭头函数,因为它的 this 还是 obj ,而window.setTimeout里的函数使用了箭头函数,所以它会和外层的 this 保持一致,也是 obj ;如果window.setTimeout里的函数没有使用箭头函数,那么它打出来的应该是 window 对象。

4 多层对象嵌套里函数的this

箭头函数里的 this 是和外层保持一致的,但是如果这个外层有好多层,那它是和哪层保持一致呢?

直接上例子:

const obj = {
    a: function() { console.log(this) },
    b: {
        c: function() {console.log(this)}
    }
}
obj.a()  // 打出的是obj对象, 相当于obj.a.call(obj)
obj.b.c() //打出的是obj.b对象, 相当于obj.b.c.call(obj.b)

上面的代码都符合直觉,接下来把 obj.b.c 对应的函数换成箭头函数,结果如下:

const obj = {
    a: function() { console.log(this) },
    b: {
        c: () => {console.log(this)}
    }
}
obj.a()   //没有使用箭头函数打出的是obj
obj.b.c()  //打出的是window对象!!

MDN 官方文档里面描述箭头函数 this 定义的时候,描述的是“箭头函数不会创建自己的 this,它只会从自己的作用域链的上一层继承 this”,所以最后一个例子,打出 window 对象的原因,是 window对象就是它的上一层 this ,而多层嵌套只是对象嵌套,这时候没有作用域链的嵌套。

转载自: JS中的箭头函数与this

posted @ 2021-08-03 11:03  lqqgis  阅读(168)  评论(0编辑  收藏  举报