我们由一段代码开始本文的主题。

1
2
3
4
5
6
7
8
9
10
11
12
var obj = {
foo: function () {
console.log(this.bar)
},
bar: 1
};

var foo = obj.foo;
var bar = 2;
foo === obj.foo //true
obj.foo() // 1
foo() // 2

可以看到,fooobj.foo 实际上是严格相等的,它们是指向同一个代码段。由于 上下文环境 不同,所以他们运行的结果也不一样。obj.foo 的上下文环境就是 this 指向的 obj,而 foo 的上下文环境是全局环境。

JavaScript 的内存数据结构

我们需要深入到 JavaScript 的内存数据结构去寻找答案。

1
let obj = { foo:1 };

我们把一个对象 { foo:1 } 赋值给对象 obj。这个过程实际上是 JavaScript 引擎在内存中生成一个 { foo:1 } 然后再把这个对象的内存地址赋值给 obj

这其实就跟 C 语言中的指针概念相似,先分配一块内存,初始化后,把内存的地址赋给一个指针。obj 起的就是一个指针的作用,它保存一个地址,JavaScript 引擎读取它时是直接从 obj 中取内存地址,然后再去改地址取原始对象。

原始对象以字典结构存储。对象的每一个属性也都对应一个属性描述对象。value 保存的是属性的值。

函数的内存结构

如果属性是一个函数,内存中的数据结构是怎样的?

1
var obj = { bar: function () {} };

这个时候函数其实就是属性的值,函数在计算机中实际上就是一段代码段,它占据着一段内存。此时属性的值并不是直接保存这段代码段,而是保存它对应的内存地址,属性是指向这个函数的指针。

那么运行 bar=obj.bar 会发生什么呢?只是把 bar 这个变量也指向了 obj.bar 指向的函数所占据的内存的地址。

所以我们知道了,函数只是一个内存中存储的代码段(执行逻辑),它可以在不同的上下文 (运行环境) 中执行。

环境变量

函数如何获得上下文 (context) 呢?this 正是起这个作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var f = function () {
console.log(this.x);
} // 该代码段被保存于一个内存区域 A

var x = 1;
var obj = {
f: f, // 指向 A
x: 2,
};

//this 指代当前上下文 即全局环境
f() //this.x==(global).x==1

//this 指代 obj
obj.f() //this.x==obj.x==2

执行函数的时候会检查调用函数的指针的上下文,而函数中的 this 指代的正是这个函数指针所在的上下文。