这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战
为什么要有this?
先看一段代码
var bar = {
myName:"time.geekbang.com",
printName: function () {
console.log(myName)
}
}
function foo() {
let myName = "极客时间"
return bar.printName
}
let myName = "极客邦"
let _printName = foo()
_printName()
bar.printName()
按照常理来说,大多数语言在执行bar.printName()的时候会输出bar对象中的myName而不是全局的myName.而JavaScript却输出的是全局中的变量,这是因为JavaScript语言的作用域链是由词法作用域决定的,而词法作用域是由代码结构决定的。
对象内部的方法要使用对象内部的属性是一个普遍的需求,但是JavaScript的作用域机制并不支持这一点,基于这个原因,JavaScript又有一套this机制来处理。
如何让对象中的函数能读取到对象中的属性呢,改造一下上述代码
var bar = {
myName:"time.geekbang.com",
printName: function () {
console.log(this.myName)
}
}
let myName = '全局作用域'
bar.printName()
这样就可以获取到了。
首先你需要明白一件事,就是作用域链和this是两套不同的系统,两者之间没有太多的联系
this是什么?
之前有说执行上下文中包括变量环境,词法环境和outer,其实还包括了this。
this和执行上下文是绑定的,每个执行上下文中都有一个this。
执行上下文包括三种:
- 全局执行上下文
- 函数执行上下文
- eval执行上下文
因此对应的this就有三种
- 全局执行上下文中的this
- 函数执行上下文中的this
- eval中的this
eval用的不多,主要用到的是全局执行上下文中的this和函数执行上下文中的this
全局执行上下文中的this
全局执行上下文中的this是什么,可以在控制台中打印一下
console.log(this)
实际输出的就是window对象,结论就是全局执行上下文中的this是指向window对象的,这也是this和作用域链唯一焦点,作用域链最底端包含了window对象,全局执行上下文中的this也是指向window对象。
函数执行上下文中的this
function foo(){
console.log(this)
}
foo()
在foo函数内部打印出this值,发现函数执行上下文和全局执行上下文中的this都是指向window对象的。那可以设置函数执行上下文的this指向其他对象呢?是可以的,有三种方法可以用来设置函数执行上下文的this指向。
通过函数的call方法设置函数的this指向
可以通过call方法来设置函数的this指向,并立刻执行该函数,比如以下代码
function foo(){
this.name = "foo"
}
fooObj = {
name:"fooObj"
}
foo.call(fooObj)
console.log(fooObj) //{name: "foo"}
一般来说这个this是指向window的,可是call函数改变了this的指向将函数foo的this指向到了fooObj,从而使fooObj的name属性值从fooObj变为了foo。
通过对象调用方法设置this指向当前对象
var Obj = {
name:"Obj",
getThis:function(){
console.log(this)
}
}
Obj.getThis() //{name: "Obj", getDetail: ƒ}
可以看到这样输出的this是指向当前对象的,可以得出一个结论,使用对象来调用其内部方法,这个方法的this是指向对象本身的。
也可以认为是JavaScript引擎在执行myObject.showThis的时候将其转换成了
obj.getThis.call(obj)
那如果将对象中的函数赋给一个全局变量会怎么样。
var Obj = {
name:"Obj",
getThis:function(){
console.log(this)
}
}
var foo = Obj.getThis
foo()
此时获取到的又是window对象了。
有两个结论
- 全局环境中调用一个函数,函数内部的this指向全局变量window
- 通过一个对象来调用其内部的方法,该方法的执行上下文中的this指向对象本身。
也就是说如果是在全局执行一个函数,那这个函数this就指向全局,但如果是对象里面,那这个this就指向对象,只和这个函数执行的调用者有关。
通过构造函数中设置
比如说new一个对象
function Person(){
this.name='P'
}
var p1 = new Person()
console.log(p1) //Person {name: "P"}
你知道JavaScript引擎在new一个对象的过程中做了什么吗?
- 首先先创建一个空对象p1
- 调用Person.call(p1),这样当Person的执行上下文创建的时候this就指向空对象p1
- 执行Person方法,因为此时this指向p1,因此p1的name='p'
- 返回p1这个对象
this的设计缺陷
1.嵌套函数的this不会继承外层函数的this
var Obj = {
name:"Obj",
getThis:function(){
console.log(this)
function bar(){
console.log(this)
}
bar()
}
}
Obj.getThis()//{name: "Obj", getThis: ƒ}
//Window ...
加工了一下上面的代码,在getThis中添加了一个方法bar,这个bar的this指向是什么?你可能会觉得这个this指向的是Obj,可是实际上函数bar的this指向的是全局的window对象,而getThis的this指向的是Obj。这是JavaScript中很让人迷惑的事情,也是很多问题的源头。
如何解决这样的问题呢?怎么让bar中可以有变量指向Obj?
一种方法可以将getThis中的this存储到一个self变量然后放到bar中
var Obj = {
name:"Obj",
getThis:function(){
console.log(this)
self = this;
function bar(){
self.name='bar'
console.log(self)
}
bar()
}
}
Obj.getThis()//{name: "Obj", getThis: ƒ}
//{name: "bar", getThis: ƒ}
此时的bar的self指向了Obj,修改self中的name,让Obj也改变了,这个方法的本质是把this体系转换成了作用域体系。因为执行到bar的时候,bar会沿着作用域链找self然后找到getThis上的self,又因为getThis中将this赋值给了self,所以就相当于bar取到了getThis的this,这个this指向就是Obj。
另一种方法是用ES6中的箭头函数来解决这个问题
var myObj = {
name : "Obj",
showThis: function(){
console.log(this)
var bar = ()=>{
this.name = "bar"
console.log(this)
}
bar()
}
}
myObj.showThis()
console.log(myObj.name)
console.log(window.name)
这样同样达到了我们想要的嵌套函数获取到最外层对象的目的。这是因为ES6中的箭头函数并不会创建执行上下文,也就代表着箭头函数没有自己的this,箭头函数的this会从其外部函数获取。
想要让嵌套函数获取最外层对象,总结就是两种方式
- 可以将外层函数的this保存成变量,让嵌套的函数获取该变量
- 可以将嵌套函数写成箭头函数,让其去获取外层函数的this
2.普通函数的this默认指向全局对象window
上文有
function foo(){
console.log(this)
}
foo()
就是这样会输出window对象。
在实际工作中,我们并不希望函数的执行上下文中的this默认指向全局对象,这样会造成一些误操作。如果要让函数执行上下文中的this指向某个对象,最好的方式是通过call方法来调用。
在JavaScript严格模式下也可以解决,默认执行一个函数,其函数执行上下文中的this值是undefined
"use strict";
function foo(){
console.log(this)
}
foo() //undefined
一个问题,想实现updateInfo可以改变userInfo,这段代码有什么问题
let userInfo = {
name:"jack.ma",
age:13,
sex:male,
updateInfo:function(){
//模拟xmlhttprequest请求延时
setTimeout(function(){
this.name = "pony.ma"
this.age = 39
this.sex = female
},100)
}
}
userInfo.updateInfo()
问题在于setTimeout里的回调函数还是嵌套函数,需要改成箭头函数,这样才能找到正确的this值。