这里来个倒叙,先说一下this的几种情况指向:
- 形如obj.fn()的this指向obj,形如fn()的this指向window
- 匿名函数的this指向window
- 事件回调函数的this指向dom元素
- 定时器的回调函数指向window
- 构造函数中的this指向实例
- 可以使用call、apply、bind来改变this的指向
下面开始:
我自己结合网上的定义,给出了this的定义:
函数中this指向了执行时,调用它的并且离它最近的对象
不过这个定义只适用于大部分情况:
先看一段代码:
function fn() {
console.log(this);
}
fn();//window
如果你知道在全局作用域中定义函数变量和函数实际是在window变量上添加属性和方法的话,那么上面的代码就很好理解了,上面的代码相当于:
function fn() {
console.log(this);
}
window.fn();//window
最后调用这个函数的对象是window对象,所以this就指向了window,再看:
var obj = {
fn: function() {
console.log(this);
}
}
obj.fn(); //this指向obj,相当于window.obj.fn();
上面的代码我们可以看到是window的obj对象调用了fn这个函数,所以定义的时候我们强调是离它最近的调用对象,这里obj离得近,this就指向了obj。
定义时还强调了是执行时,是什么意思呢?接着看代码:
function fn() {
console.log(this)
}
var obj = {
fn: fn
}
obj.fn(); //this指向obj
定义时,函数是在全局中定义的,但是执行时我们是利用obj来调用,它就指向了obj,如果还不太确定,我们再看:
var obj = {
fn: function() {
console.log(this)
}
}
var fn = obj.fn;
fn(); //this指向了window
这下相信了吧,下面介绍几种比较特殊的情况:
匿名函数中的this
匿名函数具有全局性,所以this指向的是window。
(function(){
console.log(this); //window
})()
var obj = {
fn: function() {
(function(){
console.log(this);
})()
}
}
obj.fn(); //window
上面第二段代码中,尽管是obj调用了fn函数,但是在fn函数中的匿名函数仍然具有全局性,所以this仍然指向window。
函数普通调用的this
普通调用是什么意思呢?就是形如:fn();不是作为某个对象的方法。上面有段代码:
var obj = {
fn: function() {
console.log(this);
}
}
obj.fn(); //this指向obj
我们稍微修改一下:
var obj = {
fn: function() {
function innerFn() {
console.log(this);
}
innerFn();
}
}
obj.fn(); //输出window
是不是有些懵逼了,尽管innerFn是在obj的fn函数中被调用的,但是它的作用域链上活动对象只有innerFn和全局本身(ES6以前,JavaScript作用域只有函数域),我猜测,在利用obj.fn()调用的时候,JavaScript内部是做了this指向处理的,而普通调用就指向了全局。
有人可能会问,如果我要调用外层中的this怎么办?通常我们会使用一个变量来保存this,例如:
var obj = {
fn: function() {
var self = this;
function innerFn() {
console.log(self);
}
innerFn();
}
}
obj.fn(); //输出obj
定时器回调函数中的this
setTimeout(function(){
console.log(this); //window
}, 1000)
var obj = {
fn: function() {
console.log(this);
}
}
setTimeout(obj.fn, 1000) //输出window
setTimeout回调函数你可以看做是下面这样:
var obj = {
fn: function() {
console.log(this);
}
}
var callback = obj.fn;
//在设定时间后执行回调函数
callback();
上面你可能就很熟悉了,调用fn函数的是全局对象,所以指向了window对象,如果你想改变定时器中函数this的指向,可以使用bind函数:
var obj = {
fn: function() {
console.log(this);
}
}
setTimeout(obj.fn.bind(obj), 1000) //输出obj
事件处理函数
在JavaScript中我们可以这样绑定一个事件:
<div id="div">这是一个div元素</div>
function doClickDiv(e) {
//to do something
console.log(this);
}
var oDiv = document.getElementById('div');
//绑定点击事件
oDiv.addEventListener('click', doClickDiv, false);
当点击时,输出时this指向了div这个节点,早期绑定事件的写法可以帮助我们理解:
function doClickDiv(e) {
//to do something
console.log(this);
}
var oDiv = document.getElementById('div');
//绑定点击事件
oDiv.onclick = doClickDiv;
当点击div时,会触发oDiv.onclick函数,相当于oDiv.onclick(),这和使用对象调用是一样的。
构造函数中的this
JavaScript中的函数是可以作为构造函数的,使用new即可,那么this在这种情况下指向是什么?
function Person() {
this.age = 18;
this.job = 'student';
}
var person = new Person();
console.log(person)//{age: 18, job: 'student'}
我们可以知道person是一个实例,所以函数作为构造函数时,this是指向实例的,在构造函数中实际是这样的:
function Person() {
//隐藏着的语句
//this = {} 这里只是简单说明this是一个对象,它还要关联Person函数的原型
this.age = 18;
this.job = 'student';
//隐藏着的语句
//return this;
}
var person = new Person();
console.log(person)//{age: 18, job: 'student'}
从上面我们可以看出,构造函数隐式return了this,所以person就是this,但是当构造函数有return语句时,this并不一定指向person。
当return返回一个对象时:
function Person() {
this.age = 18;
this.job = 'student';
return {
tip: 'this is an object'
}
}
var person = new Person();
console.log(person)//{tip: "this is an object"}
当return回一个非对象值时:
function Person() {
this.age = 18;
this.job = 'student';
return 1;
}
var person = new Person();
console.log(person)//{age: 18, job: 'student'}
call、apply、bind改变this指向
有时我们需要改变this的指向,就可以通过这三个方法函数来实现:
call和apply:
var prop = 'window';
function fn() {
console.log(this.prop);
}
var obj1 = {
prop: 'obj1',
fn: function(){
console.log(this.prop);
}
}
var obj2 = {
prop: 'obj2',
fn: function(){
console.log(this.prop);
}
}
fn(); // 'window'
obj1.fn(); // 'obj1'
obj2.fn(); // 'obj2'
fn.call(obj1) // 'obj1'
fn.apply(obj1) // 'obj1'
obj1.fn.call(obj2); // 'obj2'
obj1.fn.apply(obj2); // 'obj2'
从改变this指向来说,call和apply是一样的,它们两个函数的区别在于传参数形式的不同:
var obj = {
a: 1,
b: 2,
c: 3
}
function add(a, b, c) {
console.log(this);
return a + b + c;
}
add.call(obj, obj.a, obj. b, obj.c);
add.apply(obj, [obj.a, obj.b, obj.c]);
可以清楚的看出:call接受的参数是一个一个传进去的,而apply是传一个参数数组进去的。这里插播一个问题:为什么要有call、apply同时存在?其实,有时候改变参数的形式是很有必要,下面分享一个小技巧,当你要求一个数组的最大值时,你会怎么做?传统的做法是用for遍历一遍数组,然后挑出最大值,但是利用apply你就可以直接利用js的内置函数:
var arr = [3, 4, 2, 78];
var max = Math.max.apply(Math, arr); //Math.max的用法是Math.max(1, 3, 6, 2) //6
console.log(max); //78
bind:
bind函数的作用是绑定参数,其中第一个参数就是传入this指向对象,当时undefined或者null时,this指向window,它会返回一个新函数,看代码;
function add(a,b,c){
return a + b + c;
}
add(1,2,3) //6
var addOne = add.bind(null, 1);
addOne(2, 3) //6,相当于add(1, 2, 3);
bind和call的区别在于call是参入参数并执行函数,而bind是传入绑定参数,返回一个新函数。