[译] JavaScript 之 this 指南

原文连接:A guide to this in JavaScript
原文做者:Ashay Mandwarya
译者:JintNiu
推荐理由:this 一直是 JavaScript 中的重难点,借助这篇文章,从新认识并理解 this,加深印象。javascript

this 无疑是 JavaScript 中使用最普遍但又容易被误解的关键字,今天我将会对其进行详细的解释。html

当咱们在学校学习英语代词时:java

Phelps is swimming fast because he wants to win the race.数组

这句话中,咱们不直接使用 Phelps,而是使用代词“he”来指代他。相似地,JavaScript 中使用 this 关键字指向引用上下文中的对象。闭包

例:app

var car = {
    make: "Lamborghini",
    model: "Huracán",
    fullName: function () {
        console.log(this.make + " " + this.model); 
        console.log(car.make + " " + car.model); 
    }
}
car.fullName();

// Lamborghini Huracán
// Lamborghini Huracán
复制代码

在上面的代码中,咱们定义了一个具备属性 makemodelfullName 的对象 car,其中 fullName 是一个函数,函数体内使用 2 种不一样的方法输出 makemodelide

  • 使用 this 时,this.make+ " " +this.model 中的 this 指的是在上下文中的对象,也就是 car,则 this.makecar.makethis.modelcar.model
  • 使用点操做符时,咱们能够直接访问对象的属性 car.makecar.model

this

如今咱们已经了解了什么是 this 以及它 最基本的用法,为方便记忆,咱们将列出一些场景,并分别举例说明。函数

根据出现的位置,this 可分为如下几种状况:post

  1. 在方法内使用
  2. 在函数内使用
  3. 单独存在
  4. 在事件中使用
  5. call()apply()

1. 在方法内使用 this

this 在方法内使用时,指向其所属的对象。学习

在对象内定义的函数称为方法。再来看看汽车的例子:

var car = {
    make: "Lamborghini",
    model: "Huracán",
    fullName: function () {
        console.log(this.make + " " + this.model);
        console.log(car.make + " " + car.model);
    }
}
car.fullName();
复制代码

该例中,fullName() 即为方法,方法中的 this 指向对象 car

2. 在函数内使用 this

函数中的 this 就有些复杂了。与对象同样,函数也具备属性。函数每次执行时都会获取 this,它指向调用它的对象。

this 实际上只是“先行对象”的一种快捷引用,也就是对象调用。
 —  javascriptissexy.com

若是函数未被某对象调用,则函数内的 this 属于全局对象,该全局对象被称为 window。在这种状况下,this 将指向全局做用域中的变量。且看如下例子:

var make = "Mclaren";
var model = "720s"
function fullName() {
    console.log(this.make + " " + this.model);
}
var car = {
    make: "Lamborghini",
    model: "Huracán",
    fullName: function () {
        console.log(this.make + " " + this.model);
    }
}
car.fullName(); // Lmborghini Huracán
window.fullName(); // Mclaren 720S
fullName(); // Mclaren 720S
复制代码

该例中,在全局对象中定义了 make, modelfullName,对象 car 中也实现了 fullName 方法。当使用 car 调用该方法时,this 指向该对象内的变量;而使用另外两种调用方式时,this 指向全局变量。

3. 单独使用 this

当单独使用 this,不依附于任何函数或者对象时,指向全局对象。

这里的 this 指向全局变量 name

4. 在事件内使用 this

JS 中有不少种事件类型,但为了描述简单,这里咱们以点击事件为例。

每当单击按钮并触发一个事件时,能够调用另外一个函数来去执行某个任务。若是在函数内使用 this,则指向触发事件中的元素。DOM 中,全部元素都以对象的形式储存,也就是说网页元素实际上就是 DOM 中的一个对象,所以每触发一个事件时,this 就会指向该元素。

例:

<button onclick="this.style.display='none'">
  Remove Me!
</button>
复制代码

5. call(), apply() & bind()

  • bind:容许咱们在方法中设置 this 指向
  • call&apply:容许咱们借助其余函数并在函数调用中改变 this 的指向。

有关 call(), apply()bind() 的知识会在另外一篇文章阐述。

译者注:可参考文章: [译] 如何在 JavaScript 中使用 apply(💅),call(📞),bind(➰)

难点

理解掌握了 this 会使工做变轻松不少,但实际状况每每不是那么如意。请看如下例子。

例1:

var car = {
    make: "Lamborghini",
    model: "Huracán",
    name: null,
    fullName: function () {
        this.name = this.make + " " + this.model;
        console.log(this.name); 
    }
}
var anotherCar = {
    make: "Ferrari",
    model: "Italia",
    name: null
}
anotherCar.name = car.fullName();

// Lamborghini Huracán
复制代码

