1.非箭头函数下的 this
var obj = { x: 0, f1: function () { console.log(this.x); } } var f1 = obj.f1; var x = 1; obj.f1(); //0 f1(); //1
上面代码中,虽然 obj.f1 和 f1 指向的是同一个函数,但是执行的结果却不一样。这种差异的原因,就在于函数体内使用了 this 关键字。我们都知道,this 指的是函数运行时所在的环境。对于 obj.f1() 来说,f1 运行在 obj 环境下,所以this 指向obj ;对于 f1() 来说,f1() 运行在全局环境,所以 this 指向全局环境。因此,两者的运行结果不一样。
现在我们来想想,为什么会这样呢?为什么obj.f1() 就是在 obj 环境执行,而 var f1 = obj.f1 之后,f1() 就是在全局环境执行呢?
那就要从内存里面的数据结构说起了,我们一步一步来看。
var obj = { x: 0, f1: function () { console.log(this.x); } }
上面代码中,将一个对象赋值给变量 obj ,javascript 引擎会先在内存中,生成一个对象,然后再将这个对象的内存地址赋值给变量 obj。也就是说,变量obj 是一个地址。后面如果要读取 obj.x,引擎首先从 obj 拿到内存地址,然后再从该地址读出原始的对象,返回 x 属性。
如上图所示,对象的每一个属性名都对应一个属性描述对象,属性的值保存在属性描述对象的 value 属性里面。
属性的值为函数时,引擎会将函数单独保存在内存中,然后再将函数的地址赋值给 f1 属性的 value 属性。由于函数是一个单独的值,所以它可以在不同的环境(上下文)执行。
我们可以在函数内部,引用当前环境的其他变量。那么,是怎样在函数内部获得当前的运行环境的呢?答案就是 this ,this 的设计目的就是在函数体内部,指代函数当前的运行环境。
现在我们可以回答一开始的问题了, obj.f1() 是通过 obj 里存的地址找到 f1, 所以是在 obj 环境下执行的;而 var f1 = obj.f1 之后,f1() 直接指向函数本身,所以 f1() 就是在全局环境执行.
2.箭头函数中的this
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this。箭头函数导致 this 总是指向函数定义生效时所在的对象。
function Timer(){ this.s1 = 0; this.s2 = 0; //箭头函数 setInterval(() => this.s1++, 1000); //普通函数 setInterval(function (){ this.s2++; }, 1000); } var timer = new Timer(); setTimeout(() => console.log('s1: ', timer.s1), 3100); //s1: 3 setTimeout(() => console.log('s2: ',timer.s2), 3100); //s2: 0
上面代码中, Timer 函数内部设置了两个定时器,分别使用了箭头函数和普通函数。箭头函数的 this 绑定定义时所在的作用域(即 Timer 函数),普通函数的 this 指向运行时所在的作用域(即全局对象)。var timer = new Timer() 这条语句执行之后,再过1000毫秒,箭头函数定义生效,this总是指向函数定义生效时所在对象,即timer;对于普通函数,因为setTimeout()
调用的代码运行在与所在函数完全分离的执行环境上,这会导致 this 指向 window 对象。所以3100毫秒之后,timer.s1 被更新了3次,而 timer.s2 一次都没更新。
箭头函数中,this 对象的指向是固定的。这种指向的固定化,实际是因为箭头函数根本没有自己的 this ,导致内部的 this 就是外层代码块的 this 。正是因为它没有this,所以也就不能用作构造函数。
var name = 'window'; var A = { name : 'A', show: () => { console.log(this.name); } } A.show(); //window
如上面代码中的箭头函数,即 show 函数内没有 this,其内部的 this 就是外层代码块的 this,因为没有其他函数的包裹,所以最外层代码块的this指向的就是window对象。所以最后会输出window。
那么,怎样改成永远绑定A呢?
var name = 'window'; var A = { name: 'A', show: function() { var s = () => console.log(this.name); return s; } } var show = A.show(); show(); //A var B = { name: B } show.call(B); //A show.call(); //A
这样就做到了永远指向 A 对象,我们再来分析一下:
原代码中 show 即是箭头函数,没有自己的 this;修改之后的代码中,箭头函数所在的作用域是在 show 函数内,show 是普通函数,有自己的 this,在函数调用时生效。A.show() 指向的对象是 A。所以箭头函数 s 中的 this 就是指向 A 了。