Javascript中的this

初学JS,对this有些困惑,在此做一下总结,欢迎指正交流。

就和我们平常说话一样,“张三跑得飞快,因为快迟到了”,注意这个他。我们也可以说“张三跑得飞快,因为张三快迟到了”,但是一般不这么说。同样,在JS里,我们用this来指代对象。

var person = {
    firstName: "Penelope",
    lastName: "Barrymore",
    fullName: function() {​
        // 我们使用this,类比上面例句中的他
        console.log(this.firstName + " " + this.lastName);​
        // 也可以这样写
        console.log(person.firstName + " " + person.lastName);
    }
};

如果我们用person.firstName和person.lastName,语句就不够明确,在其他我们没注意到的地方可能会存在全局变量也叫"person",所以我们使用this不仅起到简化指代的作用,更可以让语句变得明确,就像我们用"他"来特指谈到的那个张三而不是别的张三。另外,在大多数时候我们不会事先知道调用该函数的对象叫什么名字,而可以通过this来获取该对象的属性和方法。

再来看一个简单例子:

var person = {
    firstName: "Penelope",
    lastName: "Barrymore",
    // "this"被用在下面的showFullName方法里,这个方法定义在person对象里 
    showFullName: function() {
        console.log(this.firstName + " " + this.lastName);
    }​
};​
// "this"指向person对象,因为person对象调用了showFullName()方法
person.showFullName(); // Penelope Barrymore

一个简单的jQuery例子:

$("button").click(function(event) {
    // $(this)指向$("button")对象​ // 因为button对象调用了click()方法 
    console.log($(this).prop("name"));
});

在这里,之所以$(this)指向$("button")是因为jQuery把$(this)绑定到触发click方法的对象上,虽然实际上定义这个$(this)的匿名函数是无法获取其外部函数的"this"的值的,我们会在后文加以解释。

全局作用域中的this

当代码在浏览器中执行时,所有的全局变量和函数都定义在window对象下,它是整个javascript应用或页面的容器。因此当我们在一个全局函数中使用this时,它指向window对象。

var firstName = "Peter",
    lastName = "Ally";​
function showFullName() {
    // 此函数中的"this"会指向window对象
    // 因为showFullName()定义在全局作用域下,和firstName、lastName一样​ 
    console.log(this.firstName + " " + this.lastName);
}​;
var person = {
    firstName: "Penelope",
    lastName: "Barrymore",
    showFullName: function() {
        // 这里的"this"指向person对象,因为showFullName方法被person对象调用​ 
        console.log(this.firstName + " " + this.lastName);
    }
}​;

showFullName(); // Peter Ally​ 
// 所有的全局变量和函数都定义在window对象下,因此
window.showFullName(); // Peter Ally​ 
// 这里"this"指向person对象,因此
person.showFullName(); // Penelope Barrymore

那些"不守规矩"的this

我们知道JS里的函数也是对象,所以函数可以有属性。在一个函数执行时,它会得到this属性(PS:随着深入学习,这句话不对,this并不是函数对象的属性,而是执行上下文的一个属性,this的值thisValue在进入上下文的时候确定,并在上下文运行期间永久不变,想要深入理解的同学可以去看汤姆大叔的深入理解javascript系列),其值为调用该函数的对象,我们姑且称这个函数为"this函数"。this总是指向一个对象,并且常常被用在函数或方法里,当然它也可以在函数外部全局作用域里使用。注意,直到this函数被调用时,this才被赋值。在大部分情况下,this指向调用this函数的对象,然而在某些情况下会有例外。

1.作为被传递的回调函数里的this

var user = {
    data: [{
        name: "T. Woods",
        age: 37
    }, {
        name: "P. Mickelson",
        age: 43
    }],
    clickHandler: function(event) {
        var randomNum = Math.floor(Math.random() * 2); // 0或1的随机数 
// 从data数组中读取name和age
        console.log(this.data[randomNum].name + " " + this.data[randomNum].age);
    }
}​; 
// 结果为undefined,因为button对象中不存在data属性
$("button").click(user.clickHandler); // Cannot read property '0' of undefined

 在这里,$("button")是一个对象,我们把user.clickHandler方法作为回调函数传递给它的click()方法,此时user.clickHandler方法里的this不再指向user对象,而是指向调用user.clickHandler方法的对象,即button对象。注意虽然我们使用user.clickHandler来调用clickHandler()方法(毕竟clickHandler是定义在user下的方法),clickHandler()方法本身是被button对象调用的,所以this指向button对象。

解决方法