结果并非咱们所指望的。分析其缘由:当咱们使用 this 调用另外一个对象的方法时,只是为 anotherCar 分配了该方法,但实际调用者是 car。所以返回的是 Lamborghini 而不是 Ferrari。

咱们可使用 call() 解决这个问题。

该例中利用 call() 方法使 anotherCar 对象调用 fullName(),该对象中本来并无 fullName() 方法,但输出了 Ferrari Italia

另外,当咱们输出 car.nameanotherCar.name 的值时,前者输出 null,然后者输出了 Ferrari Italia,也就是说 fullName() 函数确实被 anotherCar 调用了,而不是被 car 调用。

例2:

var cars = [
    { make: "Mclaren", model: "720s" },
    { make: "Ferrari", model: "Italia" }
]
var car = {
    cars: [{ make: "Lamborghini", model: "Huracán" }],
    fullName: function () {
        console.log(this.cars[0].make + " " + this.cars[0].model);
    }
}
var vehicle = car.fullName;
vehicle() // Mclaren 720s
复制代码

该例中,咱们定义了一个全局变量 cars,而且在对象 car 中也定义了同名变量,接着将 fullName() 方法赋给变量 vehicle,而后调用它。该变量属于全局变量,因为上下文的关系,this 指向的是全局变量 cars 而不是局部变量。

咱们可使用 bind() 解决这个问题。

bind 改变了this 的指向,使变量 vehicle 指向局部变量 car。也就是说,this 的指向取决于 car 的上下文环境。

例3:

var car = {
   cars: [
        { make: "Lamborghini", model: "Huracán" },
        { make: "Mclaren", model: "720s" },
        { make: "Ferrari", model: "Italia" }
    ],
    brand:"lamborghini",
    fullName: function () {
        this.cars.forEach(function(car){
            console.log(car.model + " " + this.brand);
        })
    }
}
car.fullName();

// Huracán undefined
// 720s undefined
// Italia undefined
复制代码

在以上代码中,fullName() 使用 forEach 迭代数组 cars,每次迭代都产生一个没有上下文的匿名函数,这类定义在函数内部的函数,称之为闭包(closure)。闭包在 JavaScript 中很是重要,并且被普遍使用。

另外一个重要的概念是做用域(scope)。定义在函数内部的变量不能访问其做用域之外的变量和属性;匿名函数中的 this 不能访问外部做用域,以致于 this 只能指向全局对象。该例中,全局对象中没有定义 this 所要访问的属性 brand,所以输出 undefined

以上问题的解决方法是:咱们能够在匿名函数外为 this 赋值,而后在函数内使用。

this 赋给变量 self,并代替函数体内的 this,输出指望结果。

例4:

var car = {
    make: "Lamborghini",
    model: "Huracán",
    fullName: function (cars) {
        cars.forEach(function (vehicle) {
            console.log(vehicle + " " + this.model);
        })
    }
}
car.fullName(['lambo', 'ferrari', 'porsche']);

// lambo undefined
// ferrari undefined
// porsche undefined
复制代码

当没法使用 this 进行访问时,可使用变量 self 来保存它(如例 3 ),但在该例中,也可使用箭头函数来解决:

能够看出,在 forEach() 中使用箭头函数就能够解决该问题,而不是进行绑定或暂存 this。这是因为箭头函数绑定了上下文,this 实际上指向原始上下文或原始对象。

例5:

var car = {
    make: "Lamborghini",
    model: "Huracán",
    fullName: function () {
        console.log(this.make + " " + this.model);
    }
}
var truck = {
    make: "Tesla",
    model: "Truck",
    fullName: function (callback) {
        console.log(this.make + " " + this.model);
        callback();
    }
}
truck.fullName(car.fullName);

// Tesla Truck
// undefined undefined
复制代码

上述代码中定义了两个相同的对象,但其中一个包含回调函数,回调函数将做为参数传入另外一个函数,而后经过外部函数调用来完成某种操做。

该代码中对象 truckfullName 方法包含一个回调函数,并在方法中直接进行调用。当将 car.fullName 做为参数调用 truck.fullName() 时,输出 Tesla Truckundefined undefined

结果出乎意料。实际上,car.fullName 只是做为参数传入,而不是由 truck 对象调用。换句话说,回调函数调用了对象 car 的方法,但却把 this 绑定到全局做用域上,以下图:

为便于观察,咱们输出了 this。能够看到回调函数中的 this 指向了全局做用域。继续建立全局变量 makemodel 以下例:

显而易见,回调函数中的输出了全局变量 makemodel,再次证实了 this 指向全局对象。

为获得指望结果,咱们将使用 bind()car 强制绑定到回调函数中。以下:

完成!

毫无疑问,this 是很是有用的,但不容易理解。但愿经过这篇文章你能够逐渐了解它的使用方法。

若是这篇文章对您有所帮助,点个赞👏,加个关注👣 吧~

相关文章
相关标签/搜索