网站首页 > 技术文章 正文
理解 JavaScript 中的关键字 this,以及它指向什么,有时候可能是有点复杂。
幸好有五个通用规则可以用来确定 this 绑定到什么。虽然这些规则在某些情况下并不适用,不过绝大多数情况应该能帮你搞定。
所以,下面我们开始吧!
- this 的值通常是由一个函数的执行上下文决定的。所谓执行上下文就是指一个函数如何被调用。
- 必须要知道的是,每次函数被调用时,this 都有可能是不同的(即指向不同的东西)。
- 如果依然搞不清楚第一条和第二条的含义,没有关系,本文结尾的时候会搞清楚的。
#1 全局对象
好吧,我们现在把理论定义放一边,下面先从实践来寻找支撑。打开你的 Chrome 开发者控制台(Windows: Ctrl + Shift + J)(Mac: Cmd + Option + J),并键入如下代码:
console.log(this);
会得到什么呢?
// Window {...}
window 对象!这是因为在全局作用域中,this 指向全局对象。而在浏览器中,全局对象就是 window 对象。
为帮助更好地理解为什么 this 指向 window 对象,下面我们更深入来了解一下。在控制台中,创建一个新变量 myName,把它赋值为你的姓名:
var myName = 'Brandon';
我们可以通过调用 myName,再次访问这个新变量:
myName// returns -> 'Brandon'
不过,你知道在全局作用域中声明的每个变量都是绑定到 window 对象吗?好吧,我们来试一试:
window.myName// returns -> 'Brandon'window.myName === myName// returns -> true
酷。所以,之前在全局上下文中执行 console.log(this) 时,我们知道 this 是在全局对象上被调用。因为浏览器中的全局对象是 window 对象,所以这样的结果是讲得通的:
console.log(this)
// returns -> window{...}
现在我们把 this 放在一个函数内。回忆我们之前的定义:this 的值通常是由函数如何被调用来决定的。记住这一点后,你期望这个函数返回什么?在浏览器控制台中,复制如下代码,按回车。
function test() {
return this;
}
test()
关键字 this 再次返回全局对象(window)。这是因为 this 不在一个已声明的对象内,所以它默认等于全局对象(window)。这个概念可能现在理解起来有点难,不过随着进一步阅读,应该会越来越清晰。要指出的一件事是,如果你使用严格模式,那么上例中的 this 将会是 undefined。
#2 已声明的对象
当关键字 this 被用在一个已声明的对象内时,this 的值被设置为被调用的方法所在的最近的父对象。看看如下代码,这里我声明了一个对象 person,并在方法 full 内使用 this:
var person = {
first: 'John',
last: 'Smith',
full: function() {
console.log(this.first + ' ' + this.last);
}
};
person.full();// logs => 'John Smith'
为更好阐述 this 实际上就是引用 person 对象,请将如下代码复制到浏览器控制台中。它与上面的代码大致相同,只是用 console.log(this) 替换了一下,这样我们就能看看会返回什么。
var person = {
first: 'John',
last: 'Smith',
full: function() {
console.log(this);
}
};
person.full();// logs => Object {first: "John", last: "Smith", full: function}
如你所见,控制台会返回 person 对象,证明 this 采用的是 person 的值。
在继续之前,还有最后一件事。还记得我们说过 this 的值被设置为被调用的方法所在的最近的父对象吧?如果有嵌套的对象,你会期望发生什么?看看下面的示例代码。我们已经有了一个 person 对象,该对象像以前一样有同样的 first、last 和 full 键。不过这一次,我们还嵌套进了一个 personTwo 对象。personTwo 包含了同样的三个键。
var person = { first: 'John', last: 'Smith', full: function() { console.log(this.first + ' ' + this.last);
}, personTwo: { first: 'Allison', last: 'Jones', full: function() { console.log(this.first + ' ' + this.last);
}
}
};
当调用这两个 full 方法时,会发生什么呢?下面我们来探个究竟。
person.full();// logs => 'John Smith'person.personTwo.full();// logs => 'Allison Jones'
this 的值再次被设置为方法调用所在的最近的父对象。当调用 person.full() 时,函数内的 this 是被绑定到 person 对象。与此同时,当调用 person.personTwo.full() 时,在 full 函数内,this 是被绑定到 personTwo 对象!
#3 New 关键字
当使用 new 关键字(构造器)时,this 被绑定到正在新创建的对象。
我们来看一个示例:
function Car(make, model) { this.make = make; this.model = model;
};
上面,你可能会猜 this 被绑定到全局对象 - 如果不用关键字 new 的话,你就是正确的。当我们使用 new 时,this 的值被设置为一个空对象,在本例中是 myCar。
var myCar = new Car('Ford', 'Escape');console.log(myCar);// logs => Car {make: "Ford", model: "Escape"}
要把上面的代码搞清楚,需要理解 new 关键字到底做了什么。不过这本身是一个全新的话题。所以现在,如果你不确定的话,只要看到关键字 new,就当 this 是正指向一个全新的空对象好了。
#4 Call、Bind 和 Apply
最后一个要点,我们实际上是可以用 call()、bind() 和 apply() 显式设置 this 的值的。这三个方法很相似,不过理解它们之间的细微差别很重要。
call() 和 apply() 都是立即被调用的。call() 可以带任意数量的参数:this,后跟其它的参数。apply() 只带两个参数:this,后跟一个其它参数的数组。
还跟得上我的思路吧?用一个例子应该会解释得更清楚一些。看看下面的代码。我们正试着对数字求和。复制如下代码到浏览器控制台,然后调用该函数。
function add(c, d) { console.log(this.a + this.b + c + d);
}
add(3,4);// logs => NaN
add 函数输出的是 NaN(不是一个数字)。这是因为 this.a 和 this.b 都是 undefined,二者都是不存在的。而我们不能把数字与未定义的东西相加。
下面我们给等式引入一个对象。我们可以用 call() 和 apply() 在我们的对象上调用 add 函数:
function add(c, d) { console.log(this.a + this.b + c + d);
}
var ten = {a: 1, b: 2};add.call(ten, 3, 4);// logs => 10add.apply(ten, [3,4]);// logs => 10
当用 add.call() 时,第一个参数就是 this 要绑定的对象。后续的参数被传递进我们调用的函数。因此,在 add() 中,this.a 指向 ten.a,this.b 指向 ten.b,我们得到返回的 1+2+3+4,或者 10。
add.apply() 差不多。第一个参数就是 this 应该绑定的对象。后续的参数是要用在函数中的参数数组。
那么 bind() 又是怎么样的呢?bind() 中的参数与 call() 中的是一样的,不过 bind() 不是马上被调用,而是返回一个 this 上下文已经绑定好的函数。因此,如果预先不知道所有参数的话,bind() 就很有用。下面再用一个示例来帮助理解:
var small = { a: 1, go: function(b,c,d){ console.log(this.a+b+c+d);
}
}var large = { a: 100}
将上述代码复制到控制台。然后调用:
small.go(2,3,4);// logs 1+2+3+4 => 10
很棒。这里并没有啥新东西。但是,如果我们想用 large.a 的值来替换,该怎么办呢?我们可以用 call() 或者 apply():
small.go.call(large,2,3,4);// logs 100+2+3+4 => 109
现在,如果还不知道所有 3 个参数,该怎么办呢?我们可以用 bind():
var bindTest = small.go.bind(large,2);
如果在控制台输出上面的变量 bindTest,就可以看到结果:
console.log(bindTest);
// logs => function (b,c,d){console.log(this.a+b+c+d);}
记住,用 bind() 的话,会返回一个已经绑定了 this 的函数。所以 this 已经成功绑定到 large 对象。我们还传递进了第二个参数 2。之后,当知道其余参数时,我们可以将它们传递进来:
bindTest(3,4);// logs 100+2+3+4 => 109
为清晰起见,我们将所有代码放在一个块中。仔细看一遍,然后将其复制到控制台中,真正了解一下发生了什么!
var small = {
a: 1,
go: function(b,c,d){
console.log(this.a+b+c+d);
}
}var large = {
a: 100}small.go(2,3,4);// logs 1+2+3+4 => 10var bindTest = small.go.bind(large,2);
console.log(bindTest);// logs => function (b,c,d){console.log(this.a+b+c+d);}bindTest(3,4);// logs 100+2+3+4 => 109
#5 箭头函数
箭头函数本身就是一个很大的主题,为此我写了一整篇文章来讲解它:为初学者介绍箭头函数。
总结
你成功了!现在,在大多数情况下,你应该能推断出 this 指向什么!请记住如下几件事:
- this 的值通常是由函数的执行上下文决定的
- 在全局作用域中,this 指向全局对象(window 对象)
- 当使用 new 关键字(一个构造器)时,this 被绑定到正创建的新对象
- 可以显式用 call()、bind() 和 apply() 设置 this 的值
- 箭头函数不会绑定 this - this是词法绑定的(即,基于原始上下文)
如果大家有问题或者想了解更多的技术干货可以私信发送【微信】加朗妹儿微信哟~
猜你喜欢
- 2024-10-16 JS中(a==1 && a==2 && a==3)可以在JavaScript中计算为“true”吗?
- 2024-10-16 JavaScript 的这个难点,毁掉了多少程序员
- 2024-10-16 前端开发之彻底搞懂this指向(前端this指向问题)
- 2024-10-16 JavaScript中的变量声明和作用域(一)
- 2024-10-16 Top 26 JavaScript面试问题和答案
- 2024-10-16 JS 经典实例知识点整理汇总【实践】
- 2024-10-16 苦恼于JavaScript中的reduce函数?五分钟讲透彻
- 2024-10-16 前端基础:JavaScript(前端基础题)
- 2024-10-16 一句话彻底理解JS中的回调(Callback)函数
- 2024-10-16 面试中被问到最多的 19 个 JavaScript 问题
- 最近发表
- 标签列表
-
- cmd/c (57)
- c++中::是什么意思 (57)
- sqlset (59)
- ps可以打开pdf格式吗 (58)
- phprequire_once (61)
- localstorage.removeitem (74)
- routermode (59)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- log.warn (60)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- resttemplateokhttp (59)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- reader.onload (61)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- java是值传递还是引用传递 (58)
- 无效的列索引 (74)