不知道大家在前端面试过程中,有没有被面试官问过关于this
指向哪里,怎么改变this
指向的问题,这个问题即使工作了多年的小伙伴也会一知半解,因此这篇文章详细分析了this
指向的问题,让深入理解this
指向。
函数在调用时会创建一个执行环境,
this
是在运行时基于函数的执行环境绑定的,它可以允许函数内部引用上下文中的执行变量,使函数编程更为优雅简洁。
来看看下面这段代码,为什么不同的调用方法打印出来的结果会不一样呢?
var a = 10
const obj = {
a: 20,
foo: function() {
console.log(this.a)
return function() {
console.log(this.a)
}
}
}
obj.foo()() // 20 10
const fn = obj.foo
fn() // 10
其实很简单,因为这里不同调用方式的this
指向不同。为什么不同的函数调用方式this就指向不同?这是由什么决定的呢?下面让我们带着疑问开始深入理解this
问题吧!
默认绑定规则下,函数的运行环境为全局环境,this
默认指向Window
。
默认绑定规则下的情况有以下几种
this
指向Window
this
指向Window
this
指向Window
this
指向window
当在全局函数内直接打印this
,可以看到this
指向为Window
。
独立函数调用,即直接调用函数,如foo()
,
function foo() {
console.log(this);
}
foo()
这里的foo
被默认挂在到Window
上,相当于window.foo()
,根据函数隐式绑定规则,谁调用就指向谁,这里指向的this
指向Window
,结果如下:
同理,如果在一个嵌套函数中直接调用的函数,也属于独立函数调用,此时this
也指向Window
:
var a = 10
var obj = {
a: 20,
foo: function() {
console.log(this); // {a: 20, foo: ƒ}
console.log(this.a); // 20
function son() {
console.log(this); // Window
console.log(this.a); // 10
}
// 独立函数调用
son()
}
}
obj.foo()
上面代码中,对象obj
中的方法foo
内部还嵌套了一个子函数son
,当直接调用son
方法时,son
内部的this
指向Window
,因此son
内部的this.a
结果为全局的变量a,即10。
那么当在son
函数内部想要使用到obj
中的变量a
怎么办呢?很简单,直接把this
对象赋值给另一个变量that
,在son
方法内部引用此变量即可:
var a = 10
var obj = {
a: 20,
foo: function() {
const that = this
function son() {
console.log(that.a); // 20
}
son()
}
}
obj.foo()
自执行函数,顾名思义,就是函数被定义出来后就自动调用的函数,自执行函数的this指向代码如下:
// 1
(function() {
console.log(this); // Window
})()
// 2
var a = 10
function foo() {
(function son(that) {
console.log(this); // Window
console.log(that); // {a: 20, foo: ƒ}
})(this)
}
var obj = {
a: 20,
foo: foo,
}
obj.foo()
这里的foo
函数内部嵌套了一个自执行函数son
,son
内部的this
指向Window
,这里this
指向的原理类似独立函数调用,即先声明了一个son
方法,然后再通过son()
执行函数。如果想要在son
中获取上一级对象obj
的变量,在调用的时候,将this
指向作为参数传给自执行函数son
即可。
闭包可以理解为定义在一个函数内部的函数,是能够访问其他函数内部变量的函数。当我们在闭包中查看this
指向时,可以看到this
指向了Window
。
var a = 10
var obj = {
a: 20,
foo: function() {
var sum = this.count + 10
console.log(this.a); // 20
return function() {
console.log(this.a); // 10
return sum
}
}
}
obj.foo()()
上面代码中foo
函数第一个this.a
的this
指向为obj
对象,因此结果为20,其返回函数调用的this
指向Window
,结果为10,obj.foo()()
可以理解为:
const fn = obj.foo()
fn()
fn
为obj.foo()
返回的一个函数,fn
函数独立调用,this
指向Window
。
当函数当作方法调用时,this
指向函数的直接上级对象,称为隐式绑定。
在隐式绑定规则中认为,谁调用了函数,this
就指向谁,而且会指向函数的直接上级对象。如obj.foo()
中的foo
函数内部的this
指向obj
, 而obj1.obj2.foo()
中的foo
函数this
指向为obj2
。
var a = 10
function foo () {
console.log(this.a);
}
var obj = {
a: 20,
foo: foo,
obj2: {
a: 30,
foo: foo
}
}
// exp1
foo() // 10
// exp2
obj.foo() // 20
// exp3
obj.obj2.foo() // 30
上面代码中同样是foo
函数的调用,调用方式不同,结果也不一样。
exp1中的foo
为独立函数直接调用,因此this
指向Window
,结果为10;exp2中调用方法为obj.foo()
,foo
函数的this
指向上级调用对象为obj
;结果为20,exp3中foo
函数的直接上级对象是obj2
, 因此结果为30。
隐式绑定丢失指被隐式绑定的函数丢失了绑定对象,从而默认绑定到Window
。这种方法比较容易导致我们的项目出错,但也比较常见。
1. 隐式绑定的函数作为变量赋值,丢失this指向
下面代码中,obj
下的foo
值其实为foo
函数的地址信息,并非真正的foo
函数,obj.foo()
调用时foo
的this
隐式绑定到obj
,而当var fn = obj.foo
将函数赋值给fn
后,相当于把foo
函数的地址赋值给了fn
,此时的fn
与obj
并无关联,因此这里fn()
运行时调用的运行环境是全局环境,this
指向Window
,this.a
结果为10。
var a = 10
var obj = {
a: 20,
foo: function () {
console.log(this.a);
}
}
obj.foo() // 20
var fn = obj.foo
fn() // 10
2. 隐式绑定的函数作为参数传递给函数,丢失this指向
隐式绑定的函数,直接当作为参数传递给另一个函数时,会丢失this
绑定,从而指向全局的Window
。如下obj.foo
作为参数传递给bar
函数后,this.a
结果为10。这里的bar(obj.foo)
相当于var fn = obj.foo; bar(fn)
。
var a = 10
var obj = {
a: 20,
foo: function () {
console.log(this.a);
}
}
function bar (fn) {
fn()
}
bar(obj.foo) // 10
3. 内置对象setTimeout、setInterval中函数隐式绑定丢失
内置函数setTimeout
和setInterval
的this
默认指向Window
。
// exp1
setTimeout(function() {
console.log(this); // Window
}, 1000)
// exp2
var a = 10
var obj = {
a: 20,
foo: function () {
console.log(this.a); // 10
}
}
setTimeout(obj.foo, 1000)
顺便提一下,当setTimeout
或者setInterval
的第一个参数为箭头函数时,this
会指向上一层函数执行环境,代码如下:
var a = 10
var obj = {
a: 20,
foo: function () {
console.log(this.a); // 20
setTimeout(() => {
console.log(this.a); // 20
}, 1000)
setTimeout(function() {
console.log(this.a); // 10
}, 1000);
}
}
obj.foo()
当我们想要把函数绑定到指定对象上时,就可以使用call
、 apply
、bind
等方法手动改变this
指向,即显式绑定。
下面代码中,分别用call
、 apply
、bind
三种方法例举了将foo
显式绑定到p
对象上的方法,其中call
、 apply
显式绑定的后会直接调用,而bind
方法显式绑定this
指向需要手动调用。
var a = 10
var obj = {
a: 20,
foo: function () {
console.log(this.a);
}
}
var p = {
a: 30,
}
obj.foo() // 20
obj.foo.call(p) // 30
obj.foo.apply(p) // 30
const fn = obj.foo.bind(p)
fn() // 30
硬绑定
显式绑定可以帮助我们改变this
指向到指定的对象,但是不能解决隐式绑定丢失的问题,如:
var a = 10
function foo() {
console.log(this.a);
}
var obj = {
a: 20,
foo: foo
}
var p = {
a: 30
}
function func(fn) {
fn()
}
func.call(p, obj.foo) // 10
上面代码中使用call
来绑定this
指向到p
对象上,但是最终this
指向还是指向Window
,此时我们可以通过硬绑定来解决这个问题。
var a = 10
function foo() {
console.log(this.a);
}
var obj = {
a: 20,
foo: foo
}
var p = {
a: 30
}
function func(fn) {
fn()
}
let bar = function () {
foo.call(p)
}
bar() // 30
new
绑定,是我们平时比较常用的,其实就是通过创建一个构造函数,然后new
一个实例对象,此时的this
指向就是new
出来的实例对象。
当我们new
一个对象的时候,主要做了以下几件事:
如下,先声明一个构造函数Person
,通过new
生成一个zhangsan
的实例对象,则zhangsan
的foo
函数中,this
的指向为zhangsan
实例。
function Person(name, age) {
this.name = name
this.age = age
this.foo = function () {
console.log(this.name);
}
}
const zhangsan = new Person('zhangsan', 18)
console.log(zhangsan) // {name: 'zhangsan', age: 18, foo: ƒ}
zhangsan.foo() // zhangsan
1. 独立调用的函数内部this
为undifined
function foo() {
"use strict"
console.log(this); undifined
}
foo()
2. call()
和apply()
内部的this
始终是它们的第一个参数
var a = 10
var obj = {
a: 20,
foo: function () {
"use strict"
console.log(this);
}
}
// 为null | undefined时, 非严格模式下,this指向window
obj.foo.call(null) // null
obj.foo.call(undefined) // undefined
obj.foo.apply(null) // null
obj.foo.apply(undefined) // undefined
var fn = obj.foo.bind(null)
fn()
this
指向是个比较复杂的知识点,当然,如果我们真正理解了this
指向的原理后,再遇到this
指向的问题就变得很简单了,理解this
指向,不仅是前端面试的加分项,也会对我们平时的开发和学习有诸多好处。
我们来总结一下。this
指向的绑定原则有以下几种:
this
指向全局的Window
this
就指向谁,别忘记隐式绑定丢失的情况call
、apply
、bind
改变this
指向new
绑定,对构造函数new
一个实例,this
指向new
的实例对象原文链接:https://www.jianshu.com/p/351d601cbed3