迫于
迫于在写 React 事件处理中的 this 时,写到 this
突然卡壳了,一顿冷静分析之后发现自己对于 this
的理解又忘了一些,于是痛定思痛,重新读了 《You-Dont-Know-JS》中关于 this
的文章。
函数的执行环境
首先上一段代码
function fn(){
console.log(this.a)
}
let obj = {
a: 1,
fn: fn
}
var a = 'window'
fn() //window
obj.fn() //1
首先我们要明确一点 ,this
不是编写代码时就绑定的,而是运行时绑定的。
比如 fn()
在调用时,所处的环境是 global
,所以他的 this
就是 window
而 obj.fn()
在调用时,所处的环境是在对象 obj
内,所以他的 this
指向 obj
对象
当一个函数被调用时,会建立一个称为执行环境的活动记录。这个记录包含函数是从何处(调用栈 —— call-stack)被调用的,函数是 如何 被调用的,被传递了什么参数等信息。这个记录的属性之一,就是在函数执行期间将被使用的 this 引用。
所以,this
是根据调用点(函数如何被调用)来为每次函数调用建立的绑定
调用点的规则
-
默认绑定: 首先是独立函数调用。在这种倾向下,
this
规则是在没有其他规则适用时的默认规则。function fn(){ console.log(this) } fn() //window
fn()
被独立调用,此时的this
是默认绑定,所以this
指向了window
-
隐含绑定 这种规则是:调用点是否拥有一个对象环境。
function fn(){ console.log(this.a) } let obj = { a: 1, fn: fn } obj.fn() //1
首先,
fn
被声明然后作为引用属性添加到obj
上,我们知道,对象、函数、数组等都是引用类型,存在于堆内存中,以传值(地址)的方式被变量引 用,所以obj
并不包含或者拥有函数fn
obj.fn()
的调用点以obj
环境来引用函数,所以,函数在被调用的时间节点上拥有对象环境obj
对象属性引用链的最后一层是影响调用点的
function fn() { console.log( this.a ); } let obj2 = { a: 42, fn: fn }; let obj1 = { a: 2, obj2: obj2 }; obj1.obj2.fn(); // 42
-
隐含丢失 隐含丢失:隐含绑定丢失了他的绑定,如何丢的?看代码
function fn(){ console.log(this.a) } let obj = { a: 1, fn: fn } let foo = obj.fn var a = 'window' foo() //window
这段代码,将
foo
引用了obj.fn
,但是在foo()
,他依然属于函数独立调用,所以this
指向全局作用域的a
function fn(){ console.log(this.a) } function bar(f){ f() } let obj = { a: 1, fn: fn } var a = 'window' bar(obj.fn) // window
这段代码中,将
obj.fn
作为回调函数传入bar
中,但是bar
调用回调函数的方式依然属于 函数独立调用,所以this
指向 全局作用域的a
function fn(){ console.log(this.a) } let obj = { a: 1, fn: fn } var a = 'window' setTimeout(obj.fn, 1000) // window
这段代码跟上一段一样。
-
箭头函数 箭头函数的
this
不是在运行时生效的,而是在定义这个箭头函数时生效的let obj = { fn(){ setTimeout(function(){ console.log(this) }, 1000) }, arrow(){ let a = () => {console.log(this)} setTimeout( a, 1000) } } obj.fn() //window obj.arrow() //对象obj
obj.fn
中setTimeout
中的回调函数相当于函数独立调用,this
指向window
,obj.arrow
中的setTimeout
中的回调函数是一个 箭头函数,而箭头函数的this
时函数定义生效时所在的对象,也就是obj
,arrow
中的a
引用了箭头函数,而这个箭头函数是在arrow
执行时定义的,所以所指向的对象环境为obj
。