JS 进阶必备 —— 闭包、this、箭头函数的实践笔记

闭包、this 和箭头函数是三个常见面试题,也是 js 进阶之路上的拦路虎。这次还用实践熟悉这三个问题。

this 实践

demo

写了一段代码来验证 this 的指向。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
console.log('全局环境的this', this)

function test (){
console.log('方法中的 this', this)

function child(){
console.log('方法中的方法的 this', this)
}
child()
}
test()

var obj = {
name: "object",
doSth: function() {
var oName = "obj function name"
console.log('对象中的 this', this)
},
childObj: {
name: "childObj",
doSth: function() {
var arrow = () => {
console.log("对象中箭头函数的this", this)
}
arrow()
console.log('对象的对象中的 this', this)
}
},
doBibao: function() {
var count = 500
console.log('闭包构造方法的 this', this)
return function() {
console.log('闭包返回结果的 this', this)
}
}
}
obj.doSth()
obj.childObj.doSth()
var mbb = obj.doBibao()
mbb()

setTimeout(obj.doSth, 1800)
setTimeout(obj.doSth.bind(obj), 2000)

var fun = function() {
console.log('匿名函数中的 this', this)
}
fun()

class Vue {
constructor(options){
this.name = "vue"
this.type = "object"
this.options = options
console.log('构造函数的 this', this)

options.log()
}
}

var vm = new Vue({
log: function () {
console.log('构造函数找那个传递方法的 this', this)
}
})

function bibao (){
var count = 101
console.log('闭包外的 this', this)
return function() {
count++
console.log('闭包中的 this', this)
return count;
}
}
var bi = bibao()
console.log(bi())

var mArrow = () => {
console.log('箭头函数中的 this', this)
}
mArrow()
console.log('以下内容为异步执行')

setTimeout(() => {
console.log('延时箭头函数中的 this', this)
}, 1000)

最后结果如图:
console.log

从中可以得到一些结论(以下都是非严格模式下的测试结果):

  • 全局变量的 this 指向 Window。
  • 全局变量中的具名函数、匿名函数、闭包函数、箭头函数都指向 Window。
  • 在对象中同步调用,this 指向当前对象。
  • 在对象中异步调用,this 已重新指向 Window,如果需要指向对象需要使用 bind() 方法改变 this 指向。

个人理解:this 作为上下文始终会有一个唯一指向对象。这个对象要么指向 Window 要么指向当前对象。当调用对象中方法时,方法中的 this 即指向方法。之后 this 会重新指向 Window。

call & apply & bind

说到 this 不得不说下函数的间接调用方法 apply 和 call 了。他们作用相同,唯一不同点在于 apply 方法的第二个参数接收一个参数数组。而 call 方法接收若干个参数。这两个方法的第一个参数传递的都是 this 指向。

1
2
3
4
5
6
7
function sayHello(p1, p2) {
console.log(`hello ${p1} and ${p2}`)
}

sayHello('jack', 'rose')
sayHello.call(this, 'jack', 'rose')
sayHello.apply(this, [ 'jack', 'rose' ])

如上代码,其实输出结果是一样的。第一种写法是一种语法糖,第二种和第三种才是真正的方法执行。可以看到它们的第一个参数为 this。所以,call 方法和 apply 方法是可以改变函数的 this 指向的。
而我们之前提到的 bind 函数用于重新指定函数的 this 并且创建出一个新的函数的。引用 MDN 上的说法就是:

bind() 方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。

箭头函数和一般函数的区别

箭头函数使用更加简洁的表达方式替代匿名函数而深受喜爱。然而箭头函数与一般匿名函数的不同点在于:箭头函数拥有静态的上下文环境,不会因为不同的调用而改变。
以下是我理解的静态函数与一般函数的不同点:

  • 在箭头函数中,this 是静态的不可变的,所以bind、call和apply方法修改this不起作用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var mor = {
    name: "mor"
    }

    var ar = () => {
    console.log("箭头函数的this变化", this)
    }

    ar() // Window
    ar.call(mor, 123) // Window 如果是匿名函数则返回 mor 对象结果
    var newAr = ar.bind(mor)
    newAr() // Window 如果是匿名函数则返回 mor 对象结果
  • 箭头函数拥有静态的上下文环境,不会因为不同的调用而改变。如下例子中箭头函数的 this 指向了 Window 对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    var person = {
    sex: "male",
    age: 28
    }

    person.log = function(){
    console.log("01:" + this.sex + "-" + this.age) // 指向 person 对象
    }

    person.log02 = () => {
    console.log("02:" + this.sex + "-" + this.age) // 指向 Window
    }

    person.log() // 01:male-28
    person.log02() // 02:undefined-undefined

闭包函数的原理和用途

这里来简单实现一个闭包:

1
2
3
4
5
6
7
8
9
10
function add() {
var a = 100
var b = 50
return function(){
console.log(a + b)
}
}

var count = add()
count() // 150

我对闭包的理解就是:在函数中返回函数表达式的写法。

闭包的主要用途有:

  • 避免被垃圾回收机制回收方法结果和变量,使变量始终保存在内存中,实现缓存的功能。如需清空缓存需要将值变为 null。
  • 通过闭包获取方法中的局部变量。

最后

注意,以上都是本人对于这些知识点的理解,可能会有描述不太准确的地方。如有错误还请评论指出,万分感谢。
这里简单记录了一下我对于 this、闭包和箭头函数的理解~更多内容可以看下我提供的参考资料。

参考资料