当我开始学习JavaScript
时,花了一些时间来理解JavaScript
中的this
关键字而且可以快速识别this
关键字所指向的对象。我发现理解this
关键字最困难的事情是,您一般会忘记在您已阅读或观看过一些JavaScript
课程或资源中解释的不一样案例状况。在ES6
中引入箭头函数后,事情变得更加混乱,由于箭头函数this
以不一样的方式处理关键字。我想写这篇文章来陈述我学到的东西,并尝试以一种能够帮助任何正在学习JavaScript
而且难以理解this
关键字的人的方式来解释它。数组
您可能知道,执行任何JavaScript
行的环境(或scope
)称为“执行上下文”。Javascript
运行时维护这些执行上下文的堆栈,而且当前正在执行存在于该堆栈顶部的执行上下文。this
变量引用的对象每次更改执行上下文时都会更改。浏览器
默认状况下,执行上下文是全局的,这意味着若是代码做为简单函数调用的一部分执行,则该this
变量将引用全局对象。在浏览器的状况下,全局对象是window
对象。例如,在Node.js
环境中,this值是一个特殊对象global
。bash
例如,尝试如下简单的函数调用:闭包
function foo () {
console.log("Simple function call");
console.log(this === window);
}
foo();
复制代码
调用foo()
,获得输出:app
“Simple function call”
true
复制代码
证实这里的this
指向全局对象,此例中为window
。函数
注意,若是实在严格模式下,this
的值将是undefined
,由于在严格模式下全局对象指向undefined
而不是window
。post
试一下以下示例:学习
function foo () {
'use strict';
console.log("Simple function call");
console.log(this === window);
}
foo();
复制代码
输出:ui
“Simple function call”
false
复制代码
咱们再来试下有构造函数的:this
function Person(first_name, last_name) {
this.first_name = first_name;
this.last_name = last_name;
this.displayName = function() {
console.log(`Name: ${this.first_name} ${this.last_name}`);
};
}
复制代码
建立Person
实例:
let john = new Person('John', 'Reid');
john.displayName();
复制代码
获得结果:
"Name: John Reid"
复制代码
这里发生了什么?当咱们调用 new Person
,JavaScript
会在Person
函数内建立一个新对象并把它保存为this
。接着,first_name
, last_name
和 displayName
属性会被添加到新建立的this
对象上。以下:
你会注意到在Person
的执行上下文中建立了this
对象,这个对象有first_name
, last_name
和 displayName
属性。但愿您能根据上图理解this
对象是如何建立并添加属性的。
咱们已经探讨了两种相关this
绑定的普通案例我不得不提出下面这个更加困惑的例子,以下函数:
function simpleFunction () {
console.log("Simple function call")
console.log(this === window);
}
复制代码
咱们已经知道若是像下面这样做为简单函数调用,this
关键字将指向全局对象,此例中为window
对象。
simpleFunction()
复制代码
所以,获得输出:
“Simple function call”
true
复制代码
建立一个简单的user
对象:
let user = {
count: 10,
simpleFunction: simpleFunction,
anotherFunction: function() {
console.log(this === window);
}
}
复制代码
如今,咱们有一个simpleFunction
属性指向simpleFunction
函数,一样添加另外一个属性调用anotherFunction
函数方法。
若是调用user.simpleFunction()
,获得输出:
“Simple function call”
false
复制代码
为何会这样呢?由于simpleFunction()
如今是user
对象的一个属性,因此this
指向这个user
对象而不是全局对象。
当咱们调用user.anotherFunction
,也是同样的结果。this
关键字指向user
对象。因此,console.log(this === window);
应该返回false
:
false
复制代码
再来,如下操做会返回什么呢?
let myFunction = user.anotherFunction;
myFunction();
复制代码
如今,获得结果:
true
复制代码
因此这又发生了什么?在这个例子中,咱们发起普通函数调用。正如以前所知,若是一个方法以普通函数方式执行,那么this
关键字将指向全局对象(在这个例子中是window
对象)。因此console.log(this === window);
输出true
。
再看一个例子:
var john = {
name: 'john',
yearOfBirth: 1990,
calculateAge: function() {
console.log(this);
console.log(2016 - this.yearOfBirth);
function innerFunction() {
console.log(this);
}
innerFunction();
}
}
复制代码
调用john.calculateAge()
会发生什么呢?
{name: "john", yearOfBirth: 1990, calculateAge: ƒ}
26
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
复制代码
calculateAge
函数内部, this
指向 john
对象,可是,在innerFunction
函数内部,this
指向全局对象(本例中为window
),有些人认为这是JS
的bug,可是规则告诉咱们不管什么时候一个普通函数被调用时,那么this
将指向全局对象。
...
我所学的JavaScript
函数也是一种特殊的对象,每一个函数都有call
, apply
, bind
方法。这些方法被用来设置函数的执行上下文的this
值。
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.displayName = function() {
console.log(`Name: ${this.firstName} ${this.lastName}`);
}
}
复制代码
建立两个实例:
let person = new Person("John", "Reed");
let person2 = new Person("Paul", "Adams");
复制代码
调用:
person.displayName();
person2.displayName();
复制代码
结果:
Name: John Reed
Name: Paul Adams
复制代码
call:
person.displayName.call(person2);
复制代码
上面所作的事情就是设置this
的值为person2
对象。所以,
Name: Paul Adams
复制代码
apply:
person.displayName.apply([person2]);
复制代码
获得:
Name: Paul Adams
复制代码
call
,apply
惟一的区别就是参数的传递形式,apply
应该传递一个数组,call
则应该单独传递参数。
咱们用bind
来作一样的事情,bind
返回一个新的方法,这个方法中的this
指向传递的第一个参数。
let person2Display = person.displayName.bind(person2);
复制代码
调用person2Display
,获得Name: Paul Adams
结果。
...
ES6中,有一个新方法定义函数。以下:
let displayName = (firstName, lastName) => {
console.log(Name: ${firstName} ${lastName});
};
复制代码
不像一般的函数,箭头函数没有他们自身的this
关键字。他们只是简单的使用写在函数里的this
关键字。他们有一个this
词法变量。
ES5:
var box = {
color: 'green', // 1
position: 1, // 2
clickMe: function() { // 3
document.querySelector('body').addEventListener('click', function() {
var str = 'This is box number ' + this.position + ' and it is ' + this.color; // 4
alert(str);
});
}
}
复制代码
若是调用:
box.clickMe()
复制代码
弹出框内容将是This is box number undefined and it is undefined'
.
咱们一步一步来分析是怎么回事。在//1
和//2
行,this
关键字能访问到color
和position
属性由于它指向box
对象。在clickMe
方法内部,this
关键字能访问到color
和position
属性由于它也指向box
对象。可是,clickMe
方法为querySelector
方法定义了一个回调函数,而后这个回调函数以普通函数的形式调用,因此this
指向全局对象而非box
对象。固然,全局对象没有定义color
和position
属性,因此这就是为何咱们获得了undefined
值。
咱们能够用ES5的方法来修复这个问题:
var box = {
color: 'green',
position: 1,
clickMe: function() {
var self = this;
document.querySelector('body').addEventListener('click', function() {
var str = 'This is box number ' + self.position + ' and it is ' + self.color;
alert(str);
});
}
}
复制代码
添加 var self = this
,建立了一个可使用指向box
对象的this
关键字的闭包函数的工做区。咱们仅仅只须要在回调函数内使用self
变量。
调用:
box.clickMe();
复制代码
弹出框内容This is box number 1 and it is green
。
怎么使用箭头函数可以达到上述效果呢?咱们将用箭头函数替换点击函数的回调函数。
var box = {
color: 'green',
position: 1,
clickMe: function() {
document.querySelector('body').addEventListener('click', () => {
var str = 'This is box number ' + this.position + ' and it is ' + this.color;
alert(str);
});
}
}
复制代码
箭头函数的神奇之处就是共享包裹它的this
词法关键字。因此,本例中外层函数的this
共享给箭头函数,这个外层函数的this
关键字指向box
对象,所以,color
和position
属性将是有正确的green
和1
值。
再来一个:
var box = {
color: 'green',
position: 1,
clickMe: () => {
document.querySelector('body').addEventListener('click', () => {
var str = 'This is box number ' + this.position + ' and it is ' + this.color;
alert(str);
});
}
}
复制代码
oh!如今又弹出了‘This is box number undefined and it is undefined’.
。为何?
click
事件监听函数闭包的this
关键字共享了包裹它的this
关键字。在本例中它被包裹的箭头函数clickMe
,clickMe
箭头函数的this
关键字指向全局对象,本例中是window
对象。因此this.color
和this.position
将会是undefined
由于window
对象没有position
和color
属性。
我想再给你看个在不少状况下都会有帮助的map
函数,咱们定义一个Person
构造函数方法以下:
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.displayName = function() {
console.log(`Name: ${this.firstName} ${this.lastName}`);
}
}
复制代码
Person
的原型上添加myFriends
方法:
Person.prototype.myFriends = function(friends) {
var arr = friends.map(function(friend) {
return this.firstName + ' is friends with ' + friend;
});
console.log(arr);
}
复制代码
建立一个实例:
let john = new Person("John", "Watson");
复制代码
调用john.myFriends(["Emma", "Tom"])
,结果:
["undefined is friends with Emma", "undefined is friends with Tom"]
复制代码
本例与以前的例子很是类似。myFriends
函数体内有this
关键字指向回调对象。可是,map
闭包函数内是一个普通函数调用。因此map
闭包函数内this
指向全局对象,本例中为window
对象,所以this.firstName
undefined。如今,咱们试着修复这个状况。
myFriends
函数体内指定this
为其它变量如self
,以便map
函数内闭包使用它。Person.prototype.myFriends = function(friends) {
// 'this' keyword maps to the calling object
var self = this;
var arr = friends.map(function(friend) {
// 'this' keyword maps to the global object
// here, 'this.firstName' is undefined.
return self.firstName + ' is friends with ' + friend;
});
console.log(arr);
}
复制代码
map
闭包函数使用bind
。Person.prototype.myFriends = function(friends) {
// 'this' keyword maps to the calling object
var arr = friends.map(function(friend) {
// 'this' keyword maps to the global object
// here, 'this.firstName' is undefined.
return this.firstName + ' is friends with ' + friend;
}.bind(this));
console.log(arr);
}
复制代码
调用bind
会返回一个map
回调函数的副本,this
关键字映射到外层的this
关键字,也就是是调用myFriends
方法,this
指向这个对象。
map
回调函数为箭头函数。Person.prototype.myFriends = function(friends) {
var arr = friends.map(friend => `${this.firstName} is friends with ${friend}`);
console.log(arr);
}
复制代码
如今,箭头函数内的this
关键字将共享不曾包裹它的词法做用域,也就是说实例myFriends
。
全部以上解决方案都将输出结果:
["John is friends with Emma", "John is friends with Tom"]
复制代码
...
在这一点上,我但愿我已经设法使this
关键字概念对您来讲有点平易近人。在本文中,我分享了我遇到的一些常见状况以及如何处理它们,但固然,在构建更多项目时,您将面临更多状况。我但愿个人解释能够帮助您在接近this
关键字绑定主题时保持坚实的基础。若是您有任何问题,建议或改进,我老是乐于学习更多知识并与全部知名开发人员交流知识。请随时写评论,或给我留言!