天天看点

揭开 this 的神秘面纱

首先,要知道的是 this 是指函数当前的运行环境(上下文)。

我们都知道 this 是函数运行时所在的环境,但我们不知道是函数的运行环境到底是怎么决定的。

var obj = {
   fn:function(){
       console.log(this.title); 
   },
   title:"Hello"
}

var fn = obj.fn;
var title = "Hi";

obj.fn() //"Hello"
fn() //"Hi"
           

 上面的例子中,obj.fn 和 fn 调用的都是同一个函数,但结果却不一样,这就是我们上面说的 this 是根据运行时所在环境不同导致的,对于 obj.fn 来说,fn 是运行来 obj 环境的,所以 this 指向 obj,对于 fn 来说是运行在全局的,因此 this 指向的是全局,也就是window。

那为什么 obj.fn() 就是在 obj 环境运行的,而一旦 var fn = obj.fn 就变成全局环境了呢?下面让我们一点点揭开 this 的神秘面纱。

1. 内存的数据结构

javascript 之所有有 this,是和内存结构有关系的。
var obj = {fn:5}
           

以上代码,将一个对象赋值给变量 obj,在 javascript的 引擎中会先在内存中,生成一个对象 { fn:5 },然后在将这个对象的地址赋给变量 obj。

揭开 this 的神秘面纱

就是说,变量 obj 只是存了一个地址,如果要进行取读操作 obj.fn,那引擎是先从 obj 拿到内存地址,然后在从该地址读出原始对象,然后返回它的 fn 属性。

原始的对象以类似于字典结构的状态保存,每一个属性名都对应一个属性描述对象。拿上面的那个例子的 fn 属性来看

揭开 this 的神秘面纱
注意!fn 属性的值是保存在属性描述对象的 value 属性里面

2. 函数

属性值不是一个函数的时候很清晰,那如果属性的值是一个函数呢?

var obj = { fn: function(){}};
           

当对象的属性值是函数的时候,javascript引擎会单独把函数保存在内存中,然后再将函数的地址赋值给 fn 属性的属性描述对象的value属性上。

揭开 this 的神秘面纱
由于函数是单独的一个值,所以它可以在不同的环境 ( 上下文 ) 中运行。
var fn = function(){ 
    console.log(this.age);
};

var obj = {
    fn: fn,
    age: 5
};
var age = 10;

//单独运行时
fn(); //10

//在obj中运行时
obj.fn(); // 5
           

3. 环境变量

由于 javascript 允许函数体内,引用当前环境的其他变量,而变量是有当前的运行环境提供的,那么由于函数是可以在不同的运行环境中执行,那就需要一种机制,一种能够在函数体内部获得当前运行环境。so,这时 this 出现了,它的目的就是在函数体内部,指向函数当前的运行环境。
function fn (){
    console.log(age); //由运行环境提供
}

var age= 10";

fn(); //10
           

以上代码中的 age 就由该函数运行时所在的环境提供。

再看,下面的例子

function fn(){
    console.log(this.xx);
}

var xx = 10;

var obj = {
    fn: fn,
    xx: 100
}

//单独运行时
fn()//10

//在obj中运行时
obj.fn()//100
           

以上代码中,this.xx 指向运行时所在的环境的 xx。

当 fn() 单独运行时,this.xx 指向全局环境的 xx。

当 obj.fn() 运行时,则 this.xx 指向的是 obj 环境的 xx。

现在,我们回到文章中最开始提出的问题, obj.fn() 是通过 obj 找到的 fn,所以就是在 obj 环境执行,一旦 var fn = obj.fn,则 变量 fn 就直接指向了函数本身,所以 fn() 就变成了全局环境执行。

注意!不要忘记,当对象里的属性名的值是一个函数的时候,这时,javascript 引擎就报函数单独保存在内存中,然后把函数地址给了 属性名的属性描述对象的value上。

继续阅读