理解JavaScript中的this

这里来个倒叙,先说一下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是传入绑定参数,返回一个新函数。

原文地址:https://www.cnblogs.com/kang-xjtu/p/5800932.html