规则0:函数本身是一个特殊类型,大多数时候,可以认为是一个变量。
function a()
{
alert(this);
}
或者
var a = function()
{
alert(this);
}
都可以认为是创建了一个变量,这个变量的值就是一个函数。
规则1:如果一个函数,是某个对象的key 值,那么,this就指向这个对象。
这个规则很好理解:
var a = function(obj)
{
alert(this == obj);
}
var o = {};
o.afun = a;
o.afun(o); //true
函数就是一个变量,但是可以绑定到某个对象的下面,并且 this 就会指向 o 对象。
这里必须要注意,没有被绑定的对象,默认this 指向window 对象。
举几个例子:
function a()
{
//this == window
}
function a()
{
//this == window
function b()
{
//this == window
}
}
还必须注意的是,绑定没有传递性,比如上面的嵌套的函数,a绑定到 o 对象,那么就影响了a函数,
而b 还是指向到window。
规则2:如果函数new 了一下,那么就会创建一个对象,并且this 指向 新创建的对象。
var o = new a();
这个时候,o 不再是个函数,而实际上,可以认为是这样的一个过程。
创建一个对象 var o = {};
然后,把this 指向 o,通过this 把 o 给初始化了。
规则3:通过apply 可以改变this 的指向
这个apply 的绑定就更加的灵活了,实际上,apply的功能和下面的功能差不多。
var a = function (obj)
{
alert(this == obj);
};
obj.fun = a;
obj.fun(obj);//true
简单的,可以a.apply(obj, [obj]); // true
javascript 的this 可以简单的认为是 后期绑定,没有地方绑定的时候,默认绑定window。
综合实例:
jquery 里面有一个很常用的函数 each,可以把循环的对象元素绑定到this,方便操作。
这里只是简单的做个演示:
代码
function each(tagName, callback)
{
var lists = document.getElementsByTagName(tagName);
for (var i = 0; i < lists.length; i++)
{
callback.apply(lists[i]);
}
}
each("a",
function ()
{
this.style.color = "red";
}
);
可以改变this指向的方法
ES6 箭头函数this指向
箭头函数有几个使用注意点。
(1)函数体内的this
对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new
命令,否则会抛出一个错误。
(3)不可以使用arguments
对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替。
(4)不可以使用yield
命令,因此箭头函数不能用作Generator函数。
上面四点中,第一点尤其值得注意。this
对象的指向是可变的,但是在箭头函数中,它是固定的。
function foo() { setTimeout(() => { console.log('id:', this.id); }, 100); } var id = 21; foo.call({ id: 42 }); // id: 42
上面代码中,setTimeout
的参数是一个箭头函数,这个箭头函数的定义生效是在foo
函数生成时,而它的真正执行要等到100毫秒后。如果是普通函数,执行时this
应该指向全局对象window
,这时应该输出21
。但是,箭头函数导致this
总是指向函数定义生效时所在的对象(本例是{id: 42}
),所以输出的是42
。
箭头函数可以让setTimeout
里面的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); setTimeout(() => console.log('s2: ', timer.s2), 3100); // s1: 3 // s2: 0
上面代码中,Timer
函数内部设置了两个定时器,分别使用了箭头函数和普通函数。前者的this
绑定定义时所在的作用域(即Timer
函数),后者的this
指向运行时所在的作用域(即全局对象)。所以,3100毫秒之后,timer.s1
被更新了3次,而timer.s2
一次都没更新。
箭头函数可以让this
指向固定化,这种特性很有利于封装回调函数。下面是一个例子,DOM事件的回调函数封装在一个对象里面。
var handler = {
id: '123456', init: function() { document.addEventListener('click', event => this.doSomething(event.type), false); }, doSomething: function(type) { console.log('Handling ' + type + ' for ' + this.id); } };
上面代码的init
方法中,使用了箭头函数,这导致这个箭头函数里面的this
,总是指向handler
对象。否则,回调函数运行时,this.doSomething
这一行会报错,因为此时this
指向document
对象。
this
指向的固定化,并不是因为箭头函数内部有绑定this
的机制,实际原因是箭头函数根本没有自己的this
,导致内部的this
就是外层代码块的this
。正是因为它没有this
,所以也就不能用作构造函数。
所以,箭头函数转成ES5的代码如下。
// ES6
function foo() { setTimeout(() => { console.log('id:', this.id); }, 100); } // ES5 function foo() { var _this = this; setTimeout(function () { console.log('id:', _this.id); }, 100); }
上面代码中,转换后的ES5版本清楚地说明了,箭头函数里面根本没有自己的this
,而是引用外层的this
。
请问下面的代码之中有几个this
?
function foo() { return () => { return () => { return () => { console.log('id:', this.id); }; }; }; } var f = foo.call({id: 1}); var t1 = f.call({id: 2})()(); // id: 1 var t2 = f().call({id: 3})(); // id: 1 var t3 = f()().call({id: 4}); // id: 1
上面代码之中,只有一个this
,就是函数foo
的this
,所以t1
、t2
、t3
都输出同样的结果。因为所有的内层函数都是箭头函数,都没有自己的this
,它们的this
其实都是最外层foo
函数的this
。
除了this
,以下三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量:arguments
、super
、new.target
。
function foo() { setTimeout(() => { console.log('args:', arguments); }, 100); } foo(2, 4, 6, 8) // args: [2, 4, 6, 8]
上面代码中,箭头函数内部的变量arguments
,其实是函数foo
的arguments
变量。
另外,由于箭头函数没有自己的this
,所以当然也就不能用call()
、apply()
、bind()
这些方法去改变this
的指向。
(function() { return [ (() => this.x).bind({ x: 'inner' })() ]; }).call({ x: 'outer' }); // ['outer']
上面代码中,箭头函数没有自己的this
,所以bind
方法无效,内部的this
指向外部的this
。
长期以来,JavaScript语言的this
对象一直是一个令人头痛的问题,在对象方法中使用this
,必须非常小心。箭头函数”绑定”this
,很大程度上解决了这个困扰。