问题的由来
js中一个很容易混淆的很难理解的点就是this的指向问题了,在面试环节this的出场率绝对在90%以上,所以今天来复习下this,探讨一下this的原理。
在很多时候我们会看到这样的写法1
2
3
4
5
6
7
8
9
10
11
12
13
14var obj={
foo:function(){
console.log(this.bar)
},
bar:1
}
var foo=obj.foo
var bar=2
//写法一
obj.foo()//1
//写法二
foo()//2
按照道理来讲obj.foo和foo指向的是同一个函数,但是为什么执行结果不一样呢?
这里就是老生常谈的this的指向问题了
在很多地方我们都会看到这样的说法,this指向的是函数运行时所在的环境(即执行上下文)。对于obj.foo()来说,foo运行在obj环境,所以this指向obj;对于foo()来说,foo运行在全局环境,所以this指向全局环境。所以两次的结果不一样。
这种解释没错,但是函数的运行环境到底是什么决定的?为什么obj.foo()执行环境是obj,foo()在全局环境执行?
内存的数据结构
js之所以有this的设计,跟内存里的数据结构有关系。1
var obj={foo:5}
上面的代码就一个对象赋值给变量obj。js会在内存里生成一个对象{foo:5},然后把这个对象的内存地址赋值给变量obj,也就是说遍历obj里面存储的是一个地址,要想兑取obj.foo的值,js引擎先从变量obj拿到地址,然后再从该地址读取出原始对象的内容,返回foo属性的值。
而原始的对象是以字典结构保存的,没一个属性都对应一个属性描述对象(内容,是否可写等)1
2
3
4
5
6
7
8{
foo: {
[[value]]: 5
[[writable]]: true
[[enumerable]]: true
[[configurable]]: true
}
}
函数
上面的情况是对象的属性只是一个普通的数值,如果属性是一个函数呢?1
2
3var obj={
foo:function(){}
}
这是js引擎会将函数单独保存在内存中,然后再将函数的所在的内存地址赋值给foo属性的value值。
由于函数是单独存放的值,所以它可以在不同的环境(上下文)执行1
2
3
4
5
6var f=function(){}
var obj={f:f}
//单独执行
f()
//obj环境执行
obj.f()
环境变量
js允许在函数体内部,引用当前执行环境的其他变量1
2
3var f=function(){
console.log(x)
}
在上面的代码中,函数体里面使用了变量X,该变量由运行环境提供。
现在问题来了,由于函数可以在不同的运行环境执行,所以需要有一种机制,能够在函数体内部获取当前的运行环境。所以this出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。
1 | var f=function(){ |
在上面代码中,函数f在全局执行,this.x指向全局环境的x
在obj执行环境,this.x指向obj.x
回到开头的问题,obj.foo()是通过obj找到foo,所以就是在obj执行环境。一旦var foo=obj.foo,变量foo就是直接指向函数本身,所以foo就变成在全局环境执行。