前言
看标题就知道,这篇文章是学习笔记。这篇文章主要整理两个知识点:this和闭包。这两个知识点在JavaScript中都是比较难懂并且十分重要的。我想,作者也是基于这个考虑才把这两个知识点放在书的前两章。
关于this,不得不说之事
在《JavaScript20年》中,Brendan Eich对于this的描述如下:
每个函数都有一个隐式的
this
形参。将函数作为方法调用时,这个形参会被设置为用于访问该方法的对象。这和大多数面向对象语言中的this
(或self
)含义相同。但是JavaScript在关联到对象的方法与独立函数这两者之间,使用了单一的定义形式。这使this
导致了许多程序员的困惑和bug。
翻译成大白话就是this
是个坑。那么,为什么会有这个设计的坑呢?这就说来话长了。由于这并不是我们的重点,这里就不再赘述了。感兴趣的小伙伴可以戳这里来了解具体的情况。这里我们只需要借用作者的结论就好了:
- this的灵活指向,属于JS自己发明的语言特性
- this存在的问题是公认的
- this的这种设计既不利于代码可读性,也不利于性能优化,完全可以对其施加强限制
- this设计问题的根源,是产品营销需求与设计者个人偏好之间的冲突
this存在这样那样的问题,在实际的代码编写中,我们也尽量少用。但是,面试的时候,面试官似乎还是挺喜欢挑这些问题来问的。而且,作为使用者来说,不能因为他有问题就不用了,纠结语言本身的设计纰漏也没有意义。总之,不管怎么说,我们都需要好好学学this。
关于this的指向,可以分几类:
- 隐式绑定:
- 在函数体(这里指独立的,直接定义在文件中,不是包含在某个对象中作为该对象方法的函数)中,在严格模式下,函数内的
this
会被绑定到undefined
上;在非严格模式下,this
会被绑定到全局对象Windows/global
上 - 当函数作为某个对象的方法,或者说该函数作为某个对象的属性值时,
this
会绑定到该对象上
- 在函数体(这里指独立的,直接定义在文件中,不是包含在某个对象中作为该对象方法的函数)中,在严格模式下,函数内的
- 显式绑定:
- 通过
call/apply/bind
方法可以显式的将this
绑定到某个对象上
- 通过
- new绑定:利用
new
关键字调用某个函数(我们通常称之为构造函数)时,函数会被绑定在新创建的对象上 - 箭头函数:this始终指向外层(函数或全局)的作用域
除此之外,this
指向还有优先级问题:new绑定 -> 显式绑定 -> 隐式绑定 -> 默认绑定。此外,还需要关注的点就是this指向和直接调用它的位置相关。举个栗子:
const foo = {
bar: 10,
fn: function() {
console.log(this)
console.log(this.bar)
}
}
var fn1 = foo.fn
fn1()
foo.fn()
这段代码中,fn1()
输出的结果是windows
和undefined
, 而foo.fn()
输出的结果是{bar: 10, fn: f}
和10。这里就是我们所说的,直接调用含义了。中间不能隔着什么东西,一旦隔着就会导致this指向不同的地方。
最后一个知识点是关于bind
的。call
和apply
是直接进行过i昂管函数调用的;bind
不会执行相关函数,而是返回一个新的函数,这个新的函数已经自动绑定了新的this
指向。
知识点差不多就这么多,如果有想看例题的小伙伴,可以直接参照原书。而且小编之前也总结过this的相关内容,请戳这里
“老司机”也会翻车的闭包
提到闭包就不得不提作用域和内存管理了。闭包的话,我已经在很多地方都总结过了,链接在这里:
在本文中,只需要做个简单的总结就好:
- 函数嵌套函数时,内层函数引用了外层函数作用域下的变量,并且内层函数在全局环境下可访问,进而形成闭包
- 闭包和作用域,作用域链,内存管理,函数调用栈这些概念直接相关:
- 作用域和作用域链:老生常谈的概念,此处不赘述
- 内存管理:对内存生命周期的管理,内存的证明周期包括分配内存、读写内存、释放内存
- 函数调用栈:从字面上即可理解,它就是一个栈,用来保存函数调用的现场的
- 闭包不能乱用:可能会导致内存泄漏