this绑定问题
this是属性和方法“当前”(运行时)所在的对象。this是函数调用时发生的绑定,它的值只取决于调用位置(箭头函数除外)。
函数调用的时候会产生一个执行上下文,this是对这个执行上下文的记录。
❌误区需要注意:
this不是指向函数本身;this和函数作用域无关;this和声明位置无关系,只和调用位置有关系。
名次解释:
调用栈: 到达当前执行位置调用的所有的函数。
既然this是调用时绑定的对象,我们只需要搞清楚this的绑定规则:
1.默认绑定
当函数调用时,如果函数直接调用,没有任何修饰(函数前没有任何对象),则默认绑定this到window。
如果使用严格模式,默认绑定到undefined。
所以,立即执行函数在任何位置,函数内部的this,永远指向window(严格模式下undefined)。
<script> // 在函数作用域中声明函数 function a() { !function b() { // 函数前面有任何符号都能将函数声明变为表达式 console.log(this);// window }(); !function c() { // 函数前面有任何符号都能将函数声明变为表达式 'use strict'; console.log(this); //undefined }(); } var obj = { a } obj.a(); </script>
2. 隐式绑定(隐式丢失)
当函数调用时,前面有上下文对象时,隐式绑定规则将this绑定到该对象上。
如果多层嵌套,绑定到最近的上下文对象(obj1.obj2)上。
<script> var obj2 = {a: 30, foo: foo}; var obj1 = {a: 20, obj2}; // 注意obj2要先声明,否则报错 function foo() { console.log(this.a); // 30 // this === obj2 } obj1.obj2.foo(); </script>
js监听函数的回调函数,将this绑定到调用的DOM对象上。
<div id="root">Click ME</div> <script> const rootElement = document.querySelector('#root'); rootElement.addEventListener('click', function() { // 此处严禁使用箭头函数 console.log(this); // rootElement }) </script>
前端开发中this最容易出错的地方,就是隐式绑定丢失导致的this指向window(严格模式undefined)的问题。
隐式绑定丢失常见的几种情况:
1.赋值给变量
<script> var objA = { a: 100, foo: function() { console.log(this.a); } }; objA.foo(); // 100 this === objA var func = objA.foo; // 相当于func = function(){} // 函数调用时,相当于函数直接调用,无任何修饰,使用默认绑定 func(); // undefined this === window </script>
2.作为自定义函数的回调函数传参
将上下文对象的函数作为参数传入回调函数,相当于赋值给参数变量。
<script> var a = 'outer'; function foo() { console.log(this.a); } var obj = { a: 'inner', foo } function doFoo(fn) { // 调用的时候相当于fn=obj.foo fn(); // 调用位置,相当于直接调用foo() } doFoo(obj.foo); // 'outer' this === window </script>
这个问题可以解释,为什么React类组件中事件回调函数触发必须绑定this:
首先: React类组件中,构造函数、实例方法、静态方法都是在严格模式下运行的。
import React from 'react'; import ReactDOM from 'react-dom'; class App extends React.Component{ constructor(props) { super(props); } onClick() { console.log(this); // undefine } // onClick后面跟的是回调函数,相当于callback = this.onClick; // <button onClick={callback}>Click Me</button> // 调用的时候是直接调用callback(),所以this应该是默认绑定 // 由因为在实例方法中,是严格模式,所以是undefined // 要想在onClick方法中使用this,需要进行this显式绑定 render() { return ( <button onClick={this.onClick}>Click Me</button> ) } } ReactDOM.render(<App />, document.getElementById('root'));
3. 作为javascript一些内置函数的回调函数传参
常见的有setTimeout,数组的遍历函数如forEach, map, reduce, reduceRight, some, every,filter,flatMap等
<script> function foo() { setTimeout(function() { console.log(this); // window }, 1000); console.log(this);// objA } var objA = { foo }; objA.foo(); /* 上面是调用定时器setTimeout方法,并传入两个参数,第一个是回调函数 function setTimeout(callback, delay) { // 等待delayms; callback(); } */ </script>
<script> var arr = [1,2,3,4,5,6]; arr.forEach(function(item) { console.log(this); // window });// 相当于直接执行forEach方法 /* 上面相当于调用Array原型链上的forEach方法,传入回调函数的参数 Array.prototype.forEach = function(callback) { callback(); } */ </script>
上面的三种情况都是隐式绑定丢失,导致this使用默认绑定的情况。还有一种回调函数this被修改的情况。
PS: this绑定隐式修改
这种情况一般是,有些js库中将事件处理器将回调函数绑定到DOM元素上。
如:jquery库的事件回调函数
<body> <div id="root">Click Me 1</div> <script> $("#root").click(function() { // click方法的回调函数 console.log(this); // $("#root") }) </script> </body>
3. 显式绑定
js提供了三种显示绑定的方法apply,call,bind。另外
三种方法调用有区别,具体的可以参考三种方法的详细介绍。
通过这三种方法,可以将函数this绑定到一个不相关的对象, 还可以解决隐式绑定中隐式丢失的问题。
1. 指定对象
<script> function foo(sth) {// sth === 3 return this.a + sth; // this.a === 2 } var obj = {a:2}; var bar = function() { // apply和call绑定后立即执行,所以要用外面的函数套一层 return foo.apply(obj, arguments); // 如果是call // return foo.call(obj, ...arguments) }; var b = bar(3); console.log(b); // 5 //还可以通过bind // var bar = foo.bind(obj, 3); </script>
2. React中事件处理绑定this-不传参
React类组件中,不绑定,默认this严格模式下是undefined,需要将函数内部this绑定绑定到当前组件上。
1. 使用箭头函数固定this的指向
箭头函数内部没有this(箭头函数不能使用new命令),所以箭头函数的this绑定代码块外部作用域(函数作用域或者全局作用域)的this。即定义时所在对象。
这点和普通的this是执行时所在对象不同。而且箭头函数绑定作用域后不能修改(不能通过bind等方法修改)。
// 箭头函数固定this的方法适用于setTimeout,类实例方法
class App extends React.Component{
constructor(props) {
super(props);
}
add = () => {
console.log(this); // 当前组件
setTimeout(() => {
console.log(this); // 当前组件
},1000)
}
render() {
return (
<div onClick={this.add}>Click Me</div>
)
}
}
有一点需要注意: 箭头函数绑定的是代码块外部的函数或者全局作用域。对象没有作用域。
const shape = { q() { console.log(this); }, p: () => { console.log(this); } } console.log(shape.p()); // window/严格模式下undefined console.log(shape.q()); // shape
2. 使用bind方法在构造函数中进行绑定
class App extends React.Component{ constructor(props) { super(props); this.add = this.add.bind(this); } add(e) { console.log(this); // 当前组件 setTimeout(function(){ console.log(this); // 当前组件 }.bind(this), 1000) } render() { return ( <div onClick={this.add}>Click Me</div> ) } }
3. React中事件处理绑定this-传参
只有传参的时候才使用这些绑定方式;
因为它每次渲染生成新的回调函数,如果作为props传参,会进行额外的重新渲染。
1. 在事件属性的回调函数中使用箭头函数
这种方式,必须显式的传递事件对象e
class App extends React.Component{ constructor(props) { super(props); } add(e) { console.log(this); // 当前组件 } render() { return ( <div onClick={(e) => this.add(e)}>Click Me</div> ) } }
2. 在事件属性的回调函数中使用bind方法
这种方式会隐式的传递事件对象e, 在其他参数之后
class App extends React.Component{ constructor(props) { super(props); } add(id, e) { //第二个参数 console.log(this); // 当前组件 } render() { return ( <div onClick={this.add.bind(this, id}>Click Me</div> ) } }
3. 使用data-*属性传递参数
该方法不会在render后每次生成新函数;
add = (e) => { console.log(e.target.dataset.number) } render() { return ( <button data-number={5} onClick={this.add}>App </button> ) }
4. this赋值给变量传参
针对setTimeout等回调函数隐式绑定丢失的情况,除了上面的箭头箭头函数和bind方法,还有
class App extends React.Component{ constructor(props) { super(props); this.add = this.add.bind(this); } add(e) { console.log(this); // 当前组件 const that = this; //赋值给变量,传递 setTimeout(function(){ console.log(that); // 当前组件 }, 1000) } render() { return ( <div onClick={this.add}>Click Me</div> ) } }
5. 使用一些原生函数的自身参数
数组的很多处理方法,以回调函数作为参数,这些回调函数中this会默认是window/undefined(严格模式);
他们提供最后一个参数用于绑定内部的this。不适用于setTimeout.
class App extends React.Component{ constructor(props) { super(props); this.add = this.add.bind(this); } add(e) { console.log(this); // 当前组件 const that = this; ([1]).forEach(function(item) { console.log(this); // 当前组件 },that);// 第二个参数是被绑定的this对象 } render() { return ( <div onClick={this.add}>Click Me</div> ) } }
4. new 绑定
使用new命令实例化一个构造函数的时候,逻辑如下:
1)创建一个空对象
2)将对象的原型对象指向构造函数的prototype属性
3)将这个空对象绑定到this
4) 没有其他返回对象的情况下,返回this
5.优先级
new绑定 > 显示绑定 > 隐式绑定 > 默认绑定
PS: 特殊的this取值
在class中,this除了指代类的实例对象外,在静态方法中的this有别的指向:
静态方法中,this指向当前类。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步