![3ed4a13dd085fdfaa8cf778324a67efb.png](https://img-blog.csdnimg.cn/img_convert/3ed4a13dd085fdfaa8cf778324a67efb.png)
背景
完成了对作用域,作用域链的学习,今天让我们看this在JavaScript中的表现
先看下面的代码
var bar = {
myName:"bar",
printName: function () {
console.log(myName)
}
}
let myName = "global"
let _printName = bar.printName
_printName() // global
bar.printName() // global
虽然bar.printName所指向的函数被定义在bar对象内部,但其作用域链依然指向global中的myName变量。
往往在实际开发中,我们需要在对象内部的方法使用对象内部的属性,但JavaScript的作用域机制并不支持这一功能,所以基于这种需求,this被提出。
我们将代码稍作修改
var bar = {
myName:"bar",
printName: function () {
console.log(this.myName)
}
}
let myName = "global"
let _printName = bar.printName
_printName() // undefined
bar.printName() // bar
通过测试代码,我们发现同样的函数在不同的调用方式下this发生了变化,这显然与符合词法作用域规则的作用域不相同,所以this机制与作用域机制是两套不同的系统。
JavaScript中的this
在之前的文章中,我们了解过执行上下文的结构:变量环境、词法环境和this绑定
![6b4fa1da51c0aedec51153f389b46514.png](https://img-blog.csdnimg.cn/img_convert/6b4fa1da51c0aedec51153f389b46514.png)
this的指向与执行上下文绑定,我们知道执行上下文有三种:
- 全局执行上下文
- 函数执行上下文
- eval执行上下文
所以this也有同样的三种,但这里我们只讨论全局中的this和函数中的this。
全局中的this
直接在浏览器控制台中输入console.log(this)得到是window对象,而在nodejs中打印this得到的是global对象。我们发现,全局中的this会指向全局对象,当然随着运行环境的不同全局对象在浏览器中是window,在nodejs中就是global。
函数中的this
相比全局中的this,函数中的this会复杂的多。
首先观察如下代码:
function foo(){
console.log(this)
}
foo() // window
运行函数foo后,打印出的依然是window对象。我们可以得出结论,默认情况下使用函数名直接调用函数,函数中的this会指向全局对象。 ** 这也解释了为什么在开篇的代码中,用一个新的变量指向bar.printName并调用,其中的log(this.myName)其实是访问了window.myName,则得到了undefined的结果。
1. 调用对象的方法
还是开篇的代码,当我们调用bar.printName方法时,可以打印出bar对象中的myName变量。所以使用对象.方法名的方式调用函数时,函数中的this会指向对象。
2. apply、call和bind
JavaScript为我们提供了动态改变函数中this指向的api: apply、call和bind。
apply和call
function foo() {
console.log(this)
}
var obj1 = {
myName: 'obj1'
}
var obj2 = {
myName: 'obj2'
}
foo.apply(obj1) // { myName: "obj1" }
foo.apply(obj2) // { myName: "obj2" }
foo.call(obj1) // { myName: "obj1" }
foo.call(obj2) // { myName: "obj2" }
同一个函数foo,调用其apply和call方法并传入一个对象做为其this的指向可以动态的改变this。
apply和call的区别在于参数不同,并不是这里的重点,感兴趣的同学可自行查阅:
- MDN Function.prototype.apply()
- MDN Function.prototype.call()
bind
bind与call、apply略有不同
function foo() {
console.log(this)
}
var obj = {
myName: 'obj'
}
var bar = foo.bind(obj)
foo() // window
bar() // { myName: "obj" }
我们发现调用bind方法并传入一个对象后,会返回一个新的函数。调用这个新的函数时,其中的this就指向了调用bind方法时传入的对象了。
由于bind的存在,我们可以将一些函数通过bind生成新的函数,并将这些新生成的函数传递给其他函数调用,无论如何传递,当这些函数被调用时,其中的this都是之前的对象。这一功能在没有ES6 箭头函数之前尤为重要。
3. 构造函数中的this
由于JavaScript并没有原生class的存在,class关键字只是一种语法糖,所以当我们想动态的创建一个对象时,可以定一个一个函数做作创建对象的构造函数,并使用new关键字创建对象。
function Person(name, age) {
this.name = name
this.age = age
}
var person1 = new Person('小明', 18)
var person2 = new Person('小刚', 19)
console.log(person1) // { name: '小明', age: 18 }
console.log(person2) // { name: '小刚', age: 19 }
new关键字主要完了以下的操作:
- 创建一个空的简单JavaScript对象(即 {})
- 将构造函数挂载在创建的对象上
- 以空对象做为构造函数的this调用函数
- 如果构造函数没有返回值,则以this做为返回
感兴趣的同学可以参考 MDN new 运算法
虽然this的存在,提高了JavaScript的动态性,为开发者提供了便利,但还是有缺陷需要注意。
this的缺陷
1. 在嵌套的函数中,this不会继承
让我们分析如下代码
var myObj = {
name : "myObj",
showThis: function(){
console.log(this)
function bar(){console.log(this)}
bar()
}
}
myObj.showThis()
第4行的log打印了myObj对象,而第5行的log打印了window。虽然bar函数被定义在showThis所指向的函数内,但两个this不没有什么关系。这也充分印证了this与作用域的区别。
我们修改一些代码来解决这个问题
a. 定义变量self
var myObj = {
name : "myObj",
showThis: function(){
console.log(this)
const self = this
function bar(){
console.log(self.name)
}
bar()
}
}
myObj.showThis()
我们在能正确访问myObj.name的函数中定一个变量self,使其指向this,并在内部函数中用self变量代替this。
这种方式其实就是将this的机制转换成作用域的机制.
b. 使用call/apply
var myObj = {
name : "myObj",
showThis: function(){
console.log(this)
function bar(){
console.log(this.name)
}
bar.call(this) // 同bar.apply(this)
}
}
myObj.showThis()
我们将内部函数bar的调用方式由原来的常规函数名调用方式改成使用call/apply的方式,将showThis的this传递给bar函数,依然可以实现同样的效果。
c. ES6 箭头函数
var myObj = {
name : "myObj",
showThis: function(){
console.log(this)
const bar = () => {
console.log(this.name)
}
bar()
}
}
myObj.showThis()
由于箭头函数并不会创建自己的上下文,所以其this其实是外部函数的this名,这样同样可以达到效果。
2. 常规调用
通过前面章节的介绍,我们知道函数在常规调用下,其中的this会指向全局对象window。
往往不经意间可能改变了window中变量的值,而导致一些意想不到的异常,而这类全局变量污染的异常比常规的异常更难重现,所以在实际开发中一定要注意this的指向。
当然JavaScript为我们提供了“严格模式”。在严格模式下,函数被常规调用时,this 的值为undeifnd。
总结
至此,我们探讨了this的部分知识,在使用this时需要注意
- 常规调用函数,this会指向全局对象
- 通过对象.方法名的方式,this会指向对象
- 可以通过apply、call和bind改变函数的this指向
- 在多层嵌套中的函数中,this是不相同的