首页 > 编程笔记
JS this到底指向什么?
在 JavaScript 中,this 的取值与它所处的上下文(Context)有关,并且在普通和严格模式下也有所不同。
在全局作用域上下文中,this 指向的是全局对象,全局对象在浏览器中是 window 对象,在 Node.js 中是当前模块,下方示例展示了全局作用域下的 this 的取值,代码如下:
对于在函数上下文中的 this,它的值需要根据函数的调用方式决定。如果是普通的函数,且使用一般的方式进行调用(不使用 new 以构造函数进行调用),则函数体里的 this 指向的都是 globalThis,即 window(浏览器)或 global(Node.js),可以通过下方示例进行验证,代码如下:
如果以构造函数的方式调用函数,则 this 指向的是新创建的对象,代码如下:
在对象的方法中,如果方法是使用普通函数定义的,则 this 指向的是当前对象,代码如下:
如果对象方法是使用箭头函数定义的,则 this 的指向会有所不同。箭头函数中 this 的指向是根据定义时它所在的代码位置决定的,即词法上下文(LexicalContext),this 的取值为包裹箭头函数的作用域中 this 的值。
在上方示例中,如果把 f() 函数改为箭头函数,则它里边的 this 的指向与全局作用域中的 this 的指向一样,即浏览器下为 window,Node.js 下为 module.exports,因为字面值的 obj 是在全局作用域中定义的(定义对象的大括号为对象字面值的语法,并未形成新的块级作用域),包裹 f() 函数的作用域就是全局作用域,代码如下:
如果在构造函数中使用箭头函数,则箭头函数的 this 就是构造函数中的 this,即指向创建的对象,代码如下:
一般在对象中使用普通函数作为对象的方法,这样可以保留 this 的指向,但是有些特殊情况使用箭头函数会更合适。
先来看一个例子,这个例子并不是真实的事件处理方式,不过可以解释 this 在回调函数中的问题,代码如下:
emitClick() 函数简单地模拟了单击事件的触发,它接收一个回调函数,用于在单击事件触发后要执行的业务逻辑。
接下来创建了按钮组件的实例,并触发了单击事件,把按钮中的 handleClick() 传递给了 emitClick,这样就会执行它里边的代码。
看起来应该是打印出 label 属性的值:"按钮",但是结果却是 undefined。这是因为 handleClick() 在传递给 emitClick() 的时候,this 的指向已经发生了变化。
可以看到在 emitClick() 中调用 callback() 时,也就是 Button 中的 handleClick(),左边没有任何东西,那么此时 this 指向的是全局对象,它里边没有 label 属性,所以打印出了 undefined。
要解决这个问题有3种方法,第1种解决方法是在 Button 构造函数中,把 this 的值保存到一个变量中,通常使用 self 作为变量名表示对象本身,然后在 handleClick() 中引用,代码如下:
第2种解决方法是使用箭头函数,代码如下:
第3种解决方法是使用函数对象中的 bind() 方法。使用 bind() 可以给函数绑定运行时的 this,并返回新的函数,这样在后边调用这个新函数时,它的 this 就是使用 bind() 所绑定的 this。
例如将 handleClick() 修改为使用 bind(),代码如下:
这3种解决方法可以任选其一,不过使用箭头函数的方式更为简洁清晰。
在全局作用域上下文中,this 指向的是全局对象,全局对象在浏览器中是 window 对象,在 Node.js 中是当前模块,下方示例展示了全局作用域下的 this 的取值,代码如下:
this===window; //true,浏览器环境下 this===module.exports //true,Node.js环境下关于不同环境下的全局对象,也可以使用 globalThis 来统一获取,但要注意 Node.js 中的 globalThis 指向的是 global 对象,与上例中全局作用域的 this 指向的 module.exports 不同,在浏览器下 globalThis 指向的对象为 window,与全局作用域 this 所指向的对象相同。
对于在函数上下文中的 this,它的值需要根据函数的调用方式决定。如果是普通的函数,且使用一般的方式进行调用(不使用 new 以构造函数进行调用),则函数体里的 this 指向的都是 globalThis,即 window(浏览器)或 global(Node.js),可以通过下方示例进行验证,代码如下:
function func(){ console.log(this===globalThis); } func();//true而在严格模式下,普通函数中 this 的值为 undefined。
如果以构造函数的方式调用函数,则 this 指向的是新创建的对象,代码如下:
function Func(){ this.a=5; } const obj=new Func(); obj.a; //5,obj即为Func()中this的指向
在对象的方法中,如果方法是使用普通函数定义的,则 this 指向的是当前对象,代码如下:
const obj={ a:1, f(){ console.log(obj===this); console.log(this.a); }, }; obj.f();//true //1要判断普通函数中 this 的指向有一个简单直观的方法,即看它调用时左侧的代码:
- 如果左侧没有任何代码,则 this 指向的是全局作用域中的对象,例如 f()。
- 如果为对象,则指向的是这个对象,例如 obj.f(),f() 中的 this 指向的是 obj,又如 obj.inner.f(),f() 中的 this 指向的是 inner 对象。
- 如果函数继承自 prototype,则这个规则也保持一致,哪个对象调用的这种方法,则它里边的 this 就指向哪个对象,对于 getter 和 setter 所定义的函数也是如此。
如果对象方法是使用箭头函数定义的,则 this 的指向会有所不同。箭头函数中 this 的指向是根据定义时它所在的代码位置决定的,即词法上下文(LexicalContext),this 的取值为包裹箭头函数的作用域中 this 的值。
在上方示例中,如果把 f() 函数改为箭头函数,则它里边的 this 的指向与全局作用域中的 this 的指向一样,即浏览器下为 window,Node.js 下为 module.exports,因为字面值的 obj 是在全局作用域中定义的(定义对象的大括号为对象字面值的语法,并未形成新的块级作用域),包裹 f() 函数的作用域就是全局作用域,代码如下:
const obj={ f:()=>{console.log(this)} } obj.f(); //Window
如果在构造函数中使用箭头函数,则箭头函数的 this 就是构造函数中的 this,即指向创建的对象,代码如下:
function Func(){ const init=()=>{ this.a=5; }; init(); } const obj=new Func(); obj.a; //5
一般在对象中使用普通函数作为对象的方法,这样可以保留 this 的指向,但是有些特殊情况使用箭头函数会更合适。
先来看一个例子,这个例子并不是真实的事件处理方式,不过可以解释 this 在回调函数中的问题,代码如下:
function Button(label){ this.label=label; this.handleClick=function(){ console.log(this.label); }; } //模拟触发单击事件 function emitClick(callback){ callback(); } const btn=new Button("按钮"); emitClick(btn.handleClick); //undefined代码中首先定义了 Button 构造函数,代表一个按钮组件,它有 label 属性和处理单击事件的方法 handleClick(),方法里边简单地打印出来了按钮的 label 属性值。
emitClick() 函数简单地模拟了单击事件的触发,它接收一个回调函数,用于在单击事件触发后要执行的业务逻辑。
接下来创建了按钮组件的实例,并触发了单击事件,把按钮中的 handleClick() 传递给了 emitClick,这样就会执行它里边的代码。
看起来应该是打印出 label 属性的值:"按钮",但是结果却是 undefined。这是因为 handleClick() 在传递给 emitClick() 的时候,this 的指向已经发生了变化。
可以看到在 emitClick() 中调用 callback() 时,也就是 Button 中的 handleClick(),左边没有任何东西,那么此时 this 指向的是全局对象,它里边没有 label 属性,所以打印出了 undefined。
要解决这个问题有3种方法,第1种解决方法是在 Button 构造函数中,把 this 的值保存到一个变量中,通常使用 self 作为变量名表示对象本身,然后在 handleClick() 中引用,代码如下:
function Button(label){ this.label=label; var self=this; this.handleClick=function(){ console.log(self.label); }; }这时,Button 构造函数和 handleClick() 形成了一个闭包,handleClick() 可以捕获 self 变量的值,后边无论在哪里调用,都可以访问它所指向的对象中的属性了。
第2种解决方法是使用箭头函数,代码如下:
this.handleClick=()=>{ console.log(this.label); };因为箭头函数中的 this 是根据箭头函数定义时的位置决定的,所以使用箭头函数定义 handleClick() 时,this 已经确定为构造函数 Button 的 this,所以最后成功地访问了 label 属性。
第3种解决方法是使用函数对象中的 bind() 方法。使用 bind() 可以给函数绑定运行时的 this,并返回新的函数,这样在后边调用这个新函数时,它的 this 就是使用 bind() 所绑定的 this。
例如将 handleClick() 修改为使用 bind(),代码如下:
this.handleClick=function(){console.log(this.label)}.bind(this); //或者这样更清楚一些 //this.handleClick=function(){console.log(this.label)} //this.handleClick=this.handleClick.bind(this)bind() 参数中的 this 就是给 handleClick() 绑定的 this,由于是在 Button 构造函数中,所以 this 指向的是 Button 构造函数中的 this,这样也能打印出 label 属性的值。
这3种解决方法可以任选其一,不过使用箭头函数的方式更为简洁清晰。