this 陷阱与原理
前言
this 在很多地方都有解释。听得最多的说法就是 this 是由调用的位置来决定的。话虽然没有错,但是什么是调用的位置,还是非常含糊不清。
另外,this 绑定中有
显式绑定
隐式绑定
默认绑定
又分别是什么?看定义的名称都非常的抽象。如果我们仅仅是面试前记住对应的概念,而不知道其中的原理,这种知识会非常容易遗忘。
陷阱
const obj = {
foo: function () {
console.log(this)
}
}
const foo = obj.foo
// 写法一
obj.foo()
// 写法二
foo()答案:
写法一的 this 指向 obj 对象,但是写法二的obj指向 window 对象 / undefined
很多教科书会说,this指的是函数运行时所在的环境。对于obj.foo()来说,foo运行在obj环境,所以this指向obj;对于foo()来说,foo运行在全局环境,所以this指向全局环境。所以,两者的运行结果不一样。
这种解释没错,但是教科书往往不告诉你,为什么会这样?也就是说,函数的运行环境到底是怎么决定的?举例来说,为什么obj.foo()就是在obj环境执行,而一旦var foo = obj.foo,foo()就变成在全局环境执行?
原理
定义的对象的 JS 过程

JavaScript 引擎会先在内存里面,生成一个对象{ foo: 5 },然后把这个对象的内存地址赋值给变量obj
变量obj是一个地址(reference)。后面如果要读取obj.foo,引擎先从obj拿到内存地址,然后再从该地址读出原始的对象,返回它的foo属性。
原始的对象以字典结构保存,每一个属性名都对应一个属性描述对象。举例来说,上面例子的foo属性,实际上是以下面的形式保存的。

上面的例子中,value 一个数值,但是,value也可以是一个函数。

实际示例
单独运行

对象运行

总结
回到本文开头提出的问题,obj.foo()是通过obj找到foo,所以就是在obj环境执行。一旦var foo = obj.foo,变量foo就直接指向函数的地址,所以foo()就变成在全局环境执行。
延伸1 - this 的优先级
下面将阐述为什么有这样的优先级。
call / apply
apply和call所做的事情都相同,那就是改变函数内部的this指向并调用它。
模拟实现 call
new
new运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。new 关键字会进行如下的操作:
创建一个空的简单JavaScript对象(即{}
链接该对象(即设置该对象的构造函数)到另一个对象
将步骤1新创建的对象作为this的上下文
如果该函数没有返回对象,则返回this。
延伸2 - class this 调用陷阱
😒 问题:对象解构后丢失 this 作用域
😁 因此有这样的改写
延伸3 - 回调函数的 this 陷阱
箭头函数
于是 class 中的 this 调用改写
对象方法中的 this 改写
再次总结
this 指向当前运行环境下调用的对象
this 的执行可以被 call/apply/bind 改写
箭头函数从句法上定义 this 指向,不能被 call/apply/bind 修改
最后更新于
这有帮助吗?