现在我们很想让this.data能够表示user对象的data属性,我们可以使用Bind(),Apply()和Call()方法来专门改变this的值。

在这个例子里,我们使用bind方法,将这一行

$("button").click(user.clickHandler);

改为以下这行,把clickHandler方法绑定到user对象

$("button").click(user.clickHandler.bind(user)); // P. Mickelson 43

2.闭包中的this

 另一个常见的情况就是内部函数(闭包)中的this。我们知道闭包不能通过this关键字来访问它的外部函数的this变量,这个this变量只能由外部函数自身来访问。

var user = {
    tournament: "The Masters",
    data: [{
        name: "T. Woods",
        age: 37
    }, {
        name: "P. Mickelson",
        age: 43
    }],  ​
    clickHandler: function() {
        // 在这里使用this.data没有问题,因为"this"指向user对象,data是user对象的属性       ​
        this.data.forEach(function(person) {
            // 但在这个内部匿名函数里,"this"不再指向user对象 
            // 这个内部函数无法访问外部函数的"this" 
            console.log("What is This referring to? " + this); //[object Window]​ 

            console.log(person.name + " is playing at " + this.tournament);
            // T. Woods is playing at undefined​ 
            // P. Mickelson is playing at undefined​ 
        })
    }​
};​
user.clickHandler(); // What is "this" referring to? [object Window]

这个匿名函数里的this无法访问外部函数的this,所以它被绑定到全局的window对象上。

解决方法

一个惯例方法是在进入forEach方法之前,我们把this的值赋给另一个变量。

var user = {
    tournament: "The Masters",
    data: [{
        name: "T. Woods",
        age: 37
    }, {
        name: "P. Mickelson",
        age: 43
    }],
    clickHandler: function(event) {
        // 趁着在这里"this"还是指向user对象的,我们此时把"this"的值赋给that变量
        var that = this;
        this.data.forEach(function(person) {
            // 把this.tournament改为that.tournament​,由于作用域链,内部函数可以访问外部函数的that变量
            console.log(person.name + " is playing at " + that.tournament);
        })
    }​
};​
user.clickHandler();
// T. Woods is playing at The Masters​ 
//  P. Mickelson is playing at The Masters

这个that变量可以是任意名字,一般我们取名为that。

3.当方法被赋值给一个变量时的this

// 全局变量data 
var data = [{
    name: "Samantha",
    age: 12
}, {
    name: "Alexis",
    age: 14
}];​
var user = {
    // user的属性data 
    data: [{
        name: "T. Woods",
        age: 37
    }, {
        name: "P. Mickelson",
        age: 43
    }],
    showData: function(event) {
        var randomNum = Math.floor(Math.random() * 2);
        ​
        console.log(this.data[randomNum].name + " " + this.data[randomNum].age);
    }​
};​
// 把user.showData赋值给一个变量
var showUserData = user.showData;​

//​ 调用showUserData时,取得的是全局data,而不是user对象的data
showUserData(); // Samantha 12 

解决方法

可以用bind()方法来设置this的值

// 把showData方法绑定到user对象 
var showUserData = user.showData.bind(user);​
// 现在我们取得的是来自user对象的data
showUserData(); // P. Mickelson 43

4.借用函数时的this

// 现在有两个对象,一个有avg()方法,另一个没有​ 
var gameController = {
    scores: [20, 34, 55, 46, 77],
    avgScore: null,
    players: [{
        name: "Tommy",
        playerID: 987,
        age: 23
    }, {
        name: "Pau",
        playerID: 87,
        age: 33
    }]
};​
var appController = {
    scores: [900, 845, 809, 950],
    avgScore: null,
    avg: function() {​
        var sumOfScores = this.scores.reduce(function(prev, cur, index, array) {
            return prev + cur;
        });​
        this.avgScore = sumOfScores / this.scores.length;
    }
};​
// 我们想让gameController借用avg()方法,求得自己scores的平均值
// 同时不改变appController的avgScore

// 下面这行肯定是行不通的
gameController.avgScore = appController.avg();

解决方法

我们可以使用apply()方法

// 注意我们在使用apply()方法,所以第二个参数必须是数组,它包含要传递给appController.avg()的参数​ 
appController.avg.apply(gameController, gameController.scores);​

// 成功得到gameController对象的avgScore的值,虽然借用的是appController对象的avg()方法​ 
console.log(gameController.avgScore); // 46.4​ 
// appController.avgScore仍然为null​ 
console.log(appController.avgScore); // null

译自http://javascriptissexy.com/understand-javascripts-this-with-clarity-and-master-it/

原文地址:https://www.cnblogs.com/coiorz/p/4592563.